@faapi/faapi 4.0.0 → 4.2.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 +5 -0
- package/dist/cli/index.js +197 -58
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +221 -85
- package/dist/index.js.map +1 -1
- package/dist/testing.js +42 -15
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime/sse.ts","../src/errors/FaapiError.ts","../src/errors/httpErrors.ts","../src/response/responseFormatter.ts","../src/runtime/createContext.ts","../src/utils/isPlainObject.ts","../src/response/pendingMeta.ts","../src/response/toResponse.ts","../src/injection/resolveInjection.ts","../src/utils/queryToObject.ts","../src/injection/registries.ts","../src/injection/agentRegistry.ts","../src/injection/agentHandle.ts","../src/injection/injectParams.ts","../src/runtime/invokeHandler.ts","../src/testServer.ts","../src/router/scanRoutes.ts","../src/router/constants.ts","../src/utils/normalizePath.ts","../src/router/parseRouteFile.ts","../src/utils/importWithCacheBust.ts","../src/middleware/loadMiddlewares.ts","../src/utils/prodPaths.ts","../src/router/sortRoutes.ts","../src/cli/generateSchemaFiles.ts","../src/utils/atomicWrite.ts","../src/ast/createProgram.ts","../src/ast/extractHandlerTypes.ts","../src/ast/resolveTypeNode.ts","../src/runtime/inputType.ts","../src/validator/schemaName.ts","../src/injection/analyzeInjection.ts","../src/cli/collectRouteSchemaSources.ts","../src/ast/generateZodSchema.ts","../src/cli/compileOnDemand.ts","../src/cli/compileSourceFiles.ts","../src/cli/aliasPlugin.ts","../src/utils/resolveAlias.ts","../src/utils/readTsconfig.ts","../src/cli/compileDevRoutes.ts","../src/cli/collectImports.ts","../src/validator/validateInput.ts","../src/server/createServer.ts","../src/router/matchRoute.ts","../src/loader/resolveExports.ts","../src/loader/validateRouteModule.ts","../src/loader/loadRouteModule.ts","../src/utils/parseJsonBody.ts","../src/utils/parseMultipart.ts","../src/runtime/resolveInput.ts","../src/response/sendNodeResponse.ts","../src/utils/getClientIp.ts","../src/middleware/cors.ts","../src/middleware/helmet.ts","../src/middleware/compression.ts","../src/middleware/etag.ts","../src/middleware/logger.ts","../src/server/handleWsUpgrade.ts","../src/server/serverUtils.ts","../src/runtime/wsHandler.ts","../src/wsTestClient.ts"],"sourcesContent":["/**\n * SSE(Server-Sent Events)支持\n *\n * 让 handler 能向客户端推送流式事件,用于 LLM token 流、进度通知等场景。\n *\n * 核心导出:\n * - `encodeSseEvent(event)`:把 SSE 事件对象编码为符合 HTML5 SSE 规范的字符串\n * - `createSseWriter()`:创建一个 SseWriter,封装 ReadableStream + Response,提供 send/close/sendError API\n * - `SseWriter`:writer 类型,ctx.sse() 返回此类型\n *\n * 设计要点:\n * - writer 内部用 ReadableStream + TextEncoder,send 时 enqueue,close 时 close controller\n * - response 预设 text/event-stream、no-cache、keep-alive 头,状态码默认 200\n * - close 后再 send 静默忽略,避免 handler 异步流程中误写已关闭的流\n * - sendError 向流写入 event: error 后关闭,用于流式输出中报错的优雅终止\n *\n * 与 ctx 的集成:\n * - ctx.sse() 调用 createSseWriter(),并把 response 缓存到 ctx 内部字段\n * - invokeHandler 在 handler 返回后检查 ctx 是否持有 SSE response,有则优先使用\n */\n\n/**\n * SSE 事件字段\n *\n * 遵循 HTML5 SSE 规范:\n * - `data`:消息数据,多行时每行加 `data: ` 前缀;对象自动 JSON.stringify\n * - `event`:事件类型,客户端可用 addEventListener(event) 监听\n * - `id`:事件 ID,客户端断线重连时通过 Last-Event-ID 头发送\n * - `retry`:重连等待时间(毫秒)\n * - `comment`:注释行(以 `:` 开头),用于 keep-alive 心跳,不传递给客户端消息\n */\nexport interface SseEvent {\n /** 消息数据。字符串原样输出;对象自动 JSON.stringify;多行时每行加 data: 前缀 */\n data?: unknown;\n /** 事件类型,客户端可用 addEventListener 监听 */\n event?: string;\n /** 事件 ID,客户端断线重连时通过 Last-Event-ID 头发送 */\n id?: string | number;\n /** 重连等待时间(毫秒) */\n retry?: number;\n /** 注释行(以 : 开头),用于 keep-alive 心跳 */\n comment?: string;\n}\n\n/**\n * 把 SSE 事件对象编码为符合 HTML5 SSE 规范的字符串\n *\n * 字段顺序固定:comment > event > id > retry > data,末尾空行分隔事件。\n *\n * @param event SSE 事件对象\n * @returns 编码后的字符串(含末尾空行)\n */\nexport function encodeSseEvent(event: SseEvent): string {\n let out = '';\n\n // comment(注释行,以 : 开头)\n if (event.comment !== undefined) {\n out += `: ${event.comment}\\n`;\n }\n\n // event\n if (event.event !== undefined) {\n out += `event: ${event.event}\\n`;\n }\n\n // id\n if (event.id !== undefined) {\n out += `id: ${event.id}\\n`;\n }\n\n // retry\n if (event.retry !== undefined) {\n out += `retry: ${event.retry}\\n`;\n }\n\n // data:对象 JSON.stringify,多行每行加前缀\n if (event.data !== undefined) {\n let dataStr: string;\n if (typeof event.data === 'string') {\n dataStr = event.data;\n } else if (event.data === null) {\n dataStr = 'null';\n } else {\n dataStr = JSON.stringify(event.data);\n }\n // 多行 data:每行加 data: 前缀\n const lines = dataStr.split('\\n');\n for (const line of lines) {\n out += `data: ${line}\\n`;\n }\n }\n\n // 空行结束事件\n out += '\\n';\n return out;\n}\n\n/**\n * SSE writer:封装流式推送 API\n *\n * 通过 `ctx.sse()` 创建,handler 调用 `send` 推送事件,`close` 关闭流。\n * 框架在 handler 返回后,自动使用 writer.response 作为 HTTP 响应。\n */\nexport interface SseWriter {\n /** 推送一个 SSE 事件 */\n send(event: SseEvent): void;\n /**\n * 直接写入原始字节/字符串,不做任何 SSE 序列化\n *\n * 用于透传上游已有的 SSE 原文(如 LLM 中转平台逐 chunk 转发 OpenAI 响应)。\n * 调用方负责保证内容符合 HTML5 SSE 规范;`send` 会再次加 `data: ` 前缀,\n * 不适用于原文透传场景。\n *\n * 接受 string 或 Uint8Array(Buffer 是 Uint8Array 子类,自然兼容)。\n * 与 `send` 一致:close/aborted 后静默忽略,不抛错。\n */\n sendRaw(chunk: string | Uint8Array): void;\n /** 推送一个 error 事件并关闭流(用于流式输出中报错的优雅终止) */\n sendError(error: unknown): void;\n /** 关闭流(多次调用安全) */\n close(): void;\n /** 流是否已关闭(handler 主动 close 或框架自动 close) */\n readonly closed: boolean;\n /** 客户端是否已断开(ReadableStream 被 cancel) */\n readonly aborted: boolean;\n /** 对应的 HTTP Response(由框架使用,用户一般不需要直接访问) */\n readonly response: Response;\n}\n\n/**\n * 创建一个 SSE writer\n *\n * 内部用 ReadableStream + TextEncoder 实现,send 时把编码后的事件 enqueue 到流,\n * close 时关闭 controller。response 预设标准 SSE 头。\n *\n * aborted 检测:监听 ReadableStream 的 cancel 钩子,客户端断开(cancel)时置为 true。\n * 此时 send 静默忽略,handler 可通过 writer.aborted 退出循环。\n */\nexport function createSseWriter(): SseWriter {\n const encoder = new TextEncoder();\n let controller: ReadableStreamDefaultController<Uint8Array> | null = null;\n let closed = false;\n let aborted = false;\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c;\n },\n cancel() {\n // 客户端断开连接(cancel ReadableStream)\n aborted = true;\n closed = true;\n controller = null;\n },\n });\n\n const response = new Response(stream, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n\n const writer: SseWriter = {\n send(event: SseEvent): void {\n if (closed || !controller) return;\n const text = encodeSseEvent(event);\n controller.enqueue(encoder.encode(text));\n },\n\n sendRaw(chunk: string | Uint8Array): void {\n if (closed || !controller) return;\n const bytes = typeof chunk === 'string' ? encoder.encode(chunk) : chunk;\n controller.enqueue(bytes);\n },\n\n sendError(error: unknown): void {\n if (closed || !controller) return;\n const message = error instanceof Error ? error.message : String(error);\n const text = encodeSseEvent({ event: 'error', data: message });\n try {\n controller.enqueue(encoder.encode(text));\n } finally {\n writer.close();\n }\n },\n\n close(): void {\n if (closed) return;\n closed = true;\n if (controller) {\n try {\n controller.close();\n } catch {\n // controller 可能已关闭,忽略\n }\n controller = null;\n }\n },\n\n get closed(): boolean {\n return closed;\n },\n\n get aborted(): boolean {\n return aborted;\n },\n\n get response(): Response {\n return response;\n },\n };\n\n return writer;\n}\n","import type { ErrorCode } from './errorCodes';\n\nexport class FaapiError extends Error {\n constructor(\n public readonly code: ErrorCode,\n message: string,\n public readonly statusCode: number,\n ) {\n super(message);\n this.name = 'FaapiError';\n }\n}\n","import { FaapiError } from './FaapiError';\n\n/**\n * 根据 issues 中的 code 推导 HTTP 状态码\n *\n * 语义参考 RFC 7807 / Rails / Laravel / Spring:\n * - 400 Bad Request:请求语法错误或结构不完整\n * - INVALID_FORMAT:JSON 格式错误、Date 非 ISO 8601\n * - MISSING_FIELD:缺少必填字段\n * - 422 Unprocessable Entity:请求语法正确但语义错误\n * - TYPE_MISMATCH:类型不匹配(string 期望 number)\n * - INVALID_VALUE:值不在允许范围(字面量/enum 不匹配)\n * - COERCE_FAILED:query 字符串转换失败\n *\n * 多个 issue 时取最高严重度(400 优先于 422)。\n */\nfunction deriveStatusCode(issues: ValidationIssue[]): number {\n // 有 400 类 issue → 400;否则 422\n const has400 = issues.some((i) => i.code === 'INVALID_FORMAT' || i.code === 'MISSING_FIELD');\n return has400 ? 400 : 422;\n}\n\nexport class ValidationError extends FaapiError {\n constructor(\n message: string,\n public readonly issues: ValidationIssue[],\n ) {\n super('VALIDATION_ERROR', message, deriveStatusCode(issues));\n this.name = 'ValidationError';\n }\n}\n\nexport class RouteNotFoundError extends FaapiError {\n constructor(path: string) {\n super('ROUTE_NOT_FOUND', `Route not found: ${path}`, 404);\n this.name = 'RouteNotFoundError';\n }\n}\n\nexport class MethodNotAllowedError extends FaapiError {\n constructor(\n method: string,\n path: string,\n public readonly allowedMethods: string[],\n ) {\n super('METHOD_NOT_ALLOWED', `Method ${method} not allowed for ${path}`, 405);\n this.name = 'MethodNotAllowedError';\n }\n}\n\nexport class InternalError extends FaapiError {\n constructor(message: string) {\n super('INTERNAL_ERROR', message, 500);\n this.name = 'InternalError';\n }\n}\n\nexport class ModuleLoadError extends FaapiError {\n constructor(filePath: string, reason: string) {\n super('MODULE_LOAD_ERROR', `Failed to load module ${filePath}: ${reason}`, 500);\n this.name = 'ModuleLoadError';\n }\n}\n\n/**\n * 请求体超过大小限制(413 Payload Too Large)\n *\n * 由 createServer 的 limitStreamSize 在流式读取阶段抛出,\n * 经 formatErrorResponse 转换为 413 响应,而非兜底 500。\n */\nexport class PayloadTooLargeError extends FaapiError {\n constructor(maxSize: number) {\n super('PAYLOAD_TOO_LARGE', `Request body exceeds size limit of ${maxSize} bytes`, 413);\n this.name = 'PayloadTooLargeError';\n }\n}\n\n/**\n * 校验问题类型\n *\n * 结构化错误信息,便于上层(全局错误中间件/前端)按 code 做不同处理,\n * 不依赖字符串解析。message 仅为人类可读的兜底描述。\n *\n * code 与 HTTP 状态码的映射(由 ValidationError 推导):\n * - INVALID_FORMAT / MISSING_FIELD → 400 Bad Request\n * - TYPE_MISMATCH / INVALID_VALUE / COERCE_FAILED → 422 Unprocessable Entity\n */\nexport interface ValidationIssue {\n /** 字段路径,如 'user.address.city' */\n path: string;\n /** 错误码,机器可读的契约 */\n code: ValidationErrorCode;\n /** 期望类型/值,如 'number' / '\"admin\" | \"user\"' */\n expected: string;\n /** 实际类型/值,如 'string' / 'undefined' */\n received: string;\n /** 人类可读的本地化消息(兜底,不保证稳定) */\n message: string;\n}\n\nexport type ValidationErrorCode =\n | 'TYPE_MISMATCH' // 类型不匹配(string 期望 number) → 422\n | 'MISSING_FIELD' // 缺少必填字段 → 400\n | 'INVALID_FORMAT' // 格式错误(JSON 解析失败、Date 非 ISO 等) → 400\n | 'INVALID_VALUE' // 值不在允许范围(字面量/enum 不匹配) → 422\n | 'COERCE_FAILED'; // 类型转换失败(query 字符串转 number/boolean 失败) → 422\n","import type { ResponseConfig } from '../config/configTypes';\nimport { FaapiError } from '../errors/FaapiError';\nimport { ValidationError, MethodNotAllowedError, PayloadTooLargeError } from '../errors/httpErrors';\n\n/**\n * 统一响应格式化中心\n *\n * 集中所有响应包装规则,确保 handler return 值的自动包裹、ctx.fail() 主动错误响应、\n * formatErrorResponse 抛错兜底 三条路径共享同一套 ok/fail 函数,避免响应格式漂移。\n *\n * 路径分工:\n * - 成功路径: handler `return data` → wrapOkResult → toResponse 多类型序列化\n * - 显式成功: handler `return ctx.ok(data)` → wrapOkResult → jsonOk 构造 Response\n * - 主动错误: handler `return ctx.fail({...})` → formatFailResponse → jsonOk\n * - 抛错兜底: handler throw → formatErrorResponse(err, config) → jsonOk\n *\n * 业务方通过 faapi.config.ts 的 `response.ok` / `response.fail` 自定义包装函数,\n * 三条路径自动一致生效。\n */\n\n/** 默认 ok 包装:(data) => ({ data }) */\nexport function defaultOk(data: unknown): Record<string, unknown> {\n return { data };\n}\n\n/**\n * 默认 fail 包装:省略的字段不放入 error 对象\n *\n * `code` 为 undefined 时不放入响应,保持 body 紧凑。\n */\nexport function defaultFail(e: {\n status?: number;\n code?: string;\n message: string;\n}): Record<string, unknown> {\n const error: Record<string, unknown> = { message: e.message };\n if (e.code !== undefined) error.code = e.code;\n return { error };\n}\n\n/** 从 ctx.config 读取 ResponseConfig(业务方在 faapi.config.ts 中配置的 response 字段) */\nexport function getResponseConfig(\n config: Record<string, unknown> | undefined,\n): ResponseConfig | undefined {\n return (config as { response?: ResponseConfig } | undefined)?.response;\n}\n\n/** 解析 ok 函数:业务方自定义 ?? 框架默认 */\nexport function resolveOkFn(config: Record<string, unknown> | undefined) {\n return getResponseConfig(config)?.ok ?? defaultOk;\n}\n\n/** 解析 fail 函数:业务方自定义 ?? 框架默认 */\nexport function resolveFailFn(config: Record<string, unknown> | undefined) {\n return getResponseConfig(config)?.fail ?? defaultFail;\n}\n\n/**\n * 构造 JSON 成功响应(不包裹,原样序列化)\n *\n * 用于 ctx.json / ctx.ok 等显式响应场景。\n */\nexport function jsonOk(body: unknown, status = 200, extraHeaders?: HeadersInit): Response {\n return jsonRaw(body, status, extraHeaders);\n}\n\n/** 内部:构造 JSON Response,不包外层 */\nfunction jsonRaw(body: unknown, status: number, extraHeaders?: HeadersInit): Response {\n const headers = new Headers({ 'Content-Type': 'application/json' });\n if (extraHeaders) {\n const extra = new Headers(extraHeaders);\n extra.forEach((value, key) => headers.set(key, value));\n }\n return new Response(JSON.stringify(body), { status, headers });\n}\n\n/**\n * 成功响应自动包装:handler return 值 → okFn(data) 包裹\n *\n * 规则:\n * - Response 对象:不包裹(ctx.ok/ctx.fail/ctx.json 等返回的 Response 原样透传)\n * - 其他值(含 null/undefined):用 config.response.ok(或默认 (data) => ({ data })) 包裹\n *\n * 注:null/undefined 也会被包裹为 { data: null } / { data: undefined }。\n *\n * 不构造 Response,只返回包装后的值——最终序列化交给 toResponse 处理(支持多类型分发 +\n * 合并 ctx.meta 的 setStatus/setHeader/setCookie)。\n */\nexport function wrapOkResult(\n result: unknown,\n config: Record<string, unknown> | undefined,\n): unknown {\n if (result instanceof Response) return result;\n return resolveOkFn(config)(result);\n}\n\n/**\n * 业务方主动错误响应:FailOptions → Response\n *\n * handler `return ctx.fail({ status, code, message })` 的实现。\n * 用 config.response.fail(或默认实现)包装为 `{ error: { ... } }` 结构。\n *\n * `status` 省略时默认 500;`code` 省略时由 fail 函数决定是否放入 body。\n */\nexport function formatFailResponse(\n options: { status?: number; code?: string; message: string },\n config: Record<string, unknown> | undefined,\n): Response {\n const failFn = resolveFailFn(config);\n const body = failFn({\n status: options.status,\n code: options.code,\n message: options.message,\n });\n return jsonOk(body, options.status ?? 500);\n}\n\n/**\n * 抛错兜底响应:把 handler 抛出的错误转换为统一格式的 Response\n *\n * 覆盖的 FaapiError 子类:\n * - ValidationError → 400/422 + issues\n * - MethodNotAllowedError → 405 + Allow header\n * - PayloadTooLargeError → 413\n * - 其他 FaapiError 子类 → 用 error.statusCode\n * - 未知 Error / 非 Error → 500 INTERNAL_ERROR\n *\n * 与 [formatFailResponse] 共享同一套 fail 函数:业务方在 faapi.config.ts 自定义的\n * `response.fail` 会同时应用到「主动错误响应」和「抛错兜底响应」,确保错误格式一致。\n *\n * @param error handler 抛出的错误\n * @param config 业务方配置(用于读取 response.fail 自定义包装函数,可选)\n */\nexport function formatErrorResponse(\n error: unknown,\n config?: Record<string, unknown> | undefined,\n): Response {\n const failFn = resolveFailFn(config);\n\n if (error instanceof ValidationError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n // ValidationError 需要附加 issues 字段。业务方 failFn 可能返回共享/复用对象\n // (性能优化写法)或冻结对象——就地改写会让 issues 跨请求残留污染或静默失败,\n // 因此浅拷贝后再扩展,不改写业务方返回的原对象\n const bodyObj =\n typeof body === 'object' && body !== null\n ? ({ ...(body as Record<string, unknown>) } as Record<string, unknown>)\n : { error: body };\n const existingError = bodyObj.error as Record<string, unknown> | undefined;\n if (existingError && typeof existingError === 'object') {\n bodyObj.error = { ...existingError, issues: error.issues };\n } else if (typeof body === 'object' && body !== null) {\n // 无 error 包装层:body 本身就是错误对象\n bodyObj.issues = error.issues;\n } else {\n bodyObj.error = { issues: error.issues };\n }\n return jsonOk(bodyObj, error.statusCode);\n }\n\n if (error instanceof MethodNotAllowedError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode, { Allow: error.allowedMethods.join(', ') });\n }\n\n if (error instanceof PayloadTooLargeError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode);\n }\n\n if (error instanceof FaapiError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode);\n }\n\n // 未知错误:500 INTERNAL_ERROR\n const message = error instanceof Error ? error.message : 'An unknown error occurred';\n const body = failFn({ status: 500, code: 'INTERNAL_ERROR', message });\n return jsonOk(body, 500);\n}\n","import type { FaapiContext, ResponseMeta, CookieOptions, FailOptions } from './contextTypes';\nimport { createSseWriter, type SseWriter } from './sse';\nimport { wrapOkResult, formatFailResponse, jsonOk } from '../response/responseFormatter';\n\n/**\n * 解析 Cookie 请求头为 Map\n */\nfunction parseCookies(cookieHeader: string): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n for (const pair of cookieHeader.split(';')) {\n const [name, ...rest] = pair.split('=');\n const trimmed = name?.trim();\n if (trimmed) {\n cookies.set(trimmed, rest.join('=').trim());\n }\n }\n return cookies;\n}\n\n/**\n * 格式化 Set-Cookie 值\n */\nfunction formatSetCookie(name: string, value: string, options?: CookieOptions): string {\n let cookie = `${name}=${value}`;\n if (options?.domain) cookie += `; Domain=${options.domain}`;\n if (options?.path) cookie += `; Path=${options.path}`;\n if (options?.maxAge !== undefined) cookie += `; Max-Age=${options.maxAge}`;\n if (options?.expires) cookie += `; Expires=${options.expires.toUTCString()}`;\n if (options?.httpOnly) cookie += `; HttpOnly`;\n if (options?.secure) cookie += `; Secure`;\n if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;\n return cookie;\n}\n\n/**\n * 从 Request 对象创建 FaapiContext\n * @param request Web Request 对象\n * @param params 动态路由参数\n * @param config 自定义业务配置(来自 faapi.config.ts)\n * @param ip 客户端 IP(由调用方从 IncomingMessage 提取,HTTP/WS 握手均通过 utils/getClientIp)\n *\n * ua 不作为参数传入:User-Agent 是标准 HTTP 请求头,createContext 内部直接从\n * request.headers 读取(与 ip 不同,ip 需要从 IncomingMessage 提取故由调用方传入)。\n */\nexport function createContext(\n request: Request,\n params: Record<string, string>,\n config: Record<string, unknown> = {},\n ip: string = '',\n registries?: FaapiContext['registries'],\n): FaapiContext {\n return createContextFromUrl(request, new URL(request.url), params, config, ip, registries);\n}\n\n/**\n * createContext 的热路径变体:URL 由调用方解析一次后传入\n *\n * createServer 每请求需要 pathname/searchParams,若各自 `new URL(request.url)`\n * 一次请求要重复解析 3~4 次。此变体接收已解析的 URL 对象,searchParams 与\n * 调用方共享(只读)。普通场景用 {@link createContext}。\n */\nexport function createContextFromUrl(\n request: Request,\n url: URL,\n params: Record<string, string>,\n config: Record<string, unknown> = {},\n ip: string = '',\n registries?: FaapiContext['registries'],\n): FaapiContext {\n const meta: ResponseMeta = { headers: {}, setCookies: [] };\n const parsedCookies = parseCookies(request.headers.get('cookie') ?? '');\n const cookiesObj: Record<string, string> = {};\n for (const [key, val] of parsedCookies) {\n cookiesObj[key] = val;\n }\n\n const ctx = {\n request,\n params,\n query: url.searchParams,\n headers: request.headers,\n method: request.method,\n path: url.pathname,\n ip,\n ua: request.headers.get('user-agent') ?? '',\n cookies: cookiesObj,\n config,\n meta,\n\n setStatus(status: number) {\n meta.status = status;\n },\n\n setHeader(key: string, value: string) {\n meta.headers[key] = value;\n },\n\n setETag(value: string) {\n meta.headers['etag'] = value;\n },\n\n redirect(url: string, status = 302): Response {\n return new Response(null, {\n status,\n headers: { Location: url },\n });\n },\n\n json(data: unknown, status?: number): Response {\n return jsonOk(data, status ?? 200);\n },\n\n html(html: string, status?: number): Response {\n const headers: Record<string, string> = { 'Content-Type': 'text/html; charset=utf-8' };\n return new Response(html, {\n status: status ?? 200,\n headers,\n });\n },\n\n getCookie(name: string): string | undefined {\n return parsedCookies.get(name);\n },\n\n setCookie(name: string, value: string, options?: CookieOptions): void {\n meta.setCookies.push(formatSetCookie(name, value, options));\n },\n\n deleteCookie(name: string): void {\n meta.setCookies.push(formatSetCookie(name, '', { maxAge: 0 }));\n },\n\n /**\n * 创建 SSE writer,用于流式推送事件\n *\n * handler 调用此方法后,通过返回的 writer 推送事件,框架自动把 writer.response\n * 作为 HTTP 响应(Content-Type: text/event-stream)。\n *\n * 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。\n */\n sse(): SseWriter {\n const writer = createSseWriter();\n const ctxWithSse = ctx as FaapiContext & {\n __sseResponse?: Response;\n __sseWriter?: SseWriter;\n };\n ctxWithSse.__sseResponse = writer.response;\n ctxWithSse.__sseWriter = writer;\n return writer;\n },\n\n /**\n * 显式包装成功响应(返回 Response,不会被自动包裹再次包装)\n *\n * 实现委托给 [responseFormatter.wrapOkResult](../response/responseFormatter.ts),\n * 与 handler `return data` 走的自动包裹路径共享同一套 ok 函数。\n *\n * 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。\n * handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。\n */\n ok(data: unknown): Response {\n const body = wrapOkResult(data, config);\n // wrapOkResult 对 Response 对象直接透传(不会包裹),ctx.ok 接收到的是 okFn(data) 的对象\n // 业务方不应在 ctx.ok 里传 Response,这里 fallback 调 jsonOk 序列化\n return jsonOk(body, 200);\n },\n\n /**\n * 返回错误响应(对象形式参数,status 和 code 均可省略)\n *\n * 实现委托给 [responseFormatter.formatFailResponse](../response/responseFormatter.ts),\n * 与 formatErrorResponse(handler 抛错兜底)共享同一套 fail 函数,确保错误格式一致。\n *\n * - status 省略时 HTTP 状态码默认 500\n * - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)\n * - status 和 code 独立无关联\n *\n * body 用 config.response.fail(或默认实现)包装。\n */\n fail(options: FailOptions): Response {\n return formatFailResponse(options, config);\n },\n } as FaapiContext & { meta: ResponseMeta; __sseResponse?: Response; __sseWriter?: SseWriter };\n\n // app 级注册表(方案 A 实例化)——框架请求链路的 tool/agent/agentHandle 读取来源\n if (registries) {\n ctx.registries = registries;\n }\n\n // 执行用户自定义的 ctx 扩展钩子(config.extendContext)\n const extend = config?.extendContext;\n if (typeof extend === 'function') {\n extend(ctx);\n }\n\n return ctx;\n}\n\n/**\n * 测试专用:从选项对象创建 FaapiContext,免去手写 `new Request(url)` 的样板代码\n *\n * 与 createContext 的关系:createTestContext 内部构造 Request 后调 createContext,\n * 语义完全一致,仅是测试场景的语法糖——不写无意义的 host、query 用对象形式、headers 直接传对象。\n *\n * 为什么不合并进 createContext:createContext 运行时也从真实 HTTP 请求构造 Request,\n * 保持 `(request: Request)` 签名使运行时与测试同构;createTestContext 是纯测试便捷封装,\n * 不引入运行时分支。\n *\n * body 不在此处理:createContext 本身不读 `request.body`,body 注入由 `invokeHandler`\n * 的第 3 个参数负责。POST/PUT/PATCH 测试时 body 单独传给 invokeHandler,避免在两处传 body 产生混淆。\n *\n * @param options 请求选项(path 必填,其余可选)\n * @returns FaapiContext\n */\nexport function createTestContext(options: CreateTestContextOptions): FaapiContext {\n const {\n method = 'GET',\n path,\n query,\n headers,\n params = {},\n config = {},\n ip = '',\n registries,\n } = options;\n\n // 用 URL 解析 + 拼 query,避免手动拼接字符串的转义问题\n const url = new URL(`http://localhost${path}`);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (Array.isArray(value)) {\n for (const item of value) {\n url.searchParams.append(key, String(item));\n }\n } else {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const request = new Request(url.toString(), {\n method,\n headers: headers as HeadersInit | undefined,\n });\n\n return createContext(request, params, config, ip, registries);\n}\n\n/**\n * createTestContext 的选项\n */\nexport interface CreateTestContextOptions {\n /** app 级注册表(测试 handler 声明 agent/agents 参数时注入用,可选) */\n registries?: FaapiContext['registries'];\n /** 请求方法,默认 'GET' */\n method?: string;\n /** 请求路径,必填,如 '/api/user'(无需写 host) */\n path: string;\n /** 查询参数,对象形式,自动拼接到 URL(值会被 String() 转换;数组生成同名多值参数) */\n query?: Record<string, string | number | boolean | Array<string | number | boolean>>;\n /** 请求头 */\n headers?: Record<string, string>;\n /** 动态路由参数,默认 {} */\n params?: Record<string, string>;\n /** 业务配置(来自 faapi.config.ts),默认 {} */\n config?: Record<string, unknown>;\n /** 客户端 IP,默认 '' */\n ip?: string;\n}\n","export function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') {\n return false;\n }\n\n // 数组不是普通对象\n if (Array.isArray(value)) {\n return false;\n }\n\n // 检查原型链:普通对象的 prototype 是 Object.prototype 或 null(Object.create(null))\n const proto = Object.getPrototypeOf(value);\n return proto === null || proto === Object.prototype;\n}\n","import type { ResponseMeta } from '../runtime/contextTypes';\n\n/**\n * 仅 headers 型 meta 的延迟落头通道\n *\n * Web Response 一旦构造不可变——此前 `ctx.ok()` / `ctx.fail()` 等返回 Response\n * 的场景,即使 meta 只有 headers(helmet 开启后每请求 ~13 个静态头),也要\n * `new Headers()` 拷贝 + `new Response()` 重建一次。改为把 headers 记入\n * WeakMap(按响应对象弱键,响应被 GC 自动清理),由 Node 发送层\n * (sendNodeResponse)最后一次性 `setHeader`——热路径零重建。\n *\n * 中间件若重建 Response(如 compression / etag 的大 body 分支),需用\n * `consumePendingMetaHeaders` 把延迟头并入新 Response 的 headers,避免丢失。\n */\n\nconst pending = new WeakMap<Response, Record<string, string>>();\n\n/**\n * 记录延迟落头(headers-only meta 场景)\n *\n * 同一响应多次记录按 key 合并(后写覆盖,与 mergeMeta 的 headers.set 语义一致)。\n */\nexport function deferMetaHeaders(response: Response, headers: Record<string, string>): void {\n const existing = pending.get(response);\n if (existing) {\n Object.assign(existing, headers);\n return;\n }\n pending.set(response, { ...headers });\n}\n\n/**\n * 取出并清除响应的延迟头(发送层 / 重建方消费)\n */\nexport function consumePendingMetaHeaders(response: Response): Record<string, string> | undefined {\n const headers = pending.get(response);\n if (headers) pending.delete(response);\n return headers;\n}\n\n/** meta 是否仅含 headers(无 status / setCookies,可走延迟通道) */\nexport function isHeadersOnlyMeta(meta: ResponseMeta): boolean {\n return meta.status === undefined && meta.setCookies.length === 0;\n}\n","import { isPlainObject } from '../utils/isPlainObject';\nimport type { ResponseMeta } from '../runtime/contextTypes';\nimport { deferMetaHeaders, isHeadersOnlyMeta } from './pendingMeta';\n\n/**\n * 将 handler 返回值统一转换为 Response\n *\n * 规则:\n * - Response 对象:原样返回\n * - ReadableStream:直接作为 body\n * - Buffer / Uint8Array:作为二进制 body\n * - 普通对象/数组:JSON.stringify,Content-Type: application/json\n * - string:text/plain\n * - number/boolean:text/plain,String(value)\n * - null/undefined:204 No Content\n * - Promise:await 后再处理\n *\n * 如果传入了 meta,会将 ctx.setStatus / ctx.setHeader 的设置合并到 Response 中\n */\nexport async function toResponse(value: unknown, meta?: ResponseMeta): Promise<Response> {\n // Promise:await 后再处理\n if (value instanceof Promise) {\n return toResponse(await value, meta);\n }\n\n // 辅助函数:将 meta headers 和 setCookies 合并到 Headers 对象\n const applyMeta = (headers: Headers): void => {\n if (!meta) return;\n for (const [key, val] of Object.entries(meta.headers)) {\n headers.set(key, val);\n }\n for (const cookie of meta.setCookies ?? []) {\n headers.append('set-cookie', cookie);\n }\n };\n\n // Response 对象:原样返回(但合并 meta headers)\n if (value instanceof Response) {\n if (\n meta &&\n (meta.status !== undefined ||\n Object.keys(meta.headers).length > 0 ||\n meta.setCookies.length > 0)\n ) {\n // 仅 headers 时延迟到发送层落头(避免每请求 Headers 拷贝 + Response 重建,\n // helmet 开启后 meta.headers 恒非空)。status/cookie 需要 Response 级变更,仍重建\n if (isHeadersOnlyMeta(meta)) {\n deferMetaHeaders(value, meta.headers);\n return value;\n }\n const headers = new Headers(value.headers);\n applyMeta(headers);\n return new Response(value.body, {\n status: meta.status ?? value.status,\n headers,\n });\n }\n return value;\n }\n\n // null/undefined:204 No Content\n if (value === null || value === undefined) {\n const status = meta?.status ?? 204;\n const headers = new Headers();\n applyMeta(headers);\n return new Response(null, { status, headers });\n }\n\n // ReadableStream:直接作为 body\n if (typeof ReadableStream !== 'undefined' && value instanceof ReadableStream) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // Buffer / Uint8Array:作为二进制 body\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n if (value instanceof Uint8Array) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // 普通对象/数组:JSON.stringify,Content-Type: application/json\n if (isPlainObject(value) || Array.isArray(value)) {\n const body = JSON.stringify(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return new Response(body, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // string:text/plain\n if (typeof value === 'string') {\n const headers = new Headers({ 'Content-Type': 'text/plain' });\n applyMeta(headers);\n return new Response(value, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // number/boolean:text/plain,String(value)\n if (typeof value === 'number' || typeof value === 'boolean') {\n const headers = new Headers({ 'Content-Type': 'text/plain' });\n applyMeta(headers);\n return new Response(String(value), {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // 其他类型 fallback:JSON.stringify\n const body = JSON.stringify(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return new Response(body, {\n status: meta?.status ?? 200,\n headers,\n });\n}\n","import ts from 'typescript';\n\n/**\n * 参数注入类型\n *\n * Phase 2.3 扩展 `agent` / `agents`:\n * - `agent` —— 默认 agent 元数据(Phase 2.4 实现 `config.defaultAgent` 后注入值)\n * - `agents` —— 所有已注册 agent 的元数据列表(`agentRegistry.listAgents()`)\n */\nexport type InjectionType =\n | 'query'\n | 'body'\n | 'form'\n | 'headers'\n | 'params'\n | 'context'\n | 'cookies'\n | 'ip'\n | 'ua'\n | 'files'\n | 'fields'\n | 'agent'\n | 'agents'\n | 'unknown';\n\n/**\n * 注入项信息\n */\nexport interface InjectionItem {\n name: string; // 参数名\n type: InjectionType; // 注入类型\n hasType: boolean; // 是否有类型标注(运行时类型已擦除,始终为 false)\n}\n\n/**\n * 参数名到注入类型的映射(单一来源,构建期和运行时共用)\n *\n * `form` 与 `body` 互斥:handler 声明其一即可。`form` 适用于\n * `application/x-www-form-urlencoded` 请求体(值均为 string,schema coerce=true);\n * `body` 适用于 JSON 请求体(coerce=false)。两者共享 `resolveInput` 的解析结果。\n *\n * Phase 2.3 添加 `agent` / `agents`:\n * - `agent` → 注入默认 agent 元数据(Phase 2.4 实现,暂返回 `undefined`)\n * - `agents` → 注入所有已注册 agent 元数据列表(`agentRegistry.listAgents()`)\n *\n * Phase 3.x 的 `@faapi/agent` 插件通过注入器机制增强为 `AgentHandle`\n * (含可调用 `run` 函数),覆盖内置的元数据注入。\n */\nexport const PARAM_TYPE_MAP: Record<string, InjectionType> = {\n query: 'query',\n body: 'body',\n form: 'form',\n headers: 'headers',\n params: 'params',\n context: 'context',\n ctx: 'context', // 别名\n cookies: 'cookies',\n ip: 'ip',\n ua: 'ua',\n files: 'files',\n fields: 'fields',\n agent: 'agent', // Phase 2.3\n agents: 'agents', // Phase 2.3\n};\n\n/**\n * 分析结果按函数引用缓存。\n *\n * handler 函数在路由模块加载后引用稳定,参数列表不会变化;用 WeakMap 弱键,\n * dev 热替换后旧模块的函数不再被引用即可随 GC 回收,不需要手动失效。\n */\ntype AnyFn = (...args: unknown[]) => unknown;\nconst injectionCache = new WeakMap<AnyFn, InjectionItem[]>();\n\n/**\n * 分析函数参数,决定需要注入什么内容\n *\n * 使用 TypeScript AST 解析 fn.toString() 的结果,\n * 正确处理解构参数、默认值、rest 参数等情况。\n *\n * 注意:运行时类型信息已被擦除,hasType 始终为 false。\n *\n * 返回的数组是缓存的共享实例,调用方不得就地修改。\n */\nexport function resolveInjection(fn: (...args: unknown[]) => unknown): InjectionItem[] {\n const cached = injectionCache.get(fn);\n if (cached) {\n return cached;\n }\n\n const fnStr = fn.toString();\n const params = extractParamsWithAst(fnStr);\n\n const items = params.map((param) => {\n const type = PARAM_TYPE_MAP[param.name] || 'unknown';\n return {\n name: param.name,\n type,\n hasType: false, // 运行时类型已擦除\n };\n });\n\n injectionCache.set(fn, items);\n return items;\n}\n\n/**\n * 使用 TypeScript AST 从函数字符串中提取参数名\n *\n * 相比正则解析,AST 能正确处理:\n * - 解构参数 `function GET({ page, size })` → page, size\n * - 默认值 `function GET(query = {})` → query\n * - rest 参数 `function GET(...args)` → args\n * - 含逗号的泛型(类型擦除后不存在该问题)\n */\nfunction extractParamsWithAst(fnStr: string): Array<{ name: string }> {\n // 包装成模块,让 TS 能解析\n const sourceFile = ts.createSourceFile(\n '__faapi_injection__.ts',\n fnStr,\n ts.ScriptTarget.Latest,\n true,\n );\n\n const paramNames: string[] = [];\n\n function visit(node: ts.Node): void {\n // 函数声明:function GET(query) {}\n if (ts.isFunctionDeclaration(node) && node.parameters.length > 0) {\n for (const param of node.parameters) {\n extractParamName(param, paramNames);\n }\n return;\n }\n\n // 箭头函数 / 函数表达式:(query) => {} 或 function(query) {}\n if ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && node.parameters.length > 0) {\n for (const param of node.parameters) {\n extractParamName(param, paramNames);\n }\n return;\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n\n return paramNames.map((name) => ({ name }));\n}\n\n/**\n * 从单个参数节点提取参数名\n *\n * 支持的参数形式:\n * - 标识符:query → query\n * - 解构对象:{ page, size } → page, size(每个属性都作为一个注入项)\n * - 解构数组:[a, b] → a, b\n * - rest 参数:...args → args\n * - 默认值:query = {} → query\n */\nfunction extractParamName(param: ts.ParameterDeclaration, names: string[]): void {\n const name = param.name;\n\n // 标识符:query\n if (ts.isIdentifier(name)) {\n names.push(name.text);\n return;\n }\n\n // 解构对象:{ page, size }\n if (ts.isObjectBindingPattern(name)) {\n for (const element of name.elements) {\n if (ts.isBindingElement(element)) {\n const elemName = element.name;\n if (ts.isIdentifier(elemName)) {\n names.push(elemName.text);\n }\n }\n }\n return;\n }\n\n // 解构数组:[a, b]\n if (ts.isArrayBindingPattern(name)) {\n for (const element of name.elements) {\n if (element && ts.isBindingElement(element)) {\n const elemName = element.name;\n if (ts.isIdentifier(elemName)) {\n names.push(elemName.text);\n }\n }\n }\n return;\n }\n}\n","/**\n * URLSearchParams → query 对象\n *\n * 重复 key 聚合为数组(对齐 Express qs / Hono getAll 语义):\n * - `?page=1` → `{ page: '1' }`\n * - `?tag=a&tag=b` → `{ tag: ['a', 'b'] }`\n *\n * 此前 last-wins 静默丢弃前面的值(`?ids=1&ids=2` 得 `'2'`),且 query schema\n * 的 coerce 管线对 array 元素同样生成 preprocess——声明 `ids: number[]` 的字段\n * 此前永远校验失败(解析端从未产出数组),属于校验管线输入端漏洞。\n */\nexport function queryToObject(params: URLSearchParams): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of params) {\n const existing = result[key];\n if (existing === undefined) {\n result[key] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n result[key] = [existing, value];\n }\n }\n return result;\n}\n","import type { ToolMetadata } from '../ast/extractToolMetadata';\nimport type { AgentMetadata, AgentCore } from '../ast/extractAgentMetadata';\nimport type { FaapiContext } from '../runtime/contextTypes';\n\n/**\n * app 级注册表(方案 A:注册表实例化)\n *\n * 每个应用实例(`createAppBase`)持有一套独立的注册表,随 app 创建、随\n * `app.close()` 销毁——多 app 同进程(测试 / 嵌入 / 多租户)互不串台。\n * 此前注册表是模块级全局单例 + hydrate 整体替换语义,后创建的 app 会覆盖\n * 先创建的 app 的清单,任一 app close 会清空全部(详见各模块 .md)。\n *\n * 原四个模块(toolRegistry / agentRegistry / skillRegistry / agentHandle)\n * 保留同名全局函数作为**默认实例的便捷访问器**(供编程式直调 / 单元测试 /\n * 无 app 上下文的场景),内部全部路由到本模块的 `defaultRegistries`。\n * 框架自身链路(hydrate / 请求注入 / `@faapi/agent` 插件 / lifecycle 钩子)\n * 一律走 app 实例,不再读写全局默认实例。\n */\n\n// ─── Tool 注册表 ─────────────────────────────────────────────────────\n\nexport interface ToolRegistry {\n /** 全量替换(tool 清单来自编译期产物,reload 时整体重新生成) */\n hydrate(tools: ToolMetadata[]): void;\n /** 按全名查找(如 `weather.getWeather`) */\n get(name: string): ToolMetadata | undefined;\n /** 所有已注册 tool(副本) */\n list(): ToolMetadata[];\n clear(): void;\n}\n\nexport function createToolRegistry(): ToolRegistry {\n let registry = new Map<string, ToolMetadata>();\n return {\n hydrate(tools) {\n const next = new Map<string, ToolMetadata>();\n for (const tool of tools) {\n next.set(tool.name, tool);\n }\n registry = next;\n },\n get(name) {\n return registry.get(name);\n },\n list() {\n return Array.from(registry.values());\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Agent 注册表 ────────────────────────────────────────────────────\n\n/** agent 包装为 tool 的描述符(reactLoop 据此识别 sub-agent 递归) */\nexport interface AgentToolDescriptor {\n kind: 'agent';\n name: string;\n agentName: string;\n description?: string;\n metadata: AgentCore;\n}\n\nexport interface AgentRegistry {\n /** 全量替换(与 hydrateToolRegistry 同构) */\n hydrate(agents: AgentMetadata[]): void;\n /** LLM 可见元数据(AgentCore) */\n getAgent(name: string): AgentCore | undefined;\n /** 完整元数据(含 filePath / hasRun,供加载 handler.js 执行 run) */\n getAgentEntry(name: string): AgentMetadata | undefined;\n /** 所有已注册 agent 的 LLM 可见元数据(副本) */\n listAgents(): AgentCore[];\n /** 包装为 tool 描述符 */\n asTool(name: string): AgentToolDescriptor | undefined;\n /**\n * 解析 agent 显式声明的 tool 集合。\n * 跨注册表依赖:经由构造时绑定的 tool 注册表查找(同属一套 AppRegistries,\n * 由 createAppBase 在同一启动阶段水合)\n */\n resolveAgentTools(name: string): ToolMetadata[];\n /** 解析 agent 可调用的子 agent 集合 */\n resolveSubAgents(name: string): AgentCore[];\n clear(): void;\n}\n\nexport function createAgentRegistry(tool: ToolRegistry): AgentRegistry {\n let registry = new Map<string, AgentMetadata>();\n\n const getAgent = (name: string): AgentCore | undefined => registry.get(name);\n\n return {\n hydrate(agents) {\n const next = new Map<string, AgentMetadata>();\n for (const agent of agents) {\n next.set(agent.name, agent);\n }\n registry = next;\n },\n getAgent,\n getAgentEntry(name) {\n return registry.get(name);\n },\n listAgents() {\n const merged = new Map<string, AgentCore>();\n for (const agent of registry.values()) merged.set(agent.name, agent);\n return Array.from(merged.values());\n },\n asTool(name) {\n const agent = getAgent(name);\n if (!agent) return undefined;\n return {\n kind: 'agent',\n name: `agent.${agent.name}`,\n agentName: agent.name,\n description: agent.description,\n metadata: agent,\n };\n },\n resolveAgentTools(name) {\n const agent = getAgent(name);\n if (!agent) return [];\n\n const result = new Map<string, ToolMetadata>();\n if (agent.tools) {\n for (const toolName of agent.tools) {\n const resolved = tool.get(toolName);\n if (resolved) result.set(resolved.name, resolved);\n // 未找到的 tool 名静默跳过(tool 可选可用,不强制存在)\n }\n }\n return Array.from(result.values());\n },\n resolveSubAgents(name) {\n const agent = getAgent(name);\n if (!agent || !agent.agents) return [];\n\n const result: AgentCore[] = [];\n for (const subName of agent.agents) {\n const sub = getAgent(subName);\n if (sub) result.push(sub);\n // 未注册的 agent 名跳过(agent 可选可用,不强制存在)\n }\n return result;\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Skill 注册表 ────────────────────────────────────────────────────\n\nexport interface SkillRegistry {\n /** 全量替换(DB change stream 场景也可用 upsert 增量) */\n hydrate(skills: AgentCore[]): void;\n /** 增量注册 / 覆盖(业务方监听 DB 单条变更) */\n upsert(skill: AgentCore): void;\n remove(name: string): void;\n get(name: string): AgentCore | undefined;\n list(): AgentCore[];\n clear(): void;\n}\n\nexport function createSkillRegistry(): SkillRegistry {\n let registry = new Map<string, AgentCore>();\n return {\n hydrate(skills) {\n const next = new Map<string, AgentCore>();\n for (const skill of skills) {\n next.set(skill.name, skill);\n }\n registry = next;\n },\n upsert(skill) {\n registry.set(skill.name, skill);\n },\n remove(name) {\n registry.delete(name);\n },\n get(name) {\n return registry.get(name);\n },\n list() {\n return Array.from(registry.values());\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Agent handle 工厂 ───────────────────────────────────────────────\n\n/** agent handle 工厂函数(由 `@faapi/agent` 插件注册) */\nexport type AgentHandleFactory = (ctx: FaapiContext) => unknown;\n\nexport interface AgentHandleStore {\n /** 注册工厂(null 清理);二次注册覆盖 */\n register(factory: AgentHandleFactory | null): void;\n /** 工厂已注册时返回 AgentHandle 实例,未注册返回 undefined */\n get(ctx: FaapiContext): unknown;\n clear(): void;\n}\n\nexport function createAgentHandleStore(): AgentHandleStore {\n let currentFactory: AgentHandleFactory | null = null;\n return {\n register(factory) {\n currentFactory = factory;\n },\n get(ctx) {\n if (currentFactory === null) return undefined;\n return currentFactory(ctx);\n },\n clear() {\n currentFactory = null;\n },\n };\n}\n\n// ─── App 级集合 ──────────────────────────────────────────────────────\n\n/** 一个 app 实例持有的全套注册表 */\nexport interface AppRegistries {\n tool: ToolRegistry;\n agent: AgentRegistry;\n skill: SkillRegistry;\n agentHandle: AgentHandleStore;\n}\n\n/** 创建一套 app 级注册表(`createAppBase` 每次调用创建独立实例) */\nexport function createAppRegistries(): AppRegistries {\n const tool = createToolRegistry();\n const agent = createAgentRegistry(tool);\n const skill = createSkillRegistry();\n const agentHandle = createAgentHandleStore();\n return { tool, agent, skill, agentHandle };\n}\n\n/**\n * 默认实例:原全局函数的退路(编程式直调 / 单元测试 / 无 app 上下文场景)。\n *\n * 框架自身链路不读写此实例——app 创建自己的 AppRegistries 并在请求链路 /\n * 插件 / lifecycle 钩子中传递。多 app 场景下默认实例无隔离语义(等同旧全局行为)。\n */\nexport const defaultRegistries: AppRegistries = createAppRegistries();\n","import type { AgentCore, AgentMetadata } from '../ast/extractAgentMetadata';\nimport { defaultRegistries } from './registries';\n\n// 类型从 registries 转发(AgentToolDescriptor 的定义源已随实例化迁入 registries)\nexport type { AgentToolDescriptor } from './registries';\n\n/**\n * agent 注册表全局访问器(默认实例便捷入口)\n *\n * 框架自身链路(`createAppBase` 水合 / `@faapi/agent` 插件 deps / `agents`\n * 参数注入)已改为读写 **app 实例级注册表**(见 [registries.md](./registries.md))——\n * agent 注册表实例在构造时绑定同套 tool 注册表实例(`resolveAgentTools` 跨表查找),\n * 多 app 同进程互不串台。\n *\n * 本模块的同名函数保留为**默认实例**的便捷访问器(语义约束不变:不 fallback 到\n * skillRegistry、Core / Entry 双查询入口),供编程式直调 / 单元测试使用。\n *\n * ## 与 skillRegistry 物理隔离\n *\n * agent 负责核心流程,skill 用于拓展——查询函数不 fallback 到 skillRegistry,\n * 实例化后依旧如此(两套注册表实例互不引用)。\n *\n * 详见 [agentRegistry.md](./agentRegistry.md)。\n */\n\n/**\n * 水合默认实例的 agent 注册表(全量替换)\n *\n * 框架路径请使用 app 实例:`ctx.registries.agent.hydrate(agents)`。\n *\n * @param agents 从 `faapi-agents.js` 水合还原的 `AgentMetadata[]`\n */\nexport function hydrateAgentRegistry(agents: AgentMetadata[]): void {\n defaultRegistries.agent.hydrate(agents);\n}\n\n/**\n * 清空默认实例的 agent 注册表\n *\n * app close 清理的是 app 自己的实例,不经过此函数。\n */\nexport function clearAgentRegistry(): void {\n defaultRegistries.agent.clear();\n}\n\n/**\n * 按名查找单个 agent 的 LLM 可见元数据(默认实例)\n *\n * 返回 [AgentCore](../ast/extractAgentMetadata.md)(不含 filePath / hasRun)。\n * 加载 handler.js 执行 `run` 函数请用 [getAgentEntry](#getAgentEntry)。\n *\n * @param name agent 名(如 `researcher`,含 `@agent` 覆盖值)\n */\nexport function getAgent(name: string): AgentCore | undefined {\n return defaultRegistries.agent.getAgent(name);\n}\n\n/**\n * 按名查找单个 agent 的完整元数据(默认实例,含 filePath / hasRun)\n */\nexport function getAgentEntry(name: string): AgentMetadata | undefined {\n return defaultRegistries.agent.getAgentEntry(name);\n}\n\n/**\n * 返回默认实例所有已注册 agent 的 LLM 可见元数据(副本,仅文件型 agent)\n */\nexport function listAgents(): AgentCore[] {\n return defaultRegistries.agent.listAgents();\n}\n\n/**\n * 把 agent 包装为 tool 描述符(默认实例)\n */\nexport function asTool(name: string) {\n return defaultRegistries.agent.asTool(name);\n}\n\n/**\n * 解析 agent 显式声明的 tool 集合(默认实例,跨查默认 tool 注册表)\n */\nexport function resolveAgentTools(name: string) {\n return defaultRegistries.agent.resolveAgentTools(name);\n}\n\n/**\n * 解析 agent 可调用的子 agent 集合(默认实例,按声明顺序)\n */\nexport function resolveSubAgents(name: string): AgentCore[] {\n return defaultRegistries.agent.resolveSubAgents(name);\n}\n","import type { FaapiContext } from '../runtime/contextTypes';\n\n/**\n * agent handle 工厂注册表(单例)\n *\n * 让 `@faapi/agent` 插件在启动时注册「请求级 agent handle 工厂」,\n * [injectParams](./injectParams.md) 在 `agent` 参数注入时调工厂拿到 `AgentHandle` 实例。\n *\n * 解耦设计:faapi 核心不依赖 `@faapi/agent`——核心只提供注册 / 查询点,\n * 工厂返回 `unknown`,具体类型由 `@faapi/agent` 的 `AgentHandle` 接口定义。\n *\n * 详见 [agentHandle.md](./agentHandle.md)。\n */\n\n/** agent handle 工厂函数(由 `@faapi/agent` 插件注册) */\nexport type AgentHandleFactory = (ctx: FaapiContext) => unknown;\n\nimport { defaultRegistries } from './registries';\n\n/**\n * agent handle 工厂全局访问器(默认实例便捷入口)\n *\n * 框架路径已改为 app 实例:`@faapi/agent` 插件在 setup 时经\n * `ctx.registries.agentHandle.register(factory)` 注册到**当前 app 的实例**,\n * 请求注入经 `ctx.registries.agentHandle.get(ctx)` 读取——多 app 同进程互不覆盖。\n * 此前模块级单值会让第二个 app 的插件注册覆盖第一个。\n *\n * 本模块的同名函数保留为默认实例便捷访问器(向后兼容)。\n *\n * 详见 [agentHandle.md](./agentHandle.md) 与 [registries.md](./registries.md)。\n */\n\n/**\n * 注册默认实例的 agent handle 工厂\n *\n * 框架路径请使用 `ctx.registries.agentHandle.register(factory)`(PluginContext 提供)。\n * 传入 `null` 等效于清理。\n */\nexport function registerAgentHandleFactory(factory: AgentHandleFactory | null): void {\n defaultRegistries.agentHandle.register(factory);\n}\n\n/**\n * 从默认实例获取 agent handle(工厂未注册时返回 undefined)\n */\nexport function getAgentHandle(ctx: FaapiContext): unknown {\n return defaultRegistries.agentHandle.get(ctx);\n}\n\n/**\n * 清空默认实例的工厂注册\n */\nexport function clearAgentHandleFactory(): void {\n defaultRegistries.agentHandle.clear();\n}\n","import type { FaapiContext } from '../runtime/contextTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { resolveInjection, type InjectionType } from './resolveInjection';\nimport { queryToObject } from '../utils/queryToObject';\nimport type { MultipartResult } from '../utils/parseMultipart';\nimport { listAgents } from './agentRegistry';\nimport { getAgentHandle } from './agentHandle';\n\n/**\n * 根据注入类型获取对应的值(内置)\n *\n * Phase 2.3 扩展 `agent` / `agents`:\n * - `agents` → `listAgents()`(所有已注册 agent 元数据列表)\n * - `agent` → `getAgentHandle(ctx)`(Phase 3.5:调 `@faapi/agent` 插件注册的工厂)\n *\n * Phase 3.5 的 `@faapi/agent` 插件通过 [agentHandle](./agentHandle.md) 工厂注册机制\n * 提供 `AgentHandle`(含可调用 `run` / `stream`)。未注册工厂时返回 `undefined`。\n */\nfunction getBuiltinInjectionValue(type: InjectionType, ctx: FaapiContext, body?: unknown): unknown {\n switch (type) {\n case 'query':\n return queryToObject(ctx.query);\n case 'params':\n return ctx.params;\n case 'headers':\n return ctx.headers;\n case 'context':\n return ctx;\n case 'cookies':\n return ctx.cookies;\n case 'ip':\n return ctx.ip;\n case 'ua':\n return ctx.ua;\n case 'body':\n return body;\n // form 与 body 共享解析结果(resolveInput 已按 Content-Type 解析 form-urlencoded)\n // 差异仅在 schema 校验(form coerce=true,由 collectRouteSchemaSources 标记)\n case 'form':\n return body;\n case 'files':\n if (body && typeof body === 'object' && 'files' in body) {\n return (body as MultipartResult).files;\n }\n return [];\n case 'fields':\n if (body && typeof body === 'object' && 'fields' in body) {\n return (body as MultipartResult).fields;\n }\n return {};\n // Phase 2.3:注入所有已注册 agent 元数据列表\n // 方案 A:优先读 app 实例注册表,无实例(编程式直调 ctx)回退默认全局实例\n case 'agents':\n return ctx.registries ? ctx.registries.agent.listAgents() : listAgents();\n // Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle\n case 'agent':\n return ctx.registries ? ctx.registries.agentHandle.get(ctx) : getAgentHandle(ctx);\n default:\n return undefined;\n }\n}\n\n/**\n * 根据注入信息,准备参数值并调用 handler(异步版本)\n *\n * 支持 async handler。内置注入优先于注入器(避免 query/body 等被覆盖)。\n * 非内置参数从注入器注册表按参数名查找,按需执行。\n */\nexport async function injectParamsAsync(\n handler: (...args: unknown[]) => unknown,\n ctx: FaapiContext,\n body?: unknown,\n injectors?: InjectorMap,\n): Promise<unknown> {\n const injections = resolveInjection(handler);\n\n if (injections.length === 0) {\n return await handler();\n }\n\n const args = await Promise.all(\n injections.map(async (injection) => {\n // 内置注入优先\n if (injection.type !== 'unknown') {\n return getBuiltinInjectionValue(injection.type, ctx, body);\n }\n // 注入器按参数名匹配\n if (injectors && injection.name in injectors) {\n // in 检查已证明存在\n return await injectors[injection.name]!(ctx);\n }\n return undefined;\n }),\n );\n\n return await handler(...args);\n}\n","import type { FaapiContext } from './contextTypes';\nimport type { ResponseMeta } from './contextTypes';\nimport type { SseWriter } from './sse';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { toResponse } from '../response/toResponse';\nimport { deferMetaHeaders, isHeadersOnlyMeta } from '../response/pendingMeta';\nimport { wrapOkResult } from '../response/responseFormatter';\nimport { injectParamsAsync } from '../injection/injectParams';\n\n/**\n * 自动包裹 handler 返回值(统一响应包装)\n *\n * 实现已移至 [response/responseFormatter.ts](../response/responseFormatter.ts) 的\n * wrapOkResult,与 ctx.ok() / formatFailResponse / formatErrorResponse 共享同一套\n * ok/fail 函数,确保响应格式在所有路径一致。\n *\n * 规则:\n * - Response 对象:不包裹(ctx.ok/ctx.fail/ctx.json 等返回的 Response 原样透传)\n * - 其他值(含 null/undefined):用 config.response.ok(或默认 (data) => ({ data })) 包裹\n *\n * ctx.ok() 返回的是 Response 对象,所以 handler 用 `return ctx.ok(data)` 或 `return data`\n * 最终响应一致(都是 ok(data) 的结果),不会双重包裹。\n *\n * 注意:null/undefined 也会被包裹为 { data: null } / { data: undefined }(JSON.stringify\n * 后者得到 \"{}\"),不再返回 204 No Content。如需 204,显式返回 Response 对象。\n */\nfunction wrapResult(result: unknown, ctx: FaapiContext): unknown {\n return wrapOkResult(result, ctx.config);\n}\n\n/**\n * 将 ctx.meta(setStatus/setHeader/setCookie 设置的响应元数据)合并到 Response\n *\n * 用于中间件返回 Response 时,确保用户通过 ctx.setStatus/setHeader 设置的\n * 状态码、响应头、cookie 能正确附加到中间件返回的 Response 上。\n */\nexport function mergeMeta(response: Response, meta: ResponseMeta): Response {\n const hasMeta =\n meta.status !== undefined || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;\n if (!hasMeta) return response;\n\n // 仅 headers 时延迟到发送层落头(零 Response 重建,见 pendingMeta.ts)\n if (isHeadersOnlyMeta(meta)) {\n deferMetaHeaders(response, meta.headers);\n return response;\n }\n\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(meta.headers)) {\n headers.set(key, value);\n }\n for (const cookie of meta.setCookies) {\n headers.append('set-cookie', cookie);\n }\n return new Response(response.body, {\n status: meta.status ?? response.status,\n headers,\n });\n}\n\n/**\n * 洋葱模型调度:将中间件链包装成 next 函数\n *\n * 每个中间件的 `await next()` 会执行下一个中间件并返回其 Response;\n * 最内层的 next 执行 handler(含参数注入)。\n *\n * 中间件返回 Response 的语义:\n * - 在 await next() 之前返回:拦截请求(不执行后续中间件和 handler)\n * - 在 await next() 之后返回:替换内层返回的响应\n * - 在 catch 块中返回:作为错误响应\n *\n * 中间件返回 void/undefined:使用 await next() 返回的内层响应。\n * 中间件不调用 next() 也不返回 Response:抛错(语义模糊,禁止使用)。\n *\n * 中间件返回 Response 时会合并 ctx.meta(setStatus/setHeader/setCookie 的设置),\n * 确保 ctx 便捷方法在中间件拦截场景下也生效。\n */\nexport async function compose(\n middlewares: FaapiMiddleware[],\n ctx: FaapiContext,\n finalHandler: () => Promise<Response>,\n): Promise<Response> {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n let index = -1;\n\n async function dispatch(i: number): Promise<Response> {\n if (i <= index) {\n throw new Error('next() called multiple times');\n }\n index = i;\n\n // 所有中间件执行完,运行 handler\n if (i >= middlewares.length) {\n return await finalHandler();\n }\n\n // i >= middlewares.length 已早退,此处索引必然存在\n const mw = middlewares[i]!;\n // next() 返回内层 Response,中间件可选择使用或替换\n let innerResponse: Response | undefined;\n const next = async (): Promise<Response> => {\n innerResponse = await dispatch(i + 1);\n return innerResponse;\n };\n\n const result = await mw(ctx, next);\n\n // 中间件返回 Response:使用它(拦截或替换),并合并 ctx.meta\n if (result instanceof Response) {\n return mergeMeta(result, meta);\n }\n // 中间件返回 void:使用 next() 返回的内层响应\n if (innerResponse !== undefined) {\n return innerResponse;\n }\n // 中间件既没返回 Response 也没调用 next():语义模糊,禁止\n throw new Error('中间件必须 await next() 或返回 Response');\n }\n\n return await dispatch(0);\n}\n\n/**\n * 调用路由 handler 并将返回值转为 Response\n *\n * 流程(洋葱模型):\n * 1. 中间件按洋葱模型执行:mw1.before → mw2.before → ... → handler → ... → mw2.after → mw1.after\n * 2. 中间件不调用 next() 即拦截请求(必须返回 Response)\n * 3. 中间件可用 try/catch 捕获内层错误\n * 4. 最内层执行注入器(按需)→ handler\n *\n * 注入器与中间件解耦:\n * - 注入器按 handler 参数名匹配,只执行需要的\n * - 注入器可读取中间件塞进 ctx 的值\n */\nexport async function invokeHandler(\n handler: (...args: unknown[]) => unknown,\n ctx: FaapiContext,\n body?: unknown,\n middlewares?: FaapiMiddleware[],\n injectors?: InjectorMap,\n): Promise<Response> {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n\n /**\n * 若 handler 调用了 ctx.sse(),优先使用 SSE Response\n * (handler 返回值被忽略,SSE 流由 writer 控制)\n *\n * 同时执行自动 close 兜底:若 handler 返回时 writer 仍未关闭,自动调用 close(),\n * 避免连接泄漏。handler 异步循环未退出、忘记 close、或抛错的场景由这里兜底。\n */\n const pickSseAndAutoClose = (): Response | null => {\n const sseWriter = (ctx as FaapiContext & { __sseWriter?: SseWriter }).__sseWriter;\n if (!sseWriter) return null;\n // 兜底:handler 未显式 close 时自动关闭\n if (!sseWriter.closed && !sseWriter.aborted) {\n sseWriter.close();\n }\n return mergeMeta(sseWriter.response, meta);\n };\n\n // 抛错时也保证 writer 被关闭(避免流泄漏)\n const autoCloseSseOnError = (): void => {\n const sseWriter = (ctx as FaapiContext & { __sseWriter?: SseWriter }).__sseWriter;\n if (sseWriter && !sseWriter.closed && !sseWriter.aborted) {\n sseWriter.close();\n }\n };\n\n // 无中间件:直接执行 handler\n if (!middlewares || middlewares.length === 0) {\n try {\n const result = await injectParamsAsync(handler, ctx, body, injectors);\n const sseResponse = pickSseAndAutoClose();\n if (sseResponse) return sseResponse;\n return toResponse(wrapResult(result, ctx), meta);\n } catch (err) {\n // handler 抛错时关闭未完成的 SSE 流,避免泄漏\n autoCloseSseOnError();\n throw err;\n }\n }\n\n // 有中间件:洋葱模型调度\n const finalHandler = async (): Promise<Response> => {\n try {\n const result = await injectParamsAsync(handler, ctx, body, injectors);\n const sseResponse = pickSseAndAutoClose();\n if (sseResponse) return sseResponse;\n return toResponse(wrapResult(result, ctx), meta);\n } catch (err) {\n // handler 抛错时关闭未完成的 SSE 流,避免泄漏\n autoCloseSseOnError();\n throw err;\n }\n };\n\n return await compose(middlewares, ctx, finalHandler);\n}\n","import type { Server } from 'node:http';\nimport path from 'node:path';\nimport os from 'node:os';\nimport fs from 'node:fs/promises';\nimport { scanRoutes } from './router/scanRoutes';\nimport { sortRoutes } from './router/sortRoutes';\nimport { generateSchemaFiles } from './cli/generateSchemaFiles';\nimport { invalidateSchemaCache } from './validator/validateInput';\nimport { createServer } from './server/createServer';\nimport type { RouteManifest, WsRouteManifest } from './router/routeTypes';\nimport type { FaapiMiddleware } from './middleware/middlewareTypes';\nimport type { InjectorMap } from './middleware/injectorTypes';\nimport type { CorsOptions } from './middleware/cors';\nimport type { HelmetOptions } from './middleware/helmet';\nimport type { LoggerOptions } from './middleware/logger';\nimport type { FaapiContext } from './runtime/contextTypes';\n\n/**\n * createTestServer 入参\n *\n * 业务方一行代码启动带 schema 校验的 E2E 测试服务器。\n * 详见 src/testServer.md。\n */\nexport interface TestServerOptions {\n /** 项目根目录(路由源码所在,必填) */\n rootDir: string;\n // 路由扫描 glob,相对 rootDir;默认 ['src/api/**/*.ts']\n patterns?: string[];\n /**\n * schema 产物输出目录(绝对路径或相对 rootDir)。\n * 不传时自动 mkdtemp 生成临时目录,close() 时清理。\n * 传值时 close() 仍会清理该目录。\n */\n dist?: string;\n /** CORS 中间件配置,默认 false(禁用,避免污染断言) */\n cors?: CorsOptions | boolean;\n /** 安全头配置,默认 false */\n helmet?: HelmetOptions | boolean;\n /** 请求日志配置,默认 false(避免污染测试输出) */\n logger?: LoggerOptions | boolean;\n /** 全局中间件(外层洋葱) */\n middlewares?: FaapiMiddleware[];\n /** 全局注入器 */\n injectors?: InjectorMap;\n /** 请求错误钩子(在错误响应生成后调用,用于副作用) */\n onError?: (error: unknown, ctx: FaapiContext) => Promise<void> | void;\n /** 业务配置(注入到 ctx.config) */\n config?: Record<string, unknown>;\n /** 请求体大小限制(字节),默认 10MB */\n bodyLimit?: number;\n}\n\n/**\n * createTestServer 返回值\n *\n * 业务方通过 baseUrl 发 fetch 请求,close() 一行完成 teardown。\n */\nexport interface TestServer {\n /** Node.js HTTP Server 实例(已 listen) */\n server: Server;\n /** 形如 http://localhost:<随机端口> */\n baseUrl: string;\n /** 排序后的路由清单 */\n routes: RouteManifest;\n /** WebSocket 路由清单 */\n wsRoutes: WsRouteManifest;\n /** schema 临时目录绝对路径(业务方调试时可查看生成的 zod.js) */\n schemaDist: string;\n /**\n * 关闭 server + 清理 schema 目录 + 清空 schema 模块缓存\n *\n * 内部顺序:\n * 1. server.closeAllConnections?.()(Node 18+,强制断开 WS / 长连接)\n * 2. server.close()\n * 3. fs.rm(schemaDist, { recursive, force })\n * 4. invalidateSchemaCache()\n *\n * 幂等:重复调用不会重复清理。\n */\n close(): Promise<void>;\n}\n\n/** 默认路由扫描 glob */\nconst DEFAULT_PATTERNS = ['src/api/**/*.ts'];\n\n/** 默认请求体大小限制:10MB */\nconst DEFAULT_BODY_LIMIT = 10 * 1024 * 1024;\n\n/**\n * 一键启动带 schema 校验的 E2E 测试服务器\n *\n * 内部流程:\n * 1. scanRoutes 扫描路由\n * 2. sortRoutes 排序\n * 3. mkdtemp 创建临时 schema 目录(或用传入的 dist)\n * 4. generateSchemaFiles 生成 zod.js\n * 5. createServer 创建 server(默认禁用 CORS/Helmet/Logger,避免污染断言)\n * 6. server.listen(0) 随机端口\n * 7. 返回 TestServer\n *\n * 详见 src/testServer.md。\n *\n * @param options rootDir 必填,其余可选\n * @returns TestServer 实例\n */\nexport async function createTestServer(options: TestServerOptions): Promise<TestServer> {\n const {\n rootDir,\n patterns = DEFAULT_PATTERNS,\n dist,\n cors = false,\n helmet = false,\n logger = false,\n middlewares,\n injectors,\n onError,\n config,\n bodyLimit = DEFAULT_BODY_LIMIT,\n } = options;\n\n // 1. 扫描 + 排序路由\n const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);\n const sorted = sortRoutes(routes);\n\n // 2. schema 临时目录(未传 dist 时自动 mkdtemp)\n const schemaDist = dist\n ? path.isAbsolute(dist)\n ? dist\n : path.resolve(rootDir, dist)\n : await fs.mkdtemp(path.join(os.tmpdir(), 'faapi-test-schema-'));\n\n // 3. 生成 zod.js\n await generateSchemaFiles(sorted, rootDir, schemaDist);\n\n // 4. 创建 server(默认禁用 CORS/Helmet/Logger,避免污染断言)\n const { server } = createServer({\n routes: sorted,\n rootDir,\n dist: schemaDist,\n cors,\n helmet,\n logger,\n middlewares,\n injectors,\n onError,\n config,\n wsRoutes,\n bodyLimit,\n });\n\n // 5. listen(0) 随机端口\n const baseUrl = await listenOnRandomPort(server);\n\n // 6. 关闭状态标记(幂等保护)\n let closed = false;\n\n const testServer: TestServer = {\n server,\n baseUrl,\n routes: sorted,\n wsRoutes,\n schemaDist,\n\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n\n // 强制断开 WS / 长连接(Node 18+)\n const s = server as Server & {\n closeAllConnections?: () => void;\n closeIdleConnections?: () => void;\n };\n s.closeAllConnections?.();\n s.closeIdleConnections?.();\n\n // 关闭 HTTP server\n await new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n\n // 清理 schema 临时目录(失败不阻塞 close 流程;force: true 已尽可能容错)\n await fs.rm(schemaDist, { recursive: true, force: true }).catch(() => {\n /* 清理失败不阻塞:closed 标记已设,目录可能在 OS 临时目录被外部清理 */\n });\n\n // 清空 schema 模块缓存,避免串扰后续测试\n invalidateSchemaCache();\n },\n };\n\n return testServer;\n}\n\n/**\n * server.listen(0) 取随机端口,返回 http://localhost:<port>\n *\n * 抽出来便于失败时抛清晰错误。\n */\nfunction listenOnRandomPort(server: Server): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n server.listen(0, () => {\n const addr = server.address();\n if (typeof addr === 'object' && addr !== null) {\n resolve(`http://localhost:${addr.port}`);\n } else {\n reject(new Error('Failed to get server address'));\n }\n });\n server.on('error', (err) => {\n reject(err);\n });\n });\n}\n","import fg from 'fast-glob';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport type { RouteManifest, WsRouteManifest } from './routeTypes';\nimport { HTTP_METHODS } from './constants';\nimport { filePathToUrlPath, extractParamNames, isCatchAllSegment } from './parseRouteFile';\nimport { loadMergedMiddlewares } from '../middleware/loadMiddlewares';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { toProdFilePath } from '../utils/prodPaths';\n\n/**\n * 匹配源码中导出的 HTTP 方法或 WS 函数\n *\n * 支持:\n * - `export function GET() {}`\n * - `export async function POST() {}`\n * - `export const GET = () => {}` / `export const GET = async () => {}`\n * - `export function WS() {}`\n *\n * 不通过运行时 import 提取方法名,避免启动时全量加载 handler 模块(Vite 风格:\n * 路由发现与 handler 加载解耦,handler.js 按需编译/导入)。\n */\nconst HTTP_OR_WS_EXPORT_RE = new RegExp(\n String.raw`export\\s+(?:async\\s+)?(?:function\\s+|const\\s+)(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|WS)\\b`,\n 'g',\n);\n\n/**\n * 从源码内容中提取所有 HTTP 方法 + WS 导出名(去重)\n */\nfunction extractExportsFromSource(source: string): Set<string> {\n const names = new Set<string>();\n let match: RegExpExecArray | null;\n HTTP_OR_WS_EXPORT_RE.lastIndex = 0;\n while ((match = HTTP_OR_WS_EXPORT_RE.exec(source)) !== null) {\n names.add(match[1]!);\n }\n return names;\n}\n\n/**\n * 从路由文件所在目录向上逐级查找 middlewares.ts(源码),\n * 返回从根到路由目录的中间件文件**绝对路径列表**(不加载模块)。\n *\n * 设计意图:scanRoutes 不再启动时全量 import middlewares.js,仅收集路径;\n * 实际的中间件加载延后到 hydrateRoutes / 请求阶段(与 prod 的 hydrateRoutes 一致)。\n * 这样 dev 启动时 zero import,启动速度接近 Vite。\n *\n * 路径选择规则(与 generateRoutes.extractMiddlewarePaths 一致):\n * - dist 传入:返回**产物** middlewares.js 绝对路径(打平 src/ 前缀,存在性检查源码 .ts)\n * - dist 不传:返回**源码** middlewares.ts/.js 绝对路径(兼容无 dist 的旧调用方,如 testServer)\n *\n * @param routeFilePath 源码相对路径(如 src/api/hello/handler.ts)\n * @param rootDir 项目根目录\n * @param dist 产物目录(dist 或 .faapi),不传则返回源码路径\n * @returns 中间件文件绝对路径列表(根在前,路由目录在后);空数组表示无中间件\n */\nfunction collectMiddlewarePaths(routeFilePath: string, rootDir: string, dist?: string): string[] {\n const routeDir = path.dirname(routeFilePath);\n const resolvedRoot = path.resolve(rootDir);\n\n const paths: string[] = [];\n let currentDir = path.resolve(rootDir, routeDir);\n while (true) {\n if (dist) {\n // 产物模式:检查源码 middlewares.ts 是否存在,返回产物 middlewares.js 绝对路径\n const mwTsPath = path.join(currentDir, 'middlewares.ts');\n const mwJsPath = path.join(currentDir, 'middlewares.js');\n const absTsPath = path.resolve(rootDir, mwTsPath);\n const absJsPath = path.resolve(rootDir, mwJsPath);\n const absMwPath = fs.existsSync(absTsPath)\n ? absTsPath\n : fs.existsSync(absJsPath)\n ? absJsPath\n : null;\n if (absMwPath) {\n // 转为产物形式绝对路径(打平 src/ 前缀,加 dist 前缀)\n const relMwPath = path.relative(rootDir, absMwPath);\n const prodAbsPath = path.resolve(rootDir, toProdFilePath(relMwPath, dist));\n paths.push(prodAbsPath);\n }\n } else {\n // 源码模式(testServer / 单元测试用):直接返回源码 middlewares.ts/.js 绝对路径\n for (const ext of ['.ts', '.js']) {\n const mwPath = path.join(currentDir, `middlewares${ext}`);\n const absMwPath = path.resolve(rootDir, mwPath);\n if (fs.existsSync(absMwPath)) {\n paths.push(absMwPath);\n break;\n }\n }\n }\n if (currentDir === resolvedRoot) break;\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) break;\n currentDir = parentDir;\n }\n\n // paths 是从路由目录向上收集的,需反转为根在前\n paths.reverse();\n return paths;\n}\n\n/**\n * 按路径列表加载并合并中间件(根在前,路由目录在后)\n *\n * 已移至 `middleware/loadMiddlewares.ts` 的 `loadMergedMiddlewares`,\n * scanRoutes 不再预加载中间件(dist 模式只收集路径,请求阶段按需加载)。\n */\n\n/**\n * 扫描 api 目录,同时生成 HTTP 路由清单和 WebSocket 路由清单\n *\n * **Vite 风格**:启动时只读源码 + 正则提取方法名,不 import handler.js / middlewares.js。\n * 中间件改为\"收集路径\",加载延后到 hydrateRoutes / 请求阶段。\n *\n * 路由文件格式:handler.ts,导出 HTTP 方法名作为 handler,也可导出 `WS` 函数声明 WebSocket 路由\n * ```ts\n * // api/user/handler.ts\n * export function GET() { return { list: [] } }\n * export function POST(body: any) { return { created: true } }\n *\n * // WebSocket 路由:导出 WS 函数\n * export function WS(ctx: WsContext) {\n * return { onMessage(ws, msg) { ws.send(`echo: ${msg}`) } };\n * }\n * ```\n * 一个 handler.ts 可同时导出 HTTP 方法(GET/POST...)和 WS 函数,\n * 分别生成 HTTP RouteRecord 和 WsRouteRecord。\n *\n * **中间件加载策略**:\n * - dev/prod(dist 传入):scanRoutes 仅收集 middlewarePaths,不加载模块;\n * 由 hydrateRoutes(prod)或 dev 启动流程加载\n * - 无 dist 模式(testServer / 单元测试):scanRoutes 直接加载中间件并塞入 route.middlewares\n * (保持向后兼容,避免破坏现有测试)\n *\n * @param rootDir 项目根目录\n * @param patterns glob patterns(源码 .ts 路径,如 src/api 下所有 .ts)\n * @param dist 产物目录(dist 或 .faapi)。传入时 scanRoutes 不 import 任何模块;\n * 不传时 import 源码 .ts(旧模式,需要 tsx,仅 testServer/单测使用)。\n */\nexport async function scanRoutes(\n rootDir: string,\n patterns: string[],\n dist?: string,\n): Promise<{ routes: RouteManifest; wsRoutes: WsRouteManifest }> {\n const files = await fg(patterns, {\n cwd: rootDir,\n onlyFiles: true,\n absolute: false,\n });\n\n const routes: RouteManifest = [];\n const wsRoutes: WsRouteManifest = [];\n\n for (const file of files) {\n const normalizedFile = file.replace(/\\\\/g, '/');\n const fileName = normalizedFile.split('/').pop()!;\n\n // 处理 handler.{ts,js} — API 路由 + WebSocket 路由\n // dev/build 模式扫源码 .ts(dist 传入),start 模式扫产物 .js(由 hydrateRoutes 处理)\n if (fileName === 'handler.ts' || fileName === 'handler.js') {\n const absPath = path.resolve(rootDir, normalizedFile);\n const urlPath = filePathToUrlPath(normalizedFile);\n const paramNames = extractParamNames(urlPath);\n const isDynamic = paramNames.length > 0;\n const isCatchAll = normalizedFile.split('/').some(isCatchAllSegment);\n\n // 中间件:dist 模式只收集路径(按需加载),无 dist 模式直接加载\n let middlewarePaths: string[] | undefined;\n let middlewareBundle: { middlewares: FaapiMiddleware[]; injectors: InjectorMap } | undefined;\n if (dist) {\n // dev/prod 模式:仅收集路径,请求阶段按需加载(Vite 风格)\n middlewarePaths = collectMiddlewarePaths(normalizedFile, rootDir, dist);\n } else {\n // 无 dist 模式(testServer/单测):直接加载源码中间件\n const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);\n middlewareBundle = await loadMergedMiddlewares(mwPaths);\n }\n\n // 提取导出名(HTTP 方法 + WS):读源码 + 正则匹配,不 import 模块\n const source = await fs.promises.readFile(absPath, 'utf8').catch(() => '');\n const exportNames = extractExportsFromSource(source);\n const methods = HTTP_METHODS.filter((m) => exportNames.has(m));\n\n for (const method of methods) {\n routes.push({\n method,\n urlPath,\n filePath: normalizedFile,\n paramNames,\n isDynamic,\n isCatchAll: isCatchAll || undefined,\n middlewarePaths,\n middlewares: middlewareBundle?.middlewares,\n injectors: middlewareBundle?.injectors,\n });\n }\n\n // WS 导出\n if (exportNames.has('WS')) {\n wsRoutes.push({\n urlPath,\n filePath: normalizedFile,\n paramNames,\n isDynamic,\n isCatchAll: isCatchAll || undefined,\n middlewarePaths,\n middlewares: middlewareBundle?.middlewares,\n injectors: middlewareBundle?.injectors,\n });\n }\n continue;\n }\n }\n\n return { routes, wsRoutes };\n}\n","export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const;\n\nexport type HttpMethod = (typeof HTTP_METHODS)[number];\n\nconst HTTP_METHOD_SET: ReadonlySet<string> = new Set(HTTP_METHODS);\n\nexport function isHttpMethod(value: string): value is HttpMethod {\n return HTTP_METHOD_SET.has(value);\n}\n","/**\n * 标准化路径:\n * - 将反斜杠替换为正斜杠\n * - 去除重复的斜杠\n * - 去除尾部斜杠\n * - 确保以 / 开头(如果非空)\n */\nexport function normalizePath(path: string): string {\n if (!path) return '';\n\n // 将反斜杠替换为正斜杠\n let result = path.replace(/\\\\/g, '/');\n\n // 去除重复的斜杠\n result = result.replace(/\\/+/g, '/');\n\n // 去除尾部斜杠\n result = result.replace(/\\/+$/, '');\n\n // 确保以 / 开头(如果非空)\n if (result && !result.startsWith('/')) {\n result = '/' + result;\n }\n\n return result;\n}\n","import { normalizePath } from '../utils/normalizePath';\n\n/**\n * 将 [name] 格式的目录名转换为 :name 格式\n * '[id]' -> ':id'\n * '[...slug]' -> ':...slug'\n * 'login' -> 'login'\n */\nexport function dynamicSegmentToParam(segment: string): string {\n const match = segment.match(/^\\[(.+)\\]$/);\n if (match) {\n return ':' + match[1];\n }\n return segment;\n}\n\n/**\n * 从 URL 路径中提取动态参数名\n * '/user/:id' -> ['id']\n * '/auth/login' -> []\n * '/user/:id/posts/:postId' -> ['id', 'postId']\n * '/shop/:...slug' -> ['slug']\n */\nexport function extractParamNames(urlPath: string): string[] {\n const params: string[] = [];\n const segments = urlPath.split('/');\n for (const segment of segments) {\n if (segment.startsWith(':...')) {\n // catch-all 参数::...slug -> slug\n params.push(segment.slice(4));\n } else if (segment.startsWith(':')) {\n params.push(segment.slice(1));\n }\n }\n return params;\n}\n\n/**\n * 判断路径段是否为 catch-all 路由\n * '[...slug]' -> true\n * '[id]' -> false\n */\nexport function isCatchAllSegment(segment: string): boolean {\n return /^\\[\\.\\.\\..+\\]$/.test(segment);\n}\n\n/**\n * 判断路径段是否为路由分组\n * '(marketing)' -> true\n * 'login' -> false\n */\nexport function isRouteGroup(segment: string): boolean {\n return /^\\(.+\\)$/.test(segment);\n}\n\n/**\n * 将路由文件路径转换为 URL 路径\n * 输入: 相对于项目根目录的文件路径,如 'src/api/auth/login/handler.ts'\n * 输出: URL 路径,如 '/api/auth/login'\n *\n * 规则:\n * - 去掉 src/ 前缀(路由源码目录写死为 src)\n * - 去掉文件名(handler.ts 等)\n * - 将 [id] 转换为 :id\n * - 忽略路由分组 (groupName) — 不影响 URL\n * - 标准化路径\n *\n * 说明:路由源码目录写死为 src/api/,src/ 前缀剥离后 api/ 前缀保留在 URL 中,\n * 因此 URL 始终带 /api 前缀。\n */\nexport function filePathToUrlPath(filePath: string): string {\n // 去掉 src/ 前缀(路由源码目录写死为 src)\n const withoutPrefix = filePath.startsWith('src/') ? filePath.slice(4) : filePath;\n\n // 去掉文件名(最后一个路径段)\n const lastSlashIndex = withoutPrefix.lastIndexOf('/');\n const dirPath = lastSlashIndex === -1 ? '' : withoutPrefix.slice(0, lastSlashIndex);\n\n if (!dirPath) {\n return '';\n }\n\n // 将每个路径段中的 [name] 转换为 :name,忽略路由分组 (name)\n const segments = dirPath\n .split('/')\n .filter((s) => !isRouteGroup(s)) // 过滤掉路由分组\n .map(dynamicSegmentToParam);\n\n // 拼接并标准化路径\n return normalizePath(segments.join('/'));\n}\n","import { pathToFileURL } from 'node:url';\n\n/**\n * 当前模块加载时间戳(watch 模式下用于绕过 ESM 缓存)\n *\n * 由 createDevApp.reloadRoutes 调用 setLoadTimestamp 设置。\n * ES 模块单例保证所有 import 此模块的地方共享同一个值,无需 globalThis。\n */\nlet loadTs: number | undefined;\n\nexport function setLoadTimestamp(ts: number): void {\n loadTs = ts;\n}\n\n/**\n * vitest 的 importActual 函数类型\n *\n * vi.importActual(path) 走 Vite SSR pipeline:\n * - 识别 vitest.config.ts 的 resolve.alias 与 tsconfig paths 别名\n * - 让 vi.mock 在加载的模块内生效\n */\ntype ImportActualFn = (path: string) => Promise<unknown>;\n\n/**\n * 检测 vitest 的 vi.importActual(globals: true 时注入 globalThis.vi)\n *\n * 业务方 vitest.config.ts 需 test.globals: true,或在测试文件内\n * 显式 `import { vi } from 'vitest'` 后挂到 globalThis.vi。\n *\n * 不在 vitest 环境下返回 undefined,调用方回退到 Node 原生 import()。\n */\nfunction getVitestImportActual(): ImportActualFn | undefined {\n const vi = (globalThis as { vi?: { importActual?: ImportActualFn } }).vi;\n if (typeof vi?.importActual !== 'function') return undefined;\n return vi.importActual.bind(vi);\n}\n\n/**\n * 动态 import 文件\n *\n * 两种加载路径:\n *\n * 1. **vitest 环境**(`globalThis.vi.importActual` 可用):走 Vite SSR pipeline,\n * 识别 tsconfig paths 别名(如 `@/lib/db`)+ 让 `vi.mock` 生效。\n * 业务方在 vitest 下用 `createTestServer` 时自动启用。\n *\n * 2. **非 vitest 环境**:Node 原生 `import()`。watch 模式下拼接 `?t=<timestamp>`\n * query 绕过 ESM 缓存;非 watch 模式等价普通 `import()`。\n *\n * @param filePath 文件绝对路径\n * @param bustViteCache dev 按需模式下走 Node 原生 import(绕过 Vite SSR pipeline)。\n * handler.js / zod.js 是已编译产物(别名已在编译时重写为相对路径),不需要 Vite alias 解析,\n * 直接走 Node 原生 import + 时间戳 query 避免 Vite SSR 缓存干扰。\n * @returns 模块导出对象\n */\nexport async function importWithCacheBust(\n filePath: string,\n bustViteCache = false,\n): Promise<Record<string, unknown>> {\n // vitest 环境优先走 Vite pipeline(识别 tsconfig paths + vi.mock)\n const importActual = getVitestImportActual();\n if (importActual) {\n if (bustViteCache) {\n // dev 按需模式:handler.js / zod.js 是已编译产物(别名已在编译时重写为相对路径),\n // 不需要 Vite alias 解析,直接走 Node 原生 import + 时间戳 query 避免 Vite SSR 缓存干扰\n let url = pathToFileURL(filePath).href;\n url += `?t=${Date.now()}`;\n return (await import(url)) as Record<string, unknown>;\n }\n return (await importActual(filePath)) as Record<string, unknown>;\n }\n\n // 否则走 Node 原生 ESM import\n let url = pathToFileURL(filePath).href;\n if (loadTs !== undefined) {\n url += `?t=${loadTs}`;\n }\n return (await import(url)) as Record<string, unknown>;\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\nimport type { InjectorMap } from './injectorTypes';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\n\n/**\n * 中间件 + 注入器加载结果\n */\nexport interface LoadedMiddlewareBundle {\n middlewares: FaapiMiddleware[];\n injectors: InjectorMap;\n}\n\n/**\n * 中间件缓存,key 为 middlewares.ts 的绝对路径\n */\nconst middlewareCache = new Map<string, LoadedMiddlewareBundle>();\n\n/**\n * in-flight 加载去重:同一文件并发首载共享同一 Promise(冷启动并发首请求\n * 不重复 import + 合并)。invalidateMiddlewareCache 时一并清空\n */\nconst inFlight = new Map<string, Promise<LoadedMiddlewareBundle>>();\n\n/**\n * 失效所有中间件缓存(watch 模式下文件变化时调用)\n */\nexport function invalidateMiddlewareCache(): void {\n middlewareCache.clear();\n inFlight.clear();\n}\n\n/**\n * 从缓存中读取中间件 bundle(未命中返回 undefined)\n */\nexport function getCachedMiddlewares(absPath: string): LoadedMiddlewareBundle | undefined {\n return middlewareCache.get(absPath);\n}\n\n/**\n * 写入中间件缓存\n */\nexport function setCachedMiddlewares(absPath: string, bundle: LoadedMiddlewareBundle): void {\n middlewareCache.set(absPath, bundle);\n}\n\n/**\n * 从绝对路径加载 middlewares.ts 并校验\n *\n * 文件可导出:\n * - `default`:中间件数组(洋葱模型,每项为 async 函数)\n * - `injectors`:注入器映射表(按参数名匹配 handler 参数)\n *\n * 两者都是可选的,但至少要有一个。\n *\n * 加载失败时的语义:\n * - 文件 import 抛错(语法错误 / 路径不存在 / 运行时抛错)→ console.error 输出原始错误,\n * 返回空 bundle,确保服务仍可启动而非崩溃。鉴权等关键中间件失效后业务可由 onError 感知。\n * 开发期 watcher 会因 reloadRoutes 触发重新加载,实现自愈。\n */\nexport async function loadMiddlewaresFile(filePath: string): Promise<LoadedMiddlewareBundle> {\n let module: Record<string, unknown>;\n try {\n module = (await importWithCacheBust(filePath)) as Record<string, unknown>;\n } catch (err) {\n // 关键可见性:必须打印原始错误,否则业务方完全无感知(鉴权中间件失效等同于裸奔)\n console.error(\n `[faapi] Failed to load middlewares from ${filePath}:`,\n err instanceof Error ? (err.stack ?? err.message) : err,\n );\n return { middlewares: [], injectors: {} };\n }\n\n // 加载中间件数组\n const middlewares = (module.default ?? module.middlewares ?? []) as unknown[];\n if (!Array.isArray(middlewares)) {\n console.warn(`[faapi] middlewares.ts 应导出数组,已忽略: ${filePath}`);\n return { middlewares: [], injectors: {} };\n }\n\n const validMiddlewares = middlewares.filter((m: unknown) => {\n if (typeof m !== 'function') {\n console.warn(`[faapi] 无效的中间件项(应为函数),已忽略: ${typeof m}`);\n return false;\n }\n return true;\n }) as FaapiMiddleware[];\n\n // 加载注入器映射表(可选命名导出)\n const injectors = (module.injectors ?? {}) as InjectorMap;\n if (typeof injectors !== 'object' || injectors === null) {\n console.warn(`[faapi] injectors 应导出对象,已忽略: ${filePath}`);\n return { middlewares: validMiddlewares, injectors: {} };\n }\n\n // 校验注入器:每个值必须是函数\n const validInjectors: InjectorMap = {};\n for (const [name, injector] of Object.entries(injectors)) {\n if (typeof injector !== 'function') {\n console.warn(`[faapi] 注入器 ${name} 应为函数,已忽略`);\n continue;\n }\n validInjectors[name] = injector;\n }\n\n return { middlewares: validMiddlewares, injectors: validInjectors };\n}\n\n/**\n * 按路径列表加载并合并中间件(根在前,路由目录在后)\n *\n * 合并语义:\n * - 子级中间件追加在父级之后(洋葱模型:后注册的中间件在内层)\n * - 子级注入器覆盖父级同名注入器\n *\n * 单文件加载带缓存(getCachedMiddlewares/setCachedMiddlewares),重复调用仅首次真正加载。\n *\n * @param middlewarePaths 中间件文件绝对路径列表(根在前,路由目录在后)\n * @returns 合并后的中间件+注入器;无中间件时返回 undefined\n */\nexport async function loadMergedMiddlewares(\n middlewarePaths: string[],\n): Promise<LoadedMiddlewareBundle | undefined> {\n if (middlewarePaths.length === 0) return undefined;\n\n const mergedMiddlewares: FaapiMiddleware[] = [];\n const mergedInjectors: InjectorMap = {};\n\n for (const absMwPath of middlewarePaths) {\n let bundle: LoadedMiddlewareBundle | undefined = getCachedMiddlewares(absMwPath);\n if (bundle === undefined) {\n // in-flight 去重:并发首载共享同一 Promise(对照 compileOnDemand 的 mutex 模式)\n let loading = inFlight.get(absMwPath);\n if (!loading) {\n loading = loadMiddlewaresFile(absMwPath).then((result) => {\n setCachedMiddlewares(absMwPath, result);\n return result;\n });\n inFlight.set(absMwPath, loading);\n }\n bundle = await loading;\n }\n mergedMiddlewares.push(...bundle.middlewares);\n for (const [name, injector] of Object.entries(bundle.injectors)) {\n mergedInjectors[name] = injector;\n }\n }\n\n if (mergedMiddlewares.length === 0 && Object.keys(mergedInjectors).length === 0) {\n return undefined;\n }\n\n return { middlewares: mergedMiddlewares, injectors: mergedInjectors };\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\n\n/** 路由源码目录(写死为 src) */\nexport const APP_DIR = 'src';\n\n/** 路由文件扫描 patterns(devCommand / buildCommand / createAppCore 共用) */\nexport const ROUTE_PATTERNS = ['src/api/**/*.ts'];\n\n/**\n * 把源码 filePath(src/api/hello/handler.ts)转为产物路径(<dist>/api/hello/handler.js)\n *\n * 产物结构打平 src/ 前缀:去掉 `src/`,加 dist 前缀,.ts → .js。\n * 已带 `<dist>/` 前缀的输入保持不变(幂等)。\n */\nexport function toProdFilePath(filePath: string, dist: string): string {\n let rel = filePath.replace(/\\\\/g, '/');\n // 去掉 src/ 前缀(打平产物结构)\n if (rel.startsWith('src/')) {\n rel = rel.slice(4);\n }\n const jsPath = rel.replace(/\\.ts$/, '.js');\n return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;\n}\n\n/**\n * 源文件后缀 → 产物后缀(.ts/.tsx/.jsx → .js,其余原样)\n */\nexport function toProdExtension(filePath: string): string {\n if (filePath.endsWith('.ts')) return filePath.slice(0, -3) + '.js';\n if (filePath.endsWith('.tsx')) return filePath.slice(0, -4) + '.js';\n if (filePath.endsWith('.jsx')) return filePath.slice(0, -4) + '.js';\n return filePath;\n}\n\n/**\n * 规范化路径为 realpath(处理 macOS /tmp → /private/tmp 等符号链接场景)\n *\n * 目录不存在时回退到原路径(不抛错),保证调用方逻辑稳定。\n */\nexport function toRealPath(p: string): string {\n try {\n return fs.realpathSync(p);\n } catch {\n return p;\n }\n}\n\n/**\n * 判断 filePath 是否位于 dir 目录下(不依赖路径前缀字符串比较,兼容符号链接规范化差异)\n *\n * 基于 `path.relative`:相对路径不以 `..` 开头且非绝对路径时视为位于 dir 下。\n * 调用前应先用 `toRealPath` 规范化两侧路径,确保前缀一致。\n */\nexport function isInsideDir(filePath: string, dir: string): boolean {\n const rel = path.relative(dir, filePath);\n return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);\n}\n\n/**\n * 源文件相对路径剥离 `src/` 前缀(打平产物结构)\n *\n * schema/tool zod.js 输出路径计算的公共步骤:\n * `src/api/hello/handler.ts` → `api/hello/handler.ts`(取目录后 join dist + zod.js)\n */\nexport function stripSrcPrefix(sourceFile: string): string {\n const rel = sourceFile.replace(/\\\\/g, '/');\n return rel.startsWith('src/') ? rel.slice(4) : rel;\n}\n","import type { RouteManifest } from './routeTypes';\n\n/**\n * 路由排序规则:\n * 1. 静态路由优先于动态路由\n * 2. 动态路由优先于 catch-all 路由\n * 3. 路径段数少的优先\n * 4. 字母序排列\n */\nexport function sortRoutes(routes: RouteManifest): RouteManifest {\n return [...routes].sort((a, b) => {\n // 1. 静态路由优先于动态路由\n if (a.isDynamic !== b.isDynamic) {\n return a.isDynamic ? 1 : -1;\n }\n\n // 2. catch-all 路由优先级最低(排在所有动态路由之后)\n if (a.isCatchAll !== b.isCatchAll) {\n return a.isCatchAll ? 1 : -1;\n }\n\n // 3. 路径段数少的优先\n const aSegments = a.urlPath.split('/').filter(Boolean).length;\n const bSegments = b.urlPath.split('/').filter(Boolean).length;\n if (aSegments !== bSegments) {\n return aSegments - bSegments;\n }\n\n // 4. 字母序排列\n return a.urlPath.localeCompare(b.urlPath);\n });\n}\n","import path from 'node:path';\nimport { atomicWriteFile } from '../utils/atomicWrite';\nimport type { RouteManifest } from '../router/routeTypes';\nimport type { RouteSchemaSource } from './collectRouteSchemaSources';\nimport type { RuntimeType } from '../ast/resolveTypeNode';\nimport { collectRouteSchemaSources } from './collectRouteSchemaSources';\nimport {\n generateZodSchemaSourceParts,\n generateHelpersFileSource,\n usesCoerceHelpers,\n HELPERS_FILENAME,\n} from '../ast/generateZodSchema';\n\n/**\n * 源文件路径 → 产物 zod.js 路径\n *\n * 每个 handler 目录下生成一个 `zod.js`(与 handler.js 同级,文件名固定为 zod.js):\n * - `src/api/hello/handler.ts` → `<dist>/api/hello/zod.js`\n *\n * 路由源码目录写死为 src,剥离 `src/` 前缀。\n *\n * @param sourceFile 源文件相对路径(相对 rootDir,如 'src/api/hello/handler.ts')\n * @param dist 输出目录(如 'dist' 或 '.faapi')\n * @param rootDir 项目根目录\n */\nexport function getSchemaOutputPath(sourceFile: string, dist: string, rootDir: string): string {\n let rel = sourceFile.replace(/\\\\/g, '/');\n // 去掉 src/ 前缀(打平产物结构)\n if (rel.startsWith('src/')) {\n rel = rel.slice(4);\n }\n // 取目录部分,basename 固定为 zod.js(每个 handler 目录一个 zod.js)\n const idx = rel.lastIndexOf('/');\n const relDir = idx >= 0 ? rel.slice(0, idx) : '';\n return path.resolve(rootDir, dist, relDir, 'zod.js');\n}\n\n/**\n * 运行时从 route.filePath 计算对应 zod.js 绝对路径\n *\n * 每个 handler 目录下的 zod.js(文件名固定为 zod.js,与 handler.js 同级):\n * - dev 模式:route.filePath 是源码路径(如 'src/api/hello/handler.ts'),\n * zod.js 在 `<rootDir>/<dist>/api/hello/zod.js`\n * - prod 模式:route.filePath 是产物路径(如 'dist/api/hello/handler.js'),\n * zod.js 在 `<rootDir>/dist/api/hello/zod.js`(与 handler.js 同级)\n *\n * 通过同时检查 src 和 dist 前缀,统一处理两种模式:\n * - dev:strip 'src/' 前缀,join dist\n * - prod:strip 'dist/' 前缀,join dist(dist='dist',结果与 rootDir 一致)\n *\n * @param filePath route.filePath(dev 为源码路径,prod 为产物路径)\n * @param dist 输出目录(如 'dist' 或 '.faapi')\n * @param rootDir 项目根目录\n */\nexport function getRuntimeSchemaPath(filePath: string, dist: string, rootDir: string): string {\n let rel = filePath.replace(/\\\\/g, '/');\n // strip src/ 前缀(dev 模式:filePath = 'src/...')\n if (rel.startsWith('src/')) {\n rel = rel.slice(4);\n }\n // strip dist 前缀(prod 模式:filePath = 'dist/...')\n else if (rel.startsWith(`${dist}/`)) {\n rel = rel.slice(dist.length + 1);\n }\n // 取目录部分,basename 固定为 zod.js\n const idx = rel.lastIndexOf('/');\n const relDir = idx >= 0 ? rel.slice(0, idx) : '';\n return path.resolve(rootDir, dist, relDir, 'zod.js');\n}\n\n/**\n * 计算 zod.js 到 faapi-helpers.js 的相对 import 路径\n *\n * faapi-helpers.js 固定在 dist 根部,zod.js 可能在子目录(如 'api/hello/zod.js')。\n * 根据子目录深度计算 `../` 数量:\n * - zod.js 在 `api/hello/`(深度 2)→ `../../faapi-helpers.js`\n * - zod.js 在 `api/`(深度 1)→ `../faapi-helpers.js`\n * - zod.js 在 dist 根(深度 0,理论不会发生)→ `./faapi-helpers.js`\n *\n * @param relDir zod.js 所在目录相对 dist 的路径(如 'api/hello' 或 '')\n * @returns ESM import 路径(如 '../../faapi-helpers.js')\n */\nexport function getHelpersImportPath(relDir: string): string {\n if (!relDir) return `./${HELPERS_FILENAME}`;\n const depth = relDir.split('/').filter(Boolean).length;\n return `${'../'.repeat(depth)}${HELPERS_FILENAME}`;\n}\n\n/**\n * 生成单个 handler 文件的 zod.js 源码\n *\n * 自包含:extractAllTypes 在 AST 阶段已通过 TypeScript checker 内联跨文件类型,\n * 每个 zod.js 无需 import 其他 zod.js。ref 仅用于同文件内的循环引用(通过 z.lazy 处理)。\n *\n * 导出格式:\n * - `<SchemaName>Schema`:zod schema 对象(用于 safeParse 校验)\n *\n * 无类型声明的方法不导出对应 Schema(validateInput 检测到 undefined 跳过校验)。\n *\n * coerce 逻辑:query/params 来源均为 string,schemaName 以 \"Query\" 或 \"Params\" 结尾时\n * 生成 coerce=true 的 schema(number/boolean 字段用 z.preprocess 包裹字符串转换)。\n * body 是 JSON 解析的天然 JS 类型,不需要 coerce。form 与 body 共享 schema 名(POSTBody),\n * 但 `RouteSchemaSource.coerce=true` 显式覆盖(form 值均为 string,需 coerce)。\n * Map/Set 字段在两种场景下都生成 z.preprocess 包裹(JSON.parse 出来的是数组/对象,需还原为 Map/Set 实例)。\n *\n * 命名类型声明文件级去重:同一文件的多个方法引用同一命名类型时,`const XSchema` 只在\n * 文件头声明一次(按首次引用顺序),避免重复 const 声明导致 zod.js import 即 SyntaxError。\n *\n * 公用函数复用:schema 引用 coerceNumber / coerceBoolean / coerceMap / coerceSet 变量时,\n * 这些变量从 dist 根部的 faapi-helpers.js import(跨文件复用,仅一份声明)。\n *\n * @param sources 同一文件的 schema 提取结果(含多个方法,如 GETQuery + POSTBody)\n * @param resolveType ref 解析函数(解析循环引用中的 ref):名称 → 运行时类型,\n * 未声明返回 undefined。惰性实现由 collectRouteSchemaSources 的 resolversByFile 提供。\n * @param helpersImportPath 到 faapi-helpers.js 的相对 import 路径(如 '../../faapi-helpers.js')。\n * 传空字符串表示不注入 coerce helpers 的 import(用于无 coerce schema 的文件或不支持外部 import 的测试场景)。\n * @returns zod.js 源码字符串\n */\nexport function generateSchemaFileSource(\n sources: RouteSchemaSource[],\n resolveType: (name: string) => RuntimeType | undefined,\n helpersImportPath: string,\n): string {\n const lines: string[] = [\"import { z } from 'zod';\"];\n\n // 文件级命名类型声明去重:同一文件的多个方法引用同一命名类型(如 GETQuery 与\n // POSTBody 都引用 Item)时,const 声明只提升到文件头生成一次——否则拼接产物含\n // 重复 const 声明,zod.js import 即 SyntaxError,该文件所有路由 500。\n const namedTypeDeclarations: string[] = [];\n const seenNamedTypes = new Set<string>();\n\n // 先生成所有 schema 入口导出,暂存到 schemaBlocks\n const schemaBlocks: string[] = [];\n for (const source of sources) {\n const { schemaName, typeInfo } = source;\n if (!typeInfo) {\n // 无类型声明,不导出对应 Schema\n continue;\n }\n\n // 推断 coerce:\n // - source.coerce 显式设置时优先使用(form 声明时由 collectRouteSchemaSources 设置为 true)\n // - 否则回退到 schemaName 后缀正则:query/params 需要 coerce(URL 来源均为 string),body 不需要\n const coerce = source.coerce ?? /(?:Query|Params)$/.test(schemaName);\n\n // 生成结构化片段:命名类型声明按名去重后提升到文件头,入口导出留在方法 block 内\n const { namedTypeDeclarations: decls, entryDeclaration } = generateZodSchemaSourceParts(\n typeInfo,\n resolveType,\n schemaName,\n coerce,\n );\n for (const { name, declaration } of decls) {\n if (seenNamedTypes.has(name)) continue;\n seenNamedTypes.add(name);\n namedTypeDeclarations.push(declaration);\n }\n\n schemaBlocks.push([`// ${schemaName}`, entryDeclaration, ''].join('\\n'));\n }\n\n if (namedTypeDeclarations.length > 0) {\n lines.push(...namedTypeDeclarations);\n lines.push('');\n }\n\n // 检测是否有 schema 引用了 coerce 公用函数,若有则注入 import 语句\n // 公用函数包含 coerceNumber / coerceBoolean(query/params 的 string 转换)和\n // coerceMap / coerceSet(Map/Set 的 JSON 还原,body 场景也会引用)\n const allSchemaCode = [...namedTypeDeclarations, ...schemaBlocks].join('\\n');\n if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {\n lines.push(\n `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`,\n );\n }\n lines.push('');\n\n lines.push(...schemaBlocks);\n\n return lines.join('\\n').replace(/\\n+$/, '\\n');\n}\n\n/**\n * 为路由清单中每个 handler 生成 zod.js\n *\n * 输出路径与 handler.js 同级(文件名固定为 zod.js):\n * - dev:`.faapi/api/hello/zod.js`\n * - prod:`dist/api/hello/zod.js`\n *\n * 公用函数复用:若项目中存在 coerce schema(query/params)或 Map/Set 字段,\n * 在 dist 根部生成 `faapi-helpers.js`(导出 coerceNumber / coerceBoolean / coerceMap / coerceSet),\n * 各 zod.js 通过相对路径 import。\n *\n * 内部流程:\n * 1. collectRouteSchemaSources 从路由清单提取 schema 源数据(AST 从源码 .ts)\n * 2. 按 filePath 分组 sources\n * 3. 为每个文件生成 zod.js 源码(暂存,用于检测是否需要 helpers)\n * 4. 检测是否需要 helpers,需要则生成 faapi-helpers.js\n * 5. 并行写入所有 zod.js\n *\n * @param routes 排序后的路由清单\n * @param rootDir 项目根目录\n * @param dist 输出目录(如 '.faapi' 或 'dist')\n */\nexport async function generateSchemaFiles(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<void> {\n if (routes.length === 0) return;\n\n const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir);\n\n // 按 filePath 分组 sources(同一 handler 的多个方法合并到一个 zod.js)\n const sourcesByFile = new Map<string, RouteSchemaSource[]>();\n for (const source of sources) {\n let list = sourcesByFile.get(source.filePath);\n if (!list) {\n list = [];\n sourcesByFile.set(source.filePath, list);\n }\n list.push(source);\n }\n\n // 为每个文件生成 zod.js 源码(先暂存,用于检测是否需要 helpers)\n const fileEntries: { outputPath: string; source: string }[] = [];\n for (const [filePath, fileSources] of sourcesByFile) {\n // filePath 是绝对路径,转为相对 rootDir 的路径用于计算输出路径\n const relFile = path.relative(rootDir, filePath).replace(/\\\\/g, '/');\n const outputPath = getSchemaOutputPath(relFile, dist, rootDir);\n // 计算 zod.js 所在目录相对 dist 的路径(用于 import helpers)\n // 与 getSchemaOutputPath 的目录计算逻辑一致:strip src/ 前缀后取目录部分\n let relForDir = relFile;\n if (relForDir.startsWith('src/')) {\n relForDir = relForDir.slice(4);\n }\n const dirIdx = relForDir.lastIndexOf('/');\n const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : '';\n const helpersImportPath = getHelpersImportPath(zodRelDir);\n\n const resolver = resolversByFile.get(filePath);\n const source = generateSchemaFileSource(\n fileSources,\n (name) => resolver?.resolve(name)?.runtimeType,\n helpersImportPath,\n );\n fileEntries.push({ outputPath, source });\n }\n\n // 检测是否需要生成 faapi-helpers.js\n const allSourceCode = fileEntries.map((e) => e.source).join('\\n');\n if (usesCoerceHelpers(allSourceCode)) {\n const helpersPath = path.resolve(rootDir, dist, HELPERS_FILENAME);\n await writeSchemaFile(helpersPath, generateHelpersFileSource());\n }\n\n // 并行写入所有 zod.js\n await Promise.all(\n fileEntries.map(({ outputPath, source }) => writeSchemaFile(outputPath, source)),\n );\n}\n\n/**\n * 写入 zod.js 文件(确保目录存在)\n */\nasync function writeSchemaFile(outputPath: string, source: string): Promise<void> {\n // 原子写:dev watch 重建与在途请求并发时,请求 import zod.js 不能读到半成品\n await atomicWriteFile(outputPath, source);\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\n\n/**\n * 原子写文件:先写临时文件再 rename 替换\n *\n * rename 在同一文件系统上是原子的(POSIX),并发读者要么看到旧文件要么看到\n * 新文件,不会读到截断的半成品。用于运行时会 import 的产物(zod.js、路由清单、\n * tool/agent 清单)——dev watch 重建与在途请求并发时,非原子写会让请求 import\n * 到半成品文件,报 SyntaxError/ENOENT 且错误信息不指向根因。\n *\n * 与 compileSourceFiles 的 esbuild 原子写(write:false + tmp + rename)同源语义。\n *\n * @param outputPath 目标绝对路径\n * @param content 文件内容\n */\nexport async function atomicWriteFile(outputPath: string, content: string): Promise<void> {\n await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });\n const tmp = `${outputPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n await fs.promises.writeFile(tmp, content, 'utf-8');\n await fs.promises.rename(tmp, outputPath);\n}\n","import ts from 'typescript';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Program 缓存,key 为入口文件路径\n *\n * watch 模式下通过 invalidateProgramCache() 全量清理。\n * 同一文件多次创建 Program 时复用缓存,避免重复解析。\n */\nconst programCache = new Map<string, ts.Program>();\n\n/**\n * tsconfig 解析结果缓存,key 为 tsconfig.json 绝对路径\n *\n * 同一 tsconfig 下多个文件共享解析结果,避免每个文件都读盘 + parse。\n * invalidateProgramCache() 同时清空此缓存。\n */\ninterface TsConfigResult {\n /** parseJsonConfigFileContent 解析得到的 module 选项 */\n module?: ts.ModuleKind;\n /** parseJsonConfigFileContent 解析得到的 moduleResolution 选项 */\n moduleResolution?: ts.ModuleResolutionKind;\n /** tsconfig include 模式匹配的所有 .ts 文件绝对路径(作为 program rootNames) */\n fileNames: string[];\n}\nconst tsConfigCache = new Map<string, TsConfigResult>();\n\n/**\n * 清理所有 Program 缓存 + tsconfig 解析缓存(watch 模式下文件变化时调用)\n *\n * 全量清理而非增量清理,理由:\n * - 简单可靠,无状态一致性问题\n * - 跨文件类型引用需要所有文件的 Program 同步更新\n * - tsconfig 可能变化,需重新读取\n * - dev 模式文件量有限,全量重建在百毫秒级\n */\nexport function invalidateProgramCache(): void {\n programCache.clear();\n tsConfigCache.clear();\n}\n\n/**\n * 从指定文件路径向上查找最近的 tsconfig.json\n *\n * 从 filePath 所在目录开始逐级向上,返回第一个找到的 tsconfig.json 绝对路径。\n * 找不到返回 null(如 os.tmpdir() 测试场景)。\n */\nfunction findTsConfig(filePath: string): string | null {\n let dir = path.dirname(filePath);\n const root = path.parse(dir).root;\n while (true) {\n const candidate = path.join(dir, 'tsconfig.json');\n if (fs.existsSync(candidate)) {\n return candidate;\n }\n if (dir === root) return null;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * 解析 tsconfig.json,提取 module / moduleResolution / fileNames\n *\n * 使用 ts.readConfigFile 读取 + ts.parseJsonConfigFileContent 解析。\n * 保留 tsconfig 原始的 include/exclude/files 配置,让 ts 自动扫描所有相关 .ts 文件\n * 作为 program 的 rootNames——这样跨文件 import 的源文件会被加载到 program 中,\n * 使 resolveTypeReference 的兜底遍历(找同名声明)能成功。\n *\n * 只提取 module / moduleResolution 两个 compilerOptions 覆盖默认值,其余字段\n * (target / strict / jsx / paths 等)用框架默认值,避免业务项目配置干扰 AST 提取。\n *\n * 解析失败(语法错误 / 缺字段)返回空 fileNames + 空选项,回退到默认 NodeNext,\n * 保证 build 不被 tsconfig 损坏阻塞。\n */\nfunction parseTsConfig(tsconfigPath: string): TsConfigResult {\n const cached = tsConfigCache.get(tsconfigPath);\n if (cached) return cached;\n\n const result: TsConfigResult = { fileNames: [] };\n\n try {\n const configFile = ts.readConfigFile(tsconfigPath, (p) => fs.readFileSync(p, 'utf-8'));\n if (configFile.error) {\n tsConfigCache.set(tsconfigPath, result);\n return result;\n }\n\n const config = configFile.config ?? {};\n const basePath = path.dirname(tsconfigPath);\n\n // 保留 tsconfig 原始 include/exclude/files,让 ts 自动扫描 .ts 文件\n // 这些文件作为 program 的 rootNames,让跨文件 import 的源文件被加载进来\n const parsed = ts.parseJsonConfigFileContent(\n config,\n ts.sys,\n basePath,\n /* existingOptions */ undefined,\n tsconfigPath,\n );\n\n result.fileNames = parsed.fileNames;\n if (parsed.options.module !== undefined) {\n result.module = parsed.options.module;\n }\n if (parsed.options.moduleResolution !== undefined) {\n result.moduleResolution = parsed.options.moduleResolution;\n }\n } catch {\n // 任何异常都回退默认,保证 build 不被 tsconfig 阻塞\n }\n\n tsConfigCache.set(tsconfigPath, result);\n return result;\n}\n\n/**\n * 为指定文件创建 TypeScript Program(带缓存)\n *\n * 从 filePath 向上查找最近的 tsconfig.json:\n * - 找到:用 ts.parseJsonConfigFileContent 解析,取 module / moduleResolution\n * 覆盖默认值,同时取 parsed.fileNames 作为 program 的 rootNames(让跨文件\n * import 的源文件被加载进 program)\n * - 找不到(如 os.tmpdir() 测试场景):回退到默认 NodeNext,rootNames 仅包含 filePath\n *\n * 加载全部相关文件保证 `resolveTypeReference` 的兜底遍历(找同名声明)能成功,\n * 修复业务项目用 `moduleResolution: Bundler` + 无扩展名相对导入时跨文件\n * `import type` 解析失败的问题。\n *\n * 同一次生成中需要分析多个文件时用 {@link createPrograms}(按 tsconfig 分组共享\n * 同一个 Program),避免每个文件单独创建 Program 重复解析全项目源码。\n *\n * @param filePath 要分析的 .ts 文件绝对路径\n */\nexport function createProgram(filePath: string): ts.Program {\n const cached = programCache.get(filePath);\n if (cached) {\n return cached;\n }\n\n const program = buildProgram([filePath], findTsConfig(filePath));\n\n programCache.set(filePath, program);\n return program;\n}\n\n/**\n * 为多个文件创建 Program(按 tsconfig 分组共享,带缓存)\n *\n * 同一次批量提取(build 时生成 routes/tools/agents 产物)中,所有向上查找到\n * 同一个 tsconfig.json 的文件共用**同一个 Program**——rootNames 为\n * tsconfig fileNames ∪ 全部入口文件,跨文件类型解析语义与单文件 `createProgram`\n * 完全一致,但 N 个文件只创建 1 个 Program(原来每个文件都全量解析一遍项目源码,\n * 是 build 时间的最大单项开销)。\n *\n * 缓存 key 为 `shared::<tsconfigPath>`(同一 tsconfig 一份 Program):同一批次重复\n * 调用命中缓存;不同批次通过 `program.getSourceFile` 校验缓存已覆盖本次全部入口文件,\n * 未覆盖(入口在 tsconfig include 之外)时重建。若把文件列表纳入 key,dev 按需模式下\n * 每个路由文件会各自持有一份全项目 Program(内存 O(路由数 × 项目大小))。\n * `invalidateProgramCache()` 同时清理共享缓存与单文件缓存。\n *\n * 向上查找不到 tsconfig.json 的文件(如 os.tmpdir() 测试场景)逐个回退到\n * {@link createProgram} 单文件行为,不参与共享。\n *\n * @param filePaths 要分析的 .ts 文件绝对路径列表(内部去重)\n * @returns filePath → Program 映射(包含全部请求文件)\n */\nexport function createPrograms(filePaths: string[]): Map<string, ts.Program> {\n const unique = [...new Set(filePaths)];\n const result = new Map<string, ts.Program>();\n\n // 按向上查找到的 tsconfig.json 分组(同组共享一个 Program)\n const groups = new Map<string, { tsconfigPath: string; files: string[] }>();\n const noTsconfigFiles: string[] = [];\n for (const filePath of unique) {\n const tsconfigPath = findTsConfig(filePath);\n if (!tsconfigPath) {\n noTsconfigFiles.push(filePath);\n continue;\n }\n const group = groups.get(tsconfigPath);\n if (group) {\n group.files.push(filePath);\n } else {\n groups.set(tsconfigPath, { tsconfigPath, files: [filePath] });\n }\n }\n\n for (const { tsconfigPath, files } of groups.values()) {\n // 缓存 key 只含 tsconfigPath:buildProgram 的 rootNames = 入口 ∪ tsconfig 全部\n // fileNames,同一 tsconfig 下不同入口批次的 Program 内容几乎一致——若把文件列表\n // 纳入 key,dev 按需模式下每个路由文件会各自持有一份\"全项目 Program\"\n // (内存 O(路由数 × 项目大小),无淘汰)。改为按 tsconfig 共享一份,通过\n // getSourceFile 校验缓存里的 Program 覆盖了本次全部入口文件(入口在 tsconfig\n // include 之外的场景重建,保证不缺文件)\n const cacheKey = `shared::${tsconfigPath}`;\n let program = programCache.get(cacheKey);\n const coversAll =\n program !== undefined && files.every((f) => program!.getSourceFile(f) !== undefined);\n if (!program || !coversAll) {\n program = buildProgram(files, tsconfigPath);\n programCache.set(cacheKey, program);\n }\n for (const filePath of files) {\n result.set(filePath, program);\n }\n }\n\n // 无 tsconfig 的文件回退单文件行为(保持与 createProgram 一致)\n for (const filePath of noTsconfigFiles) {\n result.set(filePath, createProgram(filePath));\n }\n\n return result;\n}\n\n/**\n * 构建 Program:合并框架默认 options 与 tsconfig 的 module / moduleResolution,\n * rootNames = 入口文件 ∪ tsconfig fileNames\n *\n * 由 {@link createProgram}(单文件)和 {@link createPrograms}(批量共享)共用,\n * 保证两条路径的 Program 构建语义一致。\n */\nfunction buildProgram(entryFiles: string[], tsconfigPath: string | null): ts.Program {\n // 默认选项:与项目历史行为一致(NodeNext),保证向后兼容\n const options: ts.CompilerOptions = {\n strict: true,\n target: ts.ScriptTarget.ES2022,\n module: ts.ModuleKind.NodeNext,\n moduleResolution: ts.ModuleResolutionKind.NodeNext,\n skipLibCheck: true,\n noEmit: true,\n };\n\n // rootNames 默认只包含入口文件;读 tsconfig 后扩展为全部相关文件\n const rootNames = [...entryFiles];\n\n if (tsconfigPath) {\n const tsOptions = parseTsConfig(tsconfigPath);\n if (tsOptions.module !== undefined) {\n options.module = tsOptions.module;\n }\n if (tsOptions.moduleResolution !== undefined) {\n options.moduleResolution = tsOptions.moduleResolution;\n }\n if (tsOptions.fileNames.length > 0) {\n // 确保入口文件在 rootNames 中(即使 tsconfig include 未覆盖到)\n for (const fileName of tsOptions.fileNames) {\n if (!rootNames.includes(fileName)) {\n rootNames.push(fileName);\n }\n }\n }\n }\n\n return ts.createProgram(rootNames, options);\n}\n","import ts from 'typescript';\nimport {\n resolveTypeNode,\n resolveInterfaceDeclaration,\n resolveEnumDeclaration,\n findTopLevelDecl,\n setProgramContext,\n type PropertyType,\n type RuntimeType,\n SchemaExtractionError,\n} from './resolveTypeNode';\n\nexport interface HandlerTypeInfo {\n name: string; // 类型名,如 'GETQuery'\n properties: PropertyType[];\n /** 完整的运行时类型描述(用于嵌套校验) */\n runtimeType: RuntimeType;\n}\n\n/**\n * 从源文件中提取指定名称的类型信息\n *\n * 支持的类型声明:\n * - interface 声明(含继承)\n * - type 别名(type Query = { ... })\n * - enum 声明(跨文件回退路径支持)\n *\n * 自身文件找不到目标声明时,回退到 program 的其他源文件查找同名顶层声明\n * (跳过 node_modules / TypeScript lib,首个匹配生效,与 resolveImportAlias\n * 兜底路径语义一致)。该回退服务于入口类型中第二次出现的跨文件引用:首次引用\n * 已由 checker 内联,二次引用被标记为 ref,代码生成阶段经\n * createLazyTypeResolver → extractTypeInfo 解析,若无回退会静默生成\n * z.unknown(),违背「不降级放行」约定。\n *\n * 遇到不支持的类型时抛 `SchemaExtractionError`,错误信息包含文件路径和类型名。\n *\n * @param program TypeScript Program\n * @param filePath 源文件路径\n * @param typeName 类型名,如 'GETQuery'\n */\nexport function extractTypeInfo(\n program: ts.Program,\n filePath: string,\n typeName: string,\n): HandlerTypeInfo | null {\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) return null;\n\n const checker = program.getTypeChecker();\n\n // 设置 program 上下文,供 resolveImportAlias 兜底遍历跨文件声明使用\n // (TypeChecker 运行时未暴露 getProgram(),通过模块级变量传入)\n setProgramContext(program);\n try {\n // 1. 查找 interface 声明\n let result: HandlerTypeInfo | null = null;\n\n ts.forEachChild(sourceFile, (node) => {\n if (result) return;\n\n if (ts.isInterfaceDeclaration(node) && node.name.text === typeName) {\n const visited = new Set<string>();\n visited.add(typeName); // 标记当前类型,使自引用直接返回 ref\n const runtimeType = withFileContext(filePath, typeName, () =>\n resolveInterfaceDeclaration(node, checker, visited),\n );\n result = {\n name: typeName,\n properties: runtimeType.kind === 'object' ? runtimeType.properties : [],\n runtimeType,\n };\n return;\n }\n\n // 2. 查找 type 别名\n if (ts.isTypeAliasDeclaration(node) && node.name.text === typeName) {\n const visited = new Set<string>();\n visited.add(typeName); // 标记当前类型,使自引用直接返回 ref\n const runtimeType = withFileContext(filePath, typeName, () =>\n resolveTypeNode(node.type, checker, visited),\n );\n result = {\n name: typeName,\n properties: runtimeType.kind === 'object' ? runtimeType.properties : [],\n runtimeType,\n };\n return;\n }\n });\n\n if (result) return result;\n\n // 3. 跨文件回退:自身文件无该声明时,在 program 的其他源文件中查找同名顶层声明\n for (const sf of program.getSourceFiles()) {\n if (sf === sourceFile) continue;\n // 与 resolveImportAlias 兜底路径使用相同的过滤规则\n if (sf.fileName.includes('/node_modules/') || sf.fileName.includes('typescript/lib/')) {\n continue;\n }\n const found = findTopLevelDecl(sf, typeName);\n if (!found) continue;\n\n const visited = new Set<string>();\n visited.add(typeName);\n const runtimeType = withFileContext(filePath, typeName, () => {\n if (found.kind === 'interface') {\n return resolveInterfaceDeclaration(found.node, checker, visited);\n }\n if (found.kind === 'typeAlias') {\n return resolveTypeNode(found.node.type, checker, visited);\n }\n return resolveEnumDeclaration(found.node);\n });\n return {\n name: typeName,\n properties: runtimeType.kind === 'object' ? runtimeType.properties : [],\n runtimeType,\n };\n }\n\n return null;\n } finally {\n setProgramContext(null);\n }\n}\n\n/**\n * 从源文件中提取所有 interface 和 type 别名的类型信息\n *\n * 用于:\n * - 生成 schema 模块时遍历所有命名类型\n * - 作为 typeResolver 提供给 generateZodSchema,解析循环引用中的 ref\n *\n * @returns Map<类型名, HandlerTypeInfo>\n */\nexport function extractAllTypes(\n program: ts.Program,\n filePath: string,\n): Map<string, HandlerTypeInfo> {\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) return new Map();\n\n const checker = program.getTypeChecker();\n\n // 设置 program 上下文,供 resolveImportAlias 兜底遍历跨文件声明使用\n setProgramContext(program);\n try {\n const result = new Map<string, HandlerTypeInfo>();\n\n ts.forEachChild(sourceFile, (node) => {\n if (ts.isInterfaceDeclaration(node)) {\n const visited = new Set<string>();\n visited.add(node.name.text); // 标记当前类型,使自引用直接返回 ref\n const runtimeType = withFileContext(filePath, node.name.text, () =>\n resolveInterfaceDeclaration(node, checker, visited),\n );\n result.set(node.name.text, {\n name: node.name.text,\n properties: runtimeType.kind === 'object' ? runtimeType.properties : [],\n runtimeType,\n });\n return;\n }\n\n if (ts.isTypeAliasDeclaration(node)) {\n const visited = new Set<string>();\n visited.add(node.name.text); // 标记当前类型,使自引用直接返回 ref\n const runtimeType = withFileContext(filePath, node.name.text, () =>\n resolveTypeNode(node.type, checker, visited),\n );\n result.set(node.name.text, {\n name: node.name.text,\n properties: runtimeType.kind === 'object' ? runtimeType.properties : [],\n runtimeType,\n });\n return;\n }\n });\n\n return result;\n } finally {\n setProgramContext(null);\n }\n}\n\n/**\n * 惰性类型解析器\n *\n * 按名称解析类型并缓存。与 extractAllTypes 的差异:extractAllTypes 提前解析\n * 文件中全部顶层类型——与路由无关的类型也会被解析,其中任何类型含不支持语法\n * 都会拖垮整个 build/reload;惰性解析器只在类型真正被需要时(入口类型或\n * ref 引用)才解析,无关类型零开销。\n */\nexport interface LazyTypeResolver {\n /**\n * 按名称解析类型\n *\n * @returns 类型信息;文件中无该名称的声明时返回 null\n * @throws SchemaExtractionError 类型声明含不支持语法时(与 extractTypeInfo 一致,不降级)\n */\n resolve(name: string): HandlerTypeInfo | null;\n}\n\n/**\n * 创建文件的惰性类型解析器\n *\n * 缓存保证同一类型只解析一次(幂等返回同一实例)。\n *\n * @param program TypeScript Program(解析期间会临时设置 program 上下文,供\n * resolveImportAlias 兜底遍历跨文件声明使用)\n * @param filePath 源文件绝对路径\n */\nexport function createLazyTypeResolver(program: ts.Program, filePath: string): LazyTypeResolver {\n const cache = new Map<string, HandlerTypeInfo | null>();\n return {\n resolve(name: string): HandlerTypeInfo | null {\n if (cache.has(name)) return cache.get(name)!;\n const info = extractTypeInfo(program, filePath, name);\n cache.set(name, info);\n return info;\n },\n };\n}\n\n/**\n * 包装类型解析,捕获 SchemaExtractionError 并补充文件路径和类型名上下文\n *\n * 只包装一次(最外层),避免嵌套调用时重复包装。\n */\nfunction withFileContext<T>(filePath: string, typeName: string, fn: () => T): T {\n try {\n return fn();\n } catch (err) {\n if (err instanceof SchemaExtractionError) {\n // 补充文件路径和类型名,只包装一次;保留行号位置(错误在类型文件中的精确定位)\n const fileName = filePath.split('/').pop() ?? filePath;\n const enriched = new SchemaExtractionError(\n err.typeText,\n `${err.reason}(文件: ${fileName}, 类型: ${typeName})`,\n { cause: err },\n err.location,\n );\n throw enriched;\n }\n throw err;\n }\n}\n","import ts from 'typescript';\n\n/**\n * 模块级 Program 上下文,供 `resolveImportAlias` 兜底遍历跨文件声明使用\n *\n * TypeScript 的 `TypeChecker` 类型声明了 `getProgram(): Program` 方法,但\n * 运行时实例未暴露该方法(实测 `typeof checker.getProgram === 'undefined'`)。\n * 因此采用模块级变量在调用入口设置当前 program,resolveImportAlias 兜底时读取。\n *\n * 调用入口(`extractTypeInfo` / `extractAllTypes`)在分析前调\n * `setProgramContext(program)`,分析结束后调 `setProgramContext(null)` 清空。\n *\n * 串行场景安全:faapi 的 AST 提取在单线程顺序执行,无并发。\n */\nlet currentProgram: ts.Program | null = null;\n\n/**\n * 设置当前 Program 上下文(供 resolveImportAlias 兜底遍历跨文件声明使用)\n *\n * @param program 当前 program;分析结束后传 null 清空\n */\nexport function setProgramContext(program: ts.Program | null): void {\n currentProgram = program;\n}\n\n/**\n * Schema 提取错误\n *\n * 遇到无法解析或不支持运行时校验的类型时抛出,\n * 避免静默降级为 any 导致用户不知情。\n */\n/** 错误在源文件中的位置(file:line:column,便于在几百行类型文件中定位) */\nexport interface SchemaErrorLocation {\n file: string;\n line: number;\n column: number;\n}\n\nexport class SchemaExtractionError extends Error {\n constructor(\n public readonly typeText: string,\n public readonly reason: string,\n options?: ErrorOptions,\n public readonly location?: SchemaErrorLocation,\n ) {\n super(\n `无法解析类型 \"${typeText}\": ${reason}` +\n (location ? ` (${location.file}:${location.line}:${location.column})` : ''),\n options,\n );\n this.name = 'SchemaExtractionError';\n }\n\n /**\n * 从 AST 节点构造错误(自动携带 file:line:column)\n *\n * 所有抛错点应优先使用此工厂——错误无行号时,几百行的类型文件只能靠\n * 类型名肉眼定位;解析 lib.d.ts 类型别名时还会出现错误文本与文件上下文错位\n */\n static at(node: ts.Node, typeText: string, reason: string): SchemaExtractionError {\n const sourceFile = node.getSourceFile();\n if (!sourceFile) {\n return new SchemaExtractionError(typeText, reason);\n }\n const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n return new SchemaExtractionError(typeText, reason, undefined, {\n file: sourceFile.fileName,\n line: line + 1,\n column: character + 1,\n });\n }\n}\n\n/**\n * 运行时类型描述\n *\n * 用于校验器在运行时判断值的结构是否符合声明类型。\n * 相比单纯的字符串,能描述数组元素类型、嵌套对象、联合类型等。\n */\nexport type RuntimeType =\n | { kind: 'string' }\n | { kind: 'number' }\n | { kind: 'boolean' }\n | { kind: 'bigint' }\n | { kind: 'null' }\n | { kind: 'undefined' }\n | { kind: 'any' } // 不校验\n | { kind: 'unknown' } // 不校验\n | { kind: 'literal'; value: string | number | boolean } // 字面量\n | { kind: 'array'; element: RuntimeType }\n | { kind: 'tuple'; elements: TupleElement[] }\n | { kind: 'object'; properties: PropertyType[]; catchall?: RuntimeType }\n | { kind: 'union'; members: RuntimeType[] }\n | { kind: 'date' }\n | { kind: 'record'; key: RuntimeType; value: RuntimeType }\n | { kind: 'map'; key: RuntimeType; value: RuntimeType } // Map<K,V>,JSON 序列化为 entries 数组\n | { kind: 'set'; element: RuntimeType } // Set<T>,JSON 序列化为数组\n | { kind: 'ref'; name: string }; // 命名类型引用(支持循环引用)\n\n/**\n * 元组元素类型\n *\n * - optional: 该位置元素可省略(如 `[string, number?]`)\n * - rest: 该位置是剩余元素(如 `[string, ...number[]]`),必须是数组类型,展开后逐个校验\n */\nexport interface TupleElement {\n type: RuntimeType;\n optional: boolean;\n rest: boolean;\n}\n\nexport interface PropertyType {\n name: string;\n type: RuntimeType;\n optional: boolean;\n /**\n * 字段级 JSDoc 约束标签(@max/@min/@maxLength 等)\n *\n * 来自字段 JSDoc 注释,由 generateZodSchema 转为 zod 链式调用。\n * 约束与字段类型不匹配时在提取阶段抛 SchemaExtractionError。\n */\n constraints?: TypeConstraint[];\n}\n\n/**\n * JSDoc 约束标签的运行时描述\n *\n * 由字段 JSDoc 注释提取,对应 zod schema 的链式约束方法。\n * 仅在 PropertyType.constraints 中出现,不挂在嵌套类型(array 元素、tuple 元素等)上。\n */\nexport type TypeConstraint =\n // 数值约束(仅 number 字段)\n | { kind: 'max'; value: number }\n | { kind: 'min'; value: number }\n | { kind: 'int' }\n | { kind: 'positive' }\n | { kind: 'negative' }\n | { kind: 'nonnegative' }\n | { kind: 'nonpositive' }\n // 长度约束(string 或 array 字段)\n | { kind: 'maxLength'; value: number }\n | { kind: 'minLength'; value: number }\n | { kind: 'length'; value: number }\n // 字符串格式约束(仅 string 字段)\n | { kind: 'regex'; pattern: string; flags?: string }\n | { kind: 'email' }\n | { kind: 'url' }\n | { kind: 'uuid' };\n\n/**\n * 将 TypeScript 类型节点解析为运行时类型描述\n *\n * 支持的类型:\n * - 基础类型:string / number / boolean / null / undefined / any / unknown / void\n * - bigint:不支持(HTTP/JSON 不能传输),AST 提取阶段抛 SchemaExtractionError\n * - 字面量类型:'foo' / 42 / true\n * - 数组类型:T[] / Array<T> / ReadonlyArray<T> / readonly T[]\n * - 元组类型:[string, number] / [string, number?] / [string, ...number[]] / readonly [T, U](按位置校验)\n * - 对象类型:{ name: string; age?: number }(含 readonly 字段修饰符,忽略 readonly)\n * - 联合类型:string | null\n * - 交叉类型:A & B(按对象合并处理)\n * - 引用类型:Date / 其他 interface(递归解析)\n * - 工具类型:Record<K, V> / Partial<T> / Readonly<T>(best effort)\n * - Pick<T, K> / Omit<T, K>:K 支持字面量联合、类型别名、keyof T\n *\n * readonly 是编译期约束,运行时不产生校验语义,所有 readonly 修饰符统一忽略。\n *\n * @param typeNode TypeScript 类型节点\n * @param checker 类型 checker(用于解析引用类型)\n * @param visited 防止递归循环\n * @param bindings 泛型形参绑定(形参名 → 已解析的实参类型),解析泛型声明的\n * 类型体时传入;形参名在 resolveTypeReference 入口优先命中,\n * 遮蔽同名的真实类型声明\n */\nexport function resolveTypeNode(\n typeNode: ts.TypeNode,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n bindings: Map<string, RuntimeType> = new Map(),\n): RuntimeType {\n const kind = typeNode.kind;\n\n // 基础类型\n switch (kind) {\n case ts.SyntaxKind.StringKeyword:\n return { kind: 'string' };\n case ts.SyntaxKind.NumberKeyword:\n return { kind: 'number' };\n case ts.SyntaxKind.BooleanKeyword:\n return { kind: 'boolean' };\n case ts.SyntaxKind.BigIntKeyword:\n // HTTP/JSON 不能传输 BigInt,在 AST 提取阶段就报错,避免运行时校验必然失败\n throw new SchemaExtractionError(\n typeNode.getText(),\n 'bigint 无法通过 HTTP/JSON 传输,请改用 string 或 number',\n );\n case ts.SyntaxKind.SymbolKeyword:\n // HTTP/JSON 不能传输 Symbol\n throw new SchemaExtractionError(typeNode.getText(), 'symbol 无法通过 HTTP/JSON 传输');\n case ts.SyntaxKind.NullKeyword:\n return { kind: 'null' };\n case ts.SyntaxKind.UndefinedKeyword:\n return { kind: 'undefined' };\n case ts.SyntaxKind.UnknownKeyword:\n // unknown 是唯一允许的\"不校验\"显式声明\n return { kind: 'any' };\n case ts.SyntaxKind.AnyKeyword:\n throw new SchemaExtractionError(typeNode.getText(), 'any 不支持,请使用 unknown 表示不校验');\n case ts.SyntaxKind.VoidKeyword:\n throw new SchemaExtractionError(typeNode.getText(), 'void 不支持运行时校验');\n case ts.SyntaxKind.NeverKeyword:\n throw new SchemaExtractionError(typeNode.getText(), 'never 不支持运行时校验');\n case ts.SyntaxKind.ObjectKeyword:\n throw new SchemaExtractionError(\n typeNode.getText(),\n 'object 不支持,请使用具体对象类型或 unknown',\n );\n }\n\n // 字面量类型\n if (ts.isLiteralTypeNode(typeNode)) {\n const literal = typeNode.literal;\n if (ts.isStringLiteral(literal)) {\n return { kind: 'literal', value: literal.text };\n }\n if (ts.isNumericLiteral(literal)) {\n return { kind: 'literal', value: Number(literal.text) };\n }\n if (literal.kind === ts.SyntaxKind.TrueKeyword) {\n return { kind: 'literal', value: true };\n }\n if (literal.kind === ts.SyntaxKind.FalseKeyword) {\n return { kind: 'literal', value: false };\n }\n if (literal.kind === ts.SyntaxKind.NullKeyword) {\n return { kind: 'null' };\n }\n throw new SchemaExtractionError(typeNode.getText(), '不支持的字面量类型');\n }\n\n // 数组类型:T[]\n if (ts.isArrayTypeNode(typeNode)) {\n return {\n kind: 'array',\n element: resolveTypeNode(typeNode.elementType, checker, visited, bindings),\n };\n }\n\n // 元组类型:[string, number] / [string, number?] / [string, ...number[]]\n if (ts.isTupleTypeNode(typeNode)) {\n const elements: TupleElement[] = typeNode.elements.map((e) => {\n // 剩余元素:...T\n if (ts.isRestTypeNode(e)) {\n const inner = resolveTypeNode(e.type, checker, visited, bindings);\n // ...T[] → 元素类型是 T 的数组元素\n if (inner.kind === 'array') {\n return { type: inner.element, optional: false, rest: true };\n }\n // ...T 但 T 不是数组(罕见,TS 通常要求 rest 是数组)\n return { type: inner, optional: false, rest: true };\n }\n // 命名元组成员:[name: string, age?: number]\n if (ts.isNamedTupleMember(e)) {\n return {\n type: resolveTypeNode(e.type, checker, visited, bindings),\n optional: !!e.questionToken,\n rest: false,\n };\n }\n // 可选元素:number?(无名称,有 ?)\n if (ts.isOptionalTypeNode(e)) {\n return {\n type: resolveTypeNode(e.type, checker, visited, bindings),\n optional: true,\n rest: false,\n };\n }\n // 普通元素\n return {\n type: resolveTypeNode(e, checker, visited, bindings),\n optional: false,\n rest: false,\n };\n });\n return { kind: 'tuple', elements };\n }\n\n // 联合类型:A | B\n if (ts.isUnionTypeNode(typeNode)) {\n const members = typeNode.types.map((t) => resolveTypeNode(t, checker, visited, bindings));\n return { kind: 'union', members };\n }\n\n // 交叉类型:A & B → 合并对象属性(全部成员均为 object 时)\n if (ts.isIntersectionTypeNode(typeNode)) {\n const propMap = new Map<string, PropertyType>();\n for (const t of typeNode.types) {\n const resolved = resolveTypeNode(t, checker, visited, bindings);\n if (resolved.kind !== 'object') {\n // branded 类型(string & {...})等混合交叉在运行时无对应校验物——\n // 静默丢弃非 object 成员会放宽校验,按约定显式抛错(见 AGENTS.md §6.3)\n throw new SchemaExtractionError(\n typeNode.getText(),\n `交叉类型包含非 object 成员(${resolved.kind}),运行时无法校验——branded 类型建议改用具体类型或 unknown`,\n );\n }\n for (const prop of resolved.properties) {\n const existing = propMap.get(prop.name);\n if (!existing) {\n propMap.set(prop.name, prop);\n continue;\n }\n // 同名字段:类型或可选性不一致即 TS 语义下的 never——运行时无法校验,\n // 显式抛错;一致则去重(优先保留带约束的声明,更严格)\n if (\n JSON.stringify(existing.type) !== JSON.stringify(prop.type) ||\n existing.optional !== prop.optional\n ) {\n throw SchemaExtractionError.at(\n typeNode,\n typeNode.getText(),\n `交叉类型成员的同名字段 \"${prop.name}\" 类型冲突(TS 中为 never),运行时无法校验`,\n );\n }\n if (!existing.constraints?.length && prop.constraints?.length) {\n propMap.set(prop.name, prop);\n }\n }\n }\n return { kind: 'object', properties: [...propMap.values()] };\n }\n\n // 内联对象类型:{ name: string; age?: number }\n if (ts.isTypeLiteralNode(typeNode)) {\n return resolveTypeLiteral(typeNode, checker, visited, bindings);\n }\n\n // keyof T — 用 checker 解析为字面量联合\n if (ts.isTypeOperatorNode(typeNode) && typeNode.operator === ts.SyntaxKind.KeyOfKeyword) {\n return resolveKeyOf(typeNode, checker);\n }\n\n // readonly T(如 readonly string[] / readonly [T, U])— 编译期约束,运行时不校验\n // 递归解析内部类型即可,等同于去掉 readonly 修饰符\n if (ts.isTypeOperatorNode(typeNode) && typeNode.operator === ts.SyntaxKind.ReadonlyKeyword) {\n return resolveTypeNode(typeNode.type, checker, visited, bindings);\n }\n\n // 引用类型:Date / 自定义 interface / Array<T> / Record<K,V> / Partial<T> 等\n if (ts.isTypeReferenceNode(typeNode)) {\n return resolveTypeReference(typeNode, checker, visited, bindings);\n }\n\n // interface extends 的 heritage 子句(ExpressionWithTypeArguments)\n // 构造 TypeRefLike 视图(expression → typeName),复用 resolveTypeReference\n // 解析(覆盖 Date / Array<T> / 自定义 interface 等全部分支)\n if (ts.isExpressionWithTypeArguments(typeNode)) {\n return resolveTypeReference(\n {\n getText: () => typeNode.getText(),\n typeName: typeNode.expression as ts.EntityName,\n typeArguments: typeNode.typeArguments,\n },\n checker,\n visited,\n bindings,\n );\n }\n\n // 其他无法识别的语法节点\n throw SchemaExtractionError.at(typeNode, typeNode.getText(), '不支持的类型语法');\n}\n\n/**\n * 解析内联对象类型字面量\n */\nfunction resolveTypeLiteral(\n typeNode: ts.TypeLiteralNode,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n bindings: Map<string, RuntimeType> = new Map(),\n): RuntimeType {\n const properties: PropertyType[] = [];\n // 索引签名([k: string]: T)与属性共存合法:属性保留,索引签名转为 catchall\n let catchall: RuntimeType | undefined;\n\n for (const member of typeNode.members) {\n // 方法签名/存取器:运行时 JSON 数据无方法语义,静默丢弃会弱化校验——显式抛错\n if (\n ts.isMethodSignature(member) ||\n ts.isGetAccessorDeclaration(member) ||\n ts.isSetAccessorDeclaration(member)\n ) {\n throw SchemaExtractionError.at(\n member,\n member.getText(),\n '对象类型含方法签名或存取器,运行时 JSON 数据无法校验方法——请改用具体属性类型',\n );\n }\n // 属性签名:name: string\n if (ts.isPropertySignature(member) && member.name) {\n const name = member.name.getText();\n const optional = !!member.questionToken;\n const type = member.type\n ? resolveTypeNode(member.type, checker, visited, bindings)\n : { kind: 'any' as const };\n const constraints = extractConstraintsFromJsDoc(member, name);\n validateConstraints(constraints, type, name);\n properties.push(\n constraints.length > 0 ? { name, type, optional, constraints } : { name, type, optional },\n );\n }\n // 索引签名:[key: string]: T → catchall(与属性共存时不丢弃属性)\n if (ts.isIndexSignatureDeclaration(member)) {\n catchall = member.type\n ? resolveTypeNode(member.type, checker, visited, bindings)\n : { kind: 'any' as const };\n }\n }\n\n return catchall !== undefined\n ? { kind: 'object', properties, catchall }\n : { kind: 'object', properties };\n}\n\n/**\n * 从 RuntimeType 提取字面量字符串集合\n * 用于 Pick<T, K> / Omit<T, K> 的 K 参数解析\n *\n * @returns 字面量值数组;无法解析时返回 null\n */\nfunction extractLiteralKeys(type: RuntimeType): string[] | null {\n if (type.kind === 'literal' && typeof type.value === 'string') {\n return [type.value];\n }\n if (type.kind === 'union') {\n const keys: string[] = [];\n for (const member of type.members) {\n if (member.kind === 'literal' && typeof member.value === 'string') {\n keys.push(member.value);\n } else {\n return null;\n }\n }\n return keys;\n }\n return null;\n}\n\n/**\n * 用 checker 解析 K 的最终类型,提取字面量字符串集合\n *\n * 用于 Pick<T, K> / Omit<T, K> 的 K 参数解析,覆盖 AST 无法直接拿到的场景:\n * - K 为类型别名(type Keys = 'id' | 'name')\n * - K 为 keyof T\n *\n * @returns 字面量值数组;无法解析时返回 null\n */\nfunction extractKeysFromChecker(typeNode: ts.TypeNode, checker?: ts.TypeChecker): string[] | null {\n if (!checker) return null;\n const type = checker.getTypeFromTypeNode(typeNode);\n\n if (type.isUnion()) {\n const keys: string[] = [];\n for (const member of type.types) {\n if (member.isStringLiteral()) {\n keys.push(member.value);\n } else if (member.isNumberLiteral()) {\n keys.push(String(member.value));\n } else {\n return null;\n }\n }\n return keys;\n }\n\n if (type.isStringLiteral()) {\n return [type.value];\n }\n if (type.isNumberLiteral()) {\n return [String(type.value)];\n }\n\n return null;\n}\n\n/**\n * 解析 keyof T — 用 checker 计算最终类型,返回字面量联合\n *\n * 例如 keyof { id: number; name: string } → 'id' | 'name'\n */\nfunction resolveKeyOf(typeNode: ts.TypeOperatorNode, checker?: ts.TypeChecker): RuntimeType {\n if (!checker) {\n throw new SchemaExtractionError(typeNode.getText(), 'keyof T 需要 checker 才能解析');\n }\n const type = checker.getTypeFromTypeNode(typeNode);\n\n if (type.isUnion()) {\n const members: RuntimeType[] = [];\n for (const member of type.types) {\n if (member.isStringLiteral()) {\n members.push({ kind: 'literal', value: member.value });\n } else if (member.isNumberLiteral()) {\n members.push({ kind: 'literal', value: member.value });\n } else {\n throw new SchemaExtractionError(typeNode.getText(), 'keyof T 的结果包含非字面量类型');\n }\n }\n return { kind: 'union', members };\n }\n\n if (type.isStringLiteral()) {\n return { kind: 'literal', value: type.value };\n }\n if (type.isNumberLiteral()) {\n return { kind: 'literal', value: type.value };\n }\n\n throw new SchemaExtractionError(typeNode.getText(), 'keyof T 的结果无法解析为字面量联合');\n}\n\n/**\n * resolveTypeReference 所需的最小节点结构\n *\n * TypeReferenceNode 与 interface extends 的 heritage 节点(ExpressionWithTypeArguments)\n * 均满足该结构——后者用 expression 充当 typeName,typeArguments 同名。\n */\ninterface TypeRefLike {\n getText(): string;\n typeName: ts.EntityName | ts.LeftHandSideExpression;\n typeArguments?: readonly ts.TypeNode[];\n}\n\n/**\n * 解析类型引用(Date / 自定义 interface / Array<T> / Record<K,V> 等)\n *\n * 泛型形参优先:typeName 命中 bindings 时直接返回绑定的实参类型\n * (泛型声明体内部引用形参的场景,形参遮蔽同名真实类型)。\n */\nfunction resolveTypeReference(\n typeNode: TypeRefLike,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n bindings: Map<string, RuntimeType> = new Map(),\n): RuntimeType {\n const typeName = typeNode.typeName.getText();\n\n // 泛型形参命中:直接返回已绑定的实参类型\n const bound = bindings.get(typeName);\n if (bound) {\n return bound;\n }\n\n // Date 类型\n if (typeName === 'Date') {\n return { kind: 'date' };\n }\n\n // Array<T> / ReadonlyArray<T> — 等同处理,readonly 是编译期约束\n if (\n (typeName === 'Array' || typeName === 'ReadonlyArray') &&\n typeNode.typeArguments?.length === 1\n ) {\n const [arg] = typeNode.typeArguments;\n return {\n kind: 'array',\n element: resolveTypeNode(arg!, checker, visited, bindings),\n };\n }\n\n // Record<K, V>\n if (typeName === 'Record' && typeNode.typeArguments?.length === 2) {\n const [keyArg, valueArg] = typeNode.typeArguments;\n return {\n kind: 'record',\n key: resolveTypeNode(keyArg!, checker, visited, bindings),\n value: resolveTypeNode(valueArg!, checker, visited, bindings),\n };\n }\n\n // Partial<T> / Required<T> / Readonly<T> — 解析内部类型\n if (\n (typeName === 'Partial' || typeName === 'Required' || typeName === 'Readonly') &&\n typeNode.typeArguments?.length === 1\n ) {\n const inner = resolveTypeNode(typeNode.typeArguments[0]!, checker, visited, bindings);\n if (inner.kind === 'object' && typeName === 'Partial') {\n // Partial 所有字段变可选\n return {\n kind: 'object',\n properties: inner.properties.map((p) => ({ ...p, optional: true })),\n };\n }\n if (inner.kind === 'object' && typeName === 'Required') {\n // Required 恢复所有字段为必填——静默保留 optional 会让校验弱于 TS 类型\n return {\n kind: 'object',\n properties: inner.properties.map((p) => ({ ...p, optional: false })),\n };\n }\n // Readonly:编译期约束,运行时无校验语义,原样返回(等同去掉 readonly 修饰符)\n return inner;\n }\n\n // Pick<T, K> / Omit<T, K> — 解析 T 的字段,按 K 筛选/排除\n if ((typeName === 'Pick' || typeName === 'Omit') && typeNode.typeArguments?.length === 2) {\n const innerType = resolveTypeNode(typeNode.typeArguments[0]!, checker, visited, bindings);\n if (innerType.kind !== 'object') {\n throw new SchemaExtractionError(\n typeNode.getText(),\n `${typeName} 的 T 必须是对象类型,实际为 ${innerType.kind}`,\n );\n }\n\n // K 解析顺序:AST 字面量联合 → checker 解析(覆盖类型别名 / keyof T)\n const keyTypeNode = typeNode.typeArguments[1]!;\n let keys = extractLiteralKeys(resolveTypeNode(keyTypeNode, checker, visited, bindings));\n if (keys === null) {\n keys = extractKeysFromChecker(keyTypeNode, checker);\n }\n if (keys === null) {\n throw new SchemaExtractionError(typeNode.getText(), `${typeName} 的 K 无法解析为字面量集合`);\n }\n\n const keySet = new Set(keys);\n const properties =\n typeName === 'Pick'\n ? innerType.properties.filter((p) => keySet.has(p.name))\n : innerType.properties.filter((p) => !keySet.has(p.name));\n\n return { kind: 'object', properties };\n }\n\n // Map<K, V> — JSON 序列化为 entries 数组,运行时用 z.preprocess(coerceMap, z.map(...)) 还原\n if (typeName === 'Map') {\n if (!typeNode.typeArguments || typeNode.typeArguments.length !== 2) {\n throw new SchemaExtractionError(\n typeNode.getText(),\n 'Map 必须带 2 个类型参数,如 Map<K, V>,裸 Map 不支持',\n );\n }\n const [mapKey, mapValue] = typeNode.typeArguments;\n return {\n kind: 'map',\n key: resolveTypeNode(mapKey!, checker, visited, bindings),\n value: resolveTypeNode(mapValue!, checker, visited, bindings),\n };\n }\n\n // Set<T> — JSON 序列化为数组,运行时用 z.preprocess(coerceSet, z.set(...)) 还原\n if (typeName === 'Set') {\n if (!typeNode.typeArguments || typeNode.typeArguments.length !== 1) {\n throw new SchemaExtractionError(\n typeNode.getText(),\n 'Set 必须带 1 个类型参数,如 Set<T>,裸 Set 不支持',\n );\n }\n const [setArg] = typeNode.typeArguments;\n return {\n kind: 'set',\n element: resolveTypeNode(setArg!, checker, visited, bindings),\n };\n }\n\n // WeakMap / WeakSet — 运行时无法枚举校验,仍然抛错(Map/Set 不复用其语义)\n if (typeName === 'WeakMap' || typeName === 'WeakSet') {\n throw new SchemaExtractionError(\n typeNode.getText(),\n `${typeName} 运行时无法枚举校验,请改用 Map / Set 或对象`,\n );\n }\n\n // Promise<T> — 运行时无法校验异步值\n if (typeName === 'Promise') {\n throw new SchemaExtractionError(\n typeNode.getText(),\n 'Promise 运行时无法校验,请勿在 query/body 类型中使用',\n );\n }\n\n // Function — HTTP/JSON 不能传输函数\n if (typeName === 'Function') {\n throw new SchemaExtractionError(typeNode.getText(), 'Function 无法通过 HTTP/JSON 传输');\n }\n\n // 防止递归循环:遇到已访问的类型返回 ref,支持循环引用\n if (visited.has(typeName)) {\n return { kind: 'ref', name: typeName };\n }\n visited.add(typeName);\n\n // 使用 checker 解析引用类型(interface / type 别名)\n if (checker) {\n const symbol =\n ts.isIdentifier(typeNode.typeName) || ts.isQualifiedName(typeNode.typeName)\n ? checker.getSymbolAtLocation(typeNode.typeName)\n : undefined;\n\n if (symbol) {\n const declaration = symbol.declarations?.[0];\n if (declaration) {\n // interface 声明\n if (ts.isInterfaceDeclaration(declaration)) {\n return resolveInterfaceDeclaration(\n declaration,\n checker,\n visited,\n bindings,\n typeNode.typeArguments,\n );\n }\n // type 别名声明\n if (ts.isTypeAliasDeclaration(declaration)) {\n const declBindings = bindTypeParameters(\n declaration.typeParameters,\n typeNode.typeArguments,\n bindings,\n checker,\n visited,\n typeNode,\n );\n return resolveTypeNode(declaration.type, checker, visited, declBindings);\n }\n // enum 声明 → 字面量联合\n if (ts.isEnumDeclaration(declaration)) {\n return resolveEnumDeclaration(declaration);\n }\n // import 别名(跨文件 import type):跟到真实声明\n // 业务项目用 moduleResolution: Bundler + 无扩展名导入时,\n // checker 拿到的 symbol 是 ImportSpecifier(Alias),不是真实声明\n if (ts.isImportSpecifier(declaration) || ts.isImportClause(declaration)) {\n const resolved = resolveImportAlias(\n typeNode,\n symbol,\n checker,\n visited,\n bindings,\n typeNode.typeArguments,\n );\n if (resolved) return resolved;\n }\n }\n }\n }\n\n throw new SchemaExtractionError(typeNode.getText(), `无法解析的引用类型 \"${typeName}\"`);\n}\n\n/**\n * 绑定泛型形参与实参,返回声明体解析用的 bindings\n *\n * - 实参表达式在调用方作用域解析(用 outerBindings,可引用外层形参)\n * - 默认类型在声明方作用域解析(用正在构造的 bindings,可引用前面形参,如 `<T, U = T[]>`)\n * - 实参与默认类型均缺失时抛 SchemaExtractionError(TS 本身也编译不过,防御性报错)\n * - 声明无 typeParameters 时原样返回 outerBindings\n */\nfunction bindTypeParameters(\n typeParameters: readonly ts.TypeParameterDeclaration[] | undefined,\n typeArguments: readonly ts.TypeNode[] | undefined,\n outerBindings: Map<string, RuntimeType>,\n checker: ts.TypeChecker | undefined,\n visited: Set<string>,\n errorNode: { getText(): string },\n): Map<string, RuntimeType> {\n if (!typeParameters || typeParameters.length === 0) return outerBindings;\n\n const bindings = new Map(outerBindings);\n for (let i = 0; i < typeParameters.length; i++) {\n const param = typeParameters[i];\n if (!param) continue;\n const arg = typeArguments?.[i];\n if (arg) {\n bindings.set(param.name.text, resolveTypeNode(arg, checker, visited, outerBindings));\n } else if (param.default) {\n bindings.set(param.name.text, resolveTypeNode(param.default, checker, visited, bindings));\n } else {\n throw new SchemaExtractionError(\n errorNode.getText(),\n `泛型参数 \"${param.name.text}\" 缺少类型实参(且无默认类型)`,\n );\n }\n }\n return bindings;\n}\n\n/**\n * 解析 import 别名符号,跟到真实的 interface / type alias / enum 声明\n *\n * 业务项目用 `moduleResolution: Bundler` + 无扩展名相对导入(如\n * `import type { StyleGuide } from '../../db/schema'`)时,checker 拿到的 symbol\n * 是 `ImportSpecifier`(Alias symbol),不是真实声明节点。\n *\n * 解析顺序:\n * 1. `checker.getAliasedSymbol(symbol)` — 优先路径,直接跟到真实 symbol\n * 2. 兜底:遍历 program 的所有 sourceFiles 找名为 `typeName` 的\n * `InterfaceDeclaration` / `TypeAliasDeclaration` / `EnumDeclaration`\n *\n * 兜底路径用于 `getAliasedSymbol` 在 `noEmit` 模式下未完全绑定 alias 的场景\n * (实测:即使业务项目 tsconfig 用 Bundler + program 加载了全部相关文件,\n * `getAliasedSymbol` 也可能返回 unknown / 无 declarations)。\n *\n * @returns RuntimeType 解析结果;无法解析返回 null(由调用方抛 SchemaExtractionError)\n */\nfunction resolveImportAlias(\n typeNode: TypeRefLike,\n symbol: ts.Symbol,\n checker: ts.TypeChecker,\n visited: Set<string>,\n bindings: Map<string, RuntimeType> = new Map(),\n typeArguments?: readonly ts.TypeNode[],\n): RuntimeType | null {\n const typeName = typeNode.typeName.getText();\n\n // 路径1:getAliasedSymbol 直接跟到真实 symbol\n try {\n const aliased = checker.getAliasedSymbol(symbol);\n if (aliased && aliased.declarations && aliased.declarations.length > 0) {\n const decl = aliased.declarations[0]!;\n if (ts.isInterfaceDeclaration(decl)) {\n return resolveInterfaceDeclaration(decl, checker, visited, bindings, typeArguments);\n }\n if (ts.isTypeAliasDeclaration(decl)) {\n const declBindings = bindTypeParameters(\n decl.typeParameters,\n typeArguments,\n bindings,\n checker,\n visited,\n typeNode,\n );\n return resolveTypeNode(decl.type, checker, visited, declBindings);\n }\n if (ts.isEnumDeclaration(decl)) {\n return resolveEnumDeclaration(decl);\n }\n }\n } catch {\n // getAliasedSymbol 抛错时走兜底\n }\n\n // 路径2:兜底遍历 program 的 sourceFiles 找同名声明\n // 用模块级变量 currentProgram(createProgram 时通过 setProgramContext 设置)\n // TypeChecker 类型上声明了 getProgram() 但运行时未暴露,只能通过外部传入\n const program = currentProgram;\n if (!program) return null;\n\n const allSFs = program.getSourceFiles();\n for (const sourceFile of allSFs) {\n // 跳过 lib.d.ts / node_modules / TypeScript 内置文件\n if (\n sourceFile.fileName.includes('/node_modules/') ||\n sourceFile.fileName.includes('typescript/lib/')\n ) {\n continue;\n }\n // 顶层声明查找(用独立函数返回,避免 TS 控制流分析把闭包赋值的 let 变量收窄为 never)\n const found = findTopLevelDecl(sourceFile, typeName);\n if (found) {\n if (found.kind === 'interface') {\n return resolveInterfaceDeclaration(found.node, checker, visited, bindings, typeArguments);\n }\n if (found.kind === 'typeAlias') {\n const declBindings = bindTypeParameters(\n found.node.typeParameters,\n typeArguments,\n bindings,\n checker,\n visited,\n typeNode,\n );\n return resolveTypeNode(found.node.type, checker, visited, declBindings);\n }\n if (found.kind === 'enum') {\n return resolveEnumDeclaration(found.node);\n }\n }\n }\n\n return null;\n}\n\n/**\n * 在 sourceFile 顶层查找名为 typeName 的 interface / type alias / enum 声明\n *\n * 独立为函数是因为 TS 控制流分析对\"闭包中赋值的 let 变量\"保守处理:\n * 即使 forEachChild 同步调用回调,分析器仍认为赋值可能未发生,\n * 导致外层读取时变量被收窄为初始值类型(null)。\n * 用函数返回值(带显式返回类型)绕过此限制。\n */\nexport type TopLevelDecl =\n | { kind: 'interface'; node: ts.InterfaceDeclaration }\n | { kind: 'typeAlias'; node: ts.TypeAliasDeclaration }\n | { kind: 'enum'; node: ts.EnumDeclaration };\n\nexport function findTopLevelDecl(sourceFile: ts.SourceFile, typeName: string): TopLevelDecl | null {\n let found: TopLevelDecl | null = null;\n ts.forEachChild(sourceFile, (node) => {\n if (found) return;\n if (ts.isInterfaceDeclaration(node) && node.name.text === typeName) {\n found = { kind: 'interface', node };\n } else if (ts.isTypeAliasDeclaration(node) && node.name.text === typeName) {\n found = { kind: 'typeAlias', node };\n } else if (ts.isEnumDeclaration(node) && node.name.text === typeName) {\n found = { kind: 'enum', node };\n }\n });\n return found;\n}\n\n/**\n * 解析 enum 声明为字面量联合类型\n *\n * - 字符串枚举:`enum Role { Admin = 'admin' }` → `'admin' | ...`\n * - 数值枚举:`enum Code { OK = 200 }` → `200 | ...`\n * - 隐式数值枚举:`enum Dir { Up, Down }` → `0 | 1 | ...`\n *\n * HTTP 视角:枚举值在 JSON 中是普通 string/number,按字面量联合校验即可。\n */\nexport function resolveEnumDeclaration(node: ts.EnumDeclaration): RuntimeType {\n const members: RuntimeType[] = [];\n let nextNumericValue = 0;\n\n for (const member of node.members) {\n if (member.initializer) {\n if (ts.isStringLiteral(member.initializer)) {\n members.push({ kind: 'literal', value: member.initializer.text });\n } else if (ts.isNumericLiteral(member.initializer)) {\n const num = Number(member.initializer.text);\n members.push({ kind: 'literal', value: num });\n nextNumericValue = num + 1;\n } else {\n throw new SchemaExtractionError(\n node.name.text,\n `enum 成员 \"${member.name.getText()}\" 的初始化值类型不支持,仅支持 string/number 字面量`,\n );\n }\n } else {\n // 隐式数值枚举:无初始化值,使用递增整数\n members.push({ kind: 'literal', value: nextNumericValue });\n nextNumericValue++;\n }\n }\n\n return { kind: 'union', members };\n}\n\n/**\n * 解析 interface 声明(含继承与泛型形参绑定)\n *\n * @param node interface 声明节点\n * @param checker 类型 checker\n * @param visited 防止递归循环\n * @param outerBindings 外层泛型形参绑定(调用方作用域)\n * @param typeArguments 本次引用携带的类型实参(如 `Base<string>` 的 [string]),\n * 与 node.typeParameters 按位置配对构造声明体 bindings\n */\nexport function resolveInterfaceDeclaration(\n node: ts.InterfaceDeclaration,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n outerBindings: Map<string, RuntimeType> = new Map(),\n typeArguments?: readonly ts.TypeNode[],\n): RuntimeType {\n const properties: PropertyType[] = [];\n const propMap = new Map<string, PropertyType>();\n // 索引签名([k: string]: T)与属性共存合法:属性保留,索引签名转为 catchall\n let catchall: RuntimeType | undefined;\n\n // 绑定本声明的泛型形参(声明体与 heritage 均在该作用域下解析)\n const bindings = bindTypeParameters(\n node.typeParameters,\n typeArguments,\n outerBindings,\n checker,\n visited,\n node,\n );\n\n // 处理继承的父接口\n for (const heritageClause of node.heritageClauses ?? []) {\n if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword) {\n for (const expr of heritageClause.types) {\n const parentType = resolveTypeNode(expr, checker, visited, bindings);\n if (parentType.kind === 'object') {\n for (const prop of parentType.properties) {\n propMap.set(prop.name, prop);\n }\n }\n }\n }\n }\n\n // 处理自身成员(覆盖继承的同名字段)\n for (const member of node.members) {\n // 方法签名/存取器:运行时 JSON 数据无方法语义,静默丢弃会弱化校验——显式抛错\n if (\n ts.isMethodSignature(member) ||\n ts.isGetAccessorDeclaration(member) ||\n ts.isSetAccessorDeclaration(member)\n ) {\n throw SchemaExtractionError.at(\n member,\n member.getText(),\n '接口含方法签名或存取器,运行时 JSON 数据无法校验方法——请改用具体属性类型',\n );\n }\n if (ts.isPropertySignature(member) && member.name) {\n const name = member.name.getText();\n const optional = !!member.questionToken;\n const type = member.type\n ? resolveTypeNode(member.type, checker, visited, bindings)\n : { kind: 'any' as const };\n const constraints = extractConstraintsFromJsDoc(member, name);\n validateConstraints(constraints, type, name);\n propMap.set(\n name,\n constraints.length > 0 ? { name, type, optional, constraints } : { name, type, optional },\n );\n }\n // 索引签名 → catchall(与属性/继承属性共存时不丢弃)\n if (ts.isIndexSignatureDeclaration(member)) {\n catchall = member.type\n ? resolveTypeNode(member.type, checker, visited, bindings)\n : { kind: 'any' as const };\n }\n }\n\n for (const prop of propMap.values()) {\n properties.push(prop);\n }\n\n return catchall !== undefined\n ? { kind: 'object', properties, catchall }\n : { kind: 'object', properties };\n}\n\n/**\n * 数值约束标签集合(仅 number 字段)\n */\nconst NUMBER_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'max',\n 'min',\n 'int',\n 'positive',\n 'negative',\n 'nonnegative',\n 'nonpositive',\n]);\n\n/**\n * 长度约束标签集合(string 或 array 字段)\n */\nconst LENGTH_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'maxLength',\n 'minLength',\n 'length',\n]);\n\n/**\n * 字符串格式约束标签集合(仅 string 字段)\n */\nconst STRING_FORMAT_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'regex',\n 'email',\n 'url',\n 'uuid',\n]);\n\n/**\n * 从字段的 JSDoc 注释中提取约束标签\n *\n * 解析 `/** ... *\\/` 块注释中的 `@max`/`@min`/`@maxLength` 等标签。\n * 行内注释 `// @max 100` 不识别。\n *\n * @param node 字段节点(PropertySignature)\n * @param fieldName 字段名,用于错误信息\n * @returns 约束数组(无约束时返回空数组)\n */\nfunction extractConstraintsFromJsDoc(\n node: ts.PropertySignature,\n fieldName: string,\n): TypeConstraint[] {\n // ts.getJSDocCommentsAndTags 返回 JSDoc/JsDocTag 数组\n const jsDocs = ts\n .getJSDocCommentsAndTags(node)\n .filter((entry): entry is ts.JSDoc => ts.isJSDoc(entry));\n\n if (jsDocs.length === 0) return [];\n\n const constraints: TypeConstraint[] = [];\n\n for (const jsDoc of jsDocs) {\n if (!jsDoc.tags) continue;\n for (const tag of jsDoc.tags) {\n const constraint = parseJsDocTag(tag, fieldName);\n if (constraint) constraints.push(constraint);\n }\n }\n\n return constraints;\n}\n\n/**\n * 提取 JSDoc 标签后的文本注释\n *\n * `tag.comment` 类型为 `string | NodeArray<JSDocComment> | undefined`,\n * 此处仅取纯文本形式(约束标签后跟的均为字面量值,不会包含 JSDocLink 等节点)。\n *\n * @returns 标签后的文本;无文本或非纯字符串时返回 undefined\n */\nfunction getTagCommentText(tag: ts.JSDocTag): string | undefined {\n const comment = tag.comment;\n if (typeof comment === 'string') return comment;\n return undefined;\n}\n\n/**\n * 解析单个 JSDoc 标签为约束\n *\n * @param tag JSDocTag 节点\n * @param fieldName 字段名,用于错误信息\n * @returns 约束对象;非约束标签返回 null\n */\nfunction parseJsDocTag(tag: ts.JSDocTag, fieldName: string): TypeConstraint | null {\n const tagName = tag.tagName.text;\n\n switch (tagName) {\n // 数值约束(带值)\n case 'max':\n case 'min': {\n const value = parseNumberValue(tag, fieldName, tagName);\n return { kind: tagName, value };\n }\n\n // 长度约束(带值)\n case 'maxLength':\n case 'minLength':\n case 'length': {\n const value = parseNumberValue(tag, fieldName, tagName);\n return { kind: tagName, value };\n }\n\n // 正则约束(带 /pattern/flags 值)\n case 'regex':\n case 'pattern': {\n const text = getTagCommentText(tag);\n if (!text) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签需要 /pattern/flags 形式的值`);\n }\n const regex = parseRegexLiteral(text.trim(), fieldName);\n return { kind: 'regex', pattern: regex.pattern, flags: regex.flags };\n }\n\n // 数值约束(无值)\n case 'int':\n case 'positive':\n case 'negative':\n case 'nonnegative':\n case 'nonpositive':\n return { kind: tagName };\n\n // 字符串格式约束(无值)\n case 'email':\n case 'url':\n case 'uuid':\n return { kind: tagName };\n\n default:\n return null;\n }\n}\n\n/**\n * 解析标签后的数字值\n *\n * @param tag JSDocTag 节点\n * @param fieldName 字段名,用于错误信息\n * @param tagName 标签名,用于错误信息\n */\nfunction parseNumberValue(tag: ts.JSDocTag, fieldName: string, tagName: string): number {\n const text = getTagCommentText(tag);\n if (!text) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签需要一个数字值`);\n }\n const trimmed = text.trim();\n const num = Number(trimmed);\n if (!Number.isFinite(num)) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签的值 \"${trimmed}\" 不是有效数字`);\n }\n return num;\n}\n\n/**\n * 解析 /pattern/flags 形式的正则字面量\n *\n * @param text 标签后的文本,如 \"/^[a-z]+$/i\"\n * @param fieldName 字段名,用于错误信息\n */\nfunction parseRegexLiteral(text: string, fieldName: string): { pattern: string; flags?: string } {\n // 必须以 / 开头,匹配结尾的 / 和可选 flags\n const match = /^\\/(.+)\\/([gimsuy]*)$/.exec(text);\n if (!match) {\n throw new SchemaExtractionError(fieldName, `正则值 \"${text}\" 不是 /pattern/flags 形式`);\n }\n const [, pattern, flags] = match;\n if (!pattern) {\n throw new SchemaExtractionError(fieldName, `正则值 \"${text}\" 的 pattern 部分为空`);\n }\n return flags ? { pattern, flags } : { pattern };\n}\n\n/**\n * 校验约束与字段类型是否匹配\n *\n * 不匹配时抛 SchemaExtractionError(复用现有错误类型)。\n *\n * @param constraints 约束数组\n * @param type 字段类型\n * @param fieldName 字段名,用于错误信息\n */\nfunction validateConstraints(\n constraints: TypeConstraint[],\n type: RuntimeType,\n fieldName: string,\n): void {\n if (constraints.length === 0) return;\n\n for (const constraint of constraints) {\n const kind = constraint.kind;\n\n if (NUMBER_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'number') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 number 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n\n if (LENGTH_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'string' && type.kind !== 'array') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 string 或 array 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n\n if (STRING_FORMAT_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'string') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 string 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n }\n}\n","/**\n * 根据 HTTP 方法判断主输入类型\n *\n * - GET / DELETE / HEAD:query(URL 查询参数)\n * - POST / PUT / PATCH:body(请求体)\n *\n * 注意:所有方法都可能同时有 query 和 body,\n * 这里返回的是\"主输入\"(用于校验和注入)。\n * DELETE 请求也支持 body(见 resolveInput.ts)。\n */\nexport function getInputTypeForMethod(method: string): 'query' | 'body' {\n const upper = method.toUpperCase();\n if (upper === 'GET' || upper === 'DELETE' || upper === 'HEAD') {\n return 'query';\n }\n return 'body';\n}\n\n/**\n * 判断方法是否有请求体\n *\n * POST / PUT / PATCH / DELETE 可能有 body\n * GET / HEAD 不应该有 body\n */\nexport function hasBody(method: string): boolean {\n const upper = method.toUpperCase();\n return upper === 'POST' || upper === 'PUT' || upper === 'PATCH' || upper === 'DELETE';\n}\n","/**\n * 根据 HTTP 方法和输入类型生成 schema 类型名\n * GET + query -> GETQuery\n * POST + body -> POSTBody\n * GET + params -> GETParams\n */\nexport function getSchemaName(method: string, inputType: 'query' | 'body' | 'params'): string {\n return `${method.toUpperCase()}${inputType.charAt(0).toUpperCase() + inputType.slice(1)}`;\n}\n","import ts from 'typescript';\nimport type { InjectionType } from './resolveInjection';\nimport { PARAM_TYPE_MAP } from './resolveInjection';\n\n/**\n * 参数元数据\n */\nexport interface ParamMeta {\n name: string; // 参数名\n type: InjectionType; // 注入类型\n typeName?: string; // 引用类型名(如 'Query')\n schema?: PropertyInfo[]; // 类型结构(内联类型时)\n}\n\n/**\n * 属性信息\n */\nexport interface PropertyInfo {\n name: string;\n type: string;\n optional: boolean;\n}\n\n/**\n * 注入元数据\n */\nexport interface InjectionMeta {\n params: ParamMeta[];\n}\n\n/**\n * AST 分析 handler 函数,提取参数注入元数据\n *\n * @param code handler 源码字符串\n * @param functionName 函数导出名(如 'GET')\n */\nexport function analyzeInjection(code: string, functionName: string): InjectionMeta {\n const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);\n return analyzeInjectionInSourceFile(sourceFile, functionName);\n}\n\n/**\n * 从已有 SourceFile 分析 handler 函数的参数注入元数据\n *\n * 与 analyzeInjection 语义一致,但接受 AST 节点而非源码字符串——\n * 调用方(如 collectRouteSchemaSources)的 program 中已有解析好的 SourceFile,\n * 逐方法重新 parse 同一文件字符串是纯浪费(同文件 N 个方法 = N 次全量重复解析)。\n *\n * @param sourceFile 已解析的 AST\n * @param functionName 函数导出名(如 'GET')\n */\nexport function analyzeInjectionInSourceFile(\n sourceFile: ts.SourceFile,\n functionName: string,\n): InjectionMeta {\n const params: ParamMeta[] = [];\n\n // 遍历 AST 查找目标函数\n ts.forEachChild(sourceFile, (node) => {\n if (ts.isFunctionDeclaration(node) && node.name?.text === functionName) {\n // 分析参数\n for (const param of node.parameters) {\n const paramMeta = analyzeParam(param, sourceFile);\n params.push(paramMeta);\n }\n }\n });\n\n return { params };\n}\n\n/**\n * 分析单个参数\n */\nfunction analyzeParam(param: ts.ParameterDeclaration, sourceFile: ts.SourceFile): ParamMeta {\n const name = param.name.getText(sourceFile);\n const type = PARAM_TYPE_MAP[name] || 'unknown';\n\n const result: ParamMeta = { name, type };\n\n // 分析类型\n if (param.type) {\n if (ts.isTypeReferenceNode(param.type)) {\n // 引用类型:Query, Body 等\n result.typeName = param.type.typeName.getText(sourceFile);\n } else if (ts.isTypeLiteralNode(param.type)) {\n // 内联类型:{ page: number }\n result.schema = extractSchema(param.type, sourceFile);\n }\n }\n\n return result;\n}\n\n/**\n * 从类型字面量提取 schema\n */\nfunction extractSchema(typeNode: ts.TypeLiteralNode, sourceFile: ts.SourceFile): PropertyInfo[] {\n const schema: PropertyInfo[] = [];\n\n for (const member of typeNode.members) {\n if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {\n const propName = member.name.text;\n const optional = !!member.questionToken;\n const propType = member.type?.getText(sourceFile) || 'unknown';\n\n schema.push({\n name: propName,\n type: propType,\n optional,\n });\n }\n }\n\n return schema;\n}\n","import { createPrograms } from '../ast/createProgram';\nimport {\n createLazyTypeResolver,\n type HandlerTypeInfo,\n type LazyTypeResolver,\n} from '../ast/extractHandlerTypes';\nimport { getInputTypeForMethod } from '../runtime/inputType';\nimport { getSchemaName } from '../validator/schemaName';\nimport { analyzeInjectionInSourceFile } from '../injection/analyzeInjection';\nimport type { RouteManifest } from '../router/routeTypes';\nimport path from 'node:path';\n\n/**\n * 单个路由的 schema 提取结果\n *\n * key 使用 urlPath(如 '/api/hello')而非 filePath,因为 urlPath 在 dev/prod 完全一致,\n * 无需 remapManifestKeys 桥接 .ts/.js 路径差异。\n */\nexport interface RouteSchemaSource {\n /** 路由 URL 路径(如 '/api/hello'),作为 schema key */\n urlPath: string;\n /** 源文件绝对路径(用于 generateSchemaFiles 按文件分组生成 zod.js) */\n filePath: string;\n schemaName: string;\n typeInfo: HandlerTypeInfo | null;\n /**\n * 是否对 number/boolean 字段生成 z.preprocess 字符串转换(coerce)。\n *\n * - query/params:始终 coerce=true(URL 来源均为 string)\n * - body:始终 coerce=false(JSON 解析已是天然 JS 类型)\n * - form:coerce=true(form-urlencoded 来源均为 string),由本函数在提取时\n * 检测到 handler 声明 `form` 参数时显式设置。schema 名仍为 `POSTBody`\n * (与 body 共享运行时 schema key),运行时 validateInput 无需感知 form/body 差异。\n *\n * 未设置时由 generateSchemaFileSource 回退到 schemaName 后缀正则推断(Query/Params → true)。\n */\n coerce?: boolean;\n}\n\n/**\n * 从路由清单收集 schema 提取所需的原始数据\n *\n * dev 和 prd 共享的核心提取流程:\n * 1. 按文件分组遍历路由\n * 2. 批量共享 Program(同一次提取只按文件组创建少量 Program)\n * 3. 对每个路由用 analyzeInjectionInSourceFile 定位入口参数类型,\n * extractTypeInfo 解析入口类型(入口类型必须严格解析,失败抛 SchemaExtractionError)\n * 4. 返回按文件分组的惰性类型解析器——generateSchemaFiles 生成 zod.js 时\n * 遇到 ref(同文件循环引用)按需解析并缓存\n *\n * 惰性语义:与路由无关的类型(未被任何入口类型引用)不会被解析——文件里\n * 存在一个含不支持语法的无关类型不再拖垮整个 build/reload。\n */\nexport function collectRouteSchemaSources(\n routes: RouteManifest,\n rootDir?: string,\n): {\n sources: RouteSchemaSource[];\n /** 按文件分组的惰性类型解析器(generateSchemaFiles 解析 ref 用) */\n resolversByFile: Map<string, LazyTypeResolver>;\n} {\n // 按文件分组收集方法(去重)\n // key 是文件绝对路径(createProgram 需要),但 schema key 用 urlPath\n const methodsByFile = new Map<string, { urlPath: string; methods: Set<string> }>();\n for (const route of routes) {\n const filePath = rootDir ? path.resolve(rootDir, route.filePath) : route.filePath;\n let entry = methodsByFile.get(filePath);\n if (!entry) {\n entry = { urlPath: route.urlPath, methods: new Set() };\n methodsByFile.set(filePath, entry);\n }\n entry.methods.add(route.method);\n }\n\n // 批量共享 Program:同一次提取只创建一个 Program,避免逐文件全量解析\n const programByFile = createPrograms([...methodsByFile.keys()]);\n\n // 每个文件一个惰性解析器:入口类型立即解析,ref 按需解析(缓存幂等)\n const resolversByFile = new Map<string, LazyTypeResolver>();\n for (const filePath of methodsByFile.keys()) {\n resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath)!, filePath));\n }\n\n // 每个文件提取 schema(key 用 urlPath)\n const sources: RouteSchemaSource[] = [];\n for (const [filePath, entry] of methodsByFile) {\n const program = programByFile.get(filePath)!;\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) continue;\n const resolver = resolversByFile.get(filePath)!;\n for (const method of entry.methods) {\n const inputType = getInputTypeForMethod(method);\n const schemaName = getSchemaName(method, inputType);\n // 复用 program 已解析的 SourceFile,逐方法零重复 parse\n const meta = analyzeInjectionInSourceFile(sourceFile, method);\n // POST/PUT/PATCH(inputType='body'):优先找 body 参数,找不到再找 form 参数。\n // form 与 body 共享 schema 名(POSTBody),运行时 validateInput 仍按 POSTBodySchema 查找;\n // 差异仅在校验:form 声明时通过 source.coerce=true 显式覆盖(form 值均为 string,\n // 需 z.preprocess 转换 number/boolean 字段)。\n const param =\n meta.params.find((p) => p.type === inputType) ??\n (inputType === 'body' ? meta.params.find((p) => p.type === 'form') : undefined);\n const isForm = param?.type === 'form';\n // 入口类型:必须严格解析(失败抛 SchemaExtractionError,带文件/类型上下文)\n const typeInfo = param?.typeName ? resolver.resolve(param.typeName) : null;\n sources.push({\n urlPath: entry.urlPath,\n filePath,\n schemaName,\n typeInfo,\n coerce: isForm || undefined,\n });\n }\n }\n\n return { sources, resolversByFile };\n}\n","import {\n SchemaExtractionError,\n type RuntimeType,\n type TupleElement,\n type TypeConstraint,\n} from './resolveTypeNode';\nimport type { HandlerTypeInfo } from './extractHandlerTypes';\n\n/**\n * 类型解析器:根据类型名获取 RuntimeType\n *\n * 用于解析循环引用中的 ref。调用方负责提供(通常从 AST 提取)。\n */\nexport type TypeResolver = (name: string) => RuntimeType | undefined;\n\n/**\n * 代码生成上下文\n *\n * 跟踪已收集的命名类型,用于生成 z.lazy 声明。\n */\nclass CodeGenContext {\n /** 命名类型集合:name → RuntimeType */\n readonly namedTypes = new Map<string, RuntimeType>();\n /** 类型解析器(用于解析 ref 的实际类型) */\n readonly resolveType: TypeResolver;\n /** 入口类型原始名(typeInfo.name,用于识别入口类型的自引用) */\n entryTypeName = '';\n /** 入口类型导出名(exportName,自引用时用此名生成变量名) */\n entryExportName = '';\n /**\n * 是否生成 coerce 逻辑(query/params 场景,URL 来源均为 string)\n *\n * true 时为 number/boolean 字段包 z.preprocess,把合法的字符串转成对应类型。\n * 嵌套类型(array/object/tuple/union 等)的元素递归处理。\n */\n coerce = false;\n\n constructor(resolveType: TypeResolver) {\n this.resolveType = resolveType;\n }\n}\n\n/**\n * 从 RuntimeType 收集所有命名类型(ref)\n *\n * 递归遍历 RuntimeType,遇到 ref 时解析并加入 namedTypes。\n * 已收集的类型不重复处理(防止循环引用无限递归)。\n */\nexport function collectNamedTypes(type: RuntimeType, ctx: CodeGenContext): void {\n switch (type.kind) {\n case 'string':\n case 'number':\n case 'boolean':\n case 'bigint':\n case 'null':\n case 'undefined':\n case 'any':\n case 'unknown':\n case 'literal':\n case 'date':\n return;\n\n case 'array':\n collectNamedTypes(type.element, ctx);\n return;\n\n case 'tuple':\n for (const el of type.elements) {\n collectNamedTypes(el.type, ctx);\n }\n return;\n\n case 'object':\n for (const prop of type.properties) {\n collectNamedTypes(prop.type, ctx);\n }\n return;\n\n case 'union':\n for (const member of type.members) {\n collectNamedTypes(member, ctx);\n }\n return;\n\n case 'record':\n collectNamedTypes(type.key, ctx);\n collectNamedTypes(type.value, ctx);\n return;\n\n case 'map':\n collectNamedTypes(type.key, ctx);\n collectNamedTypes(type.value, ctx);\n return;\n\n case 'set':\n collectNamedTypes(type.element, ctx);\n return;\n\n case 'ref': {\n if (ctx.namedTypes.has(type.name)) return;\n // 先占位,防止循环引用无限递归\n ctx.namedTypes.set(type.name, { kind: 'any' });\n const resolved = ctx.resolveType(type.name);\n if (resolved) {\n ctx.namedTypes.set(type.name, resolved);\n collectNamedTypes(resolved, ctx);\n return;\n }\n // ref 只在 AST 提取阶段遇到真实 TS 类型引用时产生(首现内联、二次标 ref),\n // 声明必然存在于某个源文件中;解析不到说明查找能力不足(如命名空间内声明),\n // 按框架约定显式抛错而非静默降级为 z.unknown()(AGENTS.md §6.3)\n throw new SchemaExtractionError(\n type.name,\n `无法解析命名类型引用 \"${type.name}\"(handler 文件与 program 源文件中均未找到同名顶层声明)`,\n );\n }\n }\n}\n\n/**\n * RuntimeType → zod 表达式字符串(不含声明)\n *\n * @param type 类型描述\n * @param ctx 代码生成上下文\n * @param constraints 字段级约束(仅 object 字段传入,影响最外层 zod 链)\n */\nexport function runtimeTypeToZodExpression(\n type: RuntimeType,\n ctx: CodeGenContext,\n constraints?: TypeConstraint[],\n): string {\n const expr = baseExpression(type, ctx);\n // 字段级约束链(@max/@min/@regex 等)追加到基础表达式后\n // coerce 模式下约束需作用在 preprocess 内部的 z.X() 上,故先应用约束再包裹 preprocess\n const withConstraints =\n constraints && constraints.length > 0 ? applyConstraints(expr, constraints, type.kind) : expr;\n // coerce 模式下,number/boolean 在外层包 z.preprocess\n // 嵌套类型(array/object/tuple/union 等)内部已递归处理,外层不再包裹\n if (ctx.coerce && (type.kind === 'number' || type.kind === 'boolean')) {\n return wrapCoercePreprocess(type.kind, withConstraints);\n }\n // coerce 模式下,数字/布尔字面量同样需要字符串转换:query 声明 `status: 1 | 2`\n // 时 URL 传来 \"1\"(string),裸 z.literal(1) 必然校验失败。union 成员经本函数\n // 递归生成,字面量成员逐个包裹(string 字面量天然命中,无需转换)\n if (\n ctx.coerce &&\n type.kind === 'literal' &&\n (typeof type.value === 'number' || typeof type.value === 'boolean')\n ) {\n return wrapCoercePreprocess(\n typeof type.value === 'number' ? 'number' : 'boolean',\n withConstraints,\n );\n }\n return withConstraints;\n}\n\n/**\n * 在 zod 表达式上追加约束链\n *\n * 约束与字段类型的匹配在 AST 提取阶段已校验,这里直接生成链式调用。\n *\n * coerce 模式下调用方应先 applyConstraints 再 wrapCoercePreprocess,\n * 使约束作用在 preprocess 内部的 z.X() 上,而非 preprocess 外壳。\n *\n * @param baseExpr 基础 zod 表达式(未包裹 preprocess)\n * @param constraints 约束数组\n * @param typeKind 字段类型 kind\n */\nfunction applyConstraints(\n baseExpr: string,\n constraints: TypeConstraint[],\n typeKind: RuntimeType['kind'],\n): string {\n const suffix = constraints.map((c) => constraintToZodChain(c, typeKind)).join('');\n return `${baseExpr}${suffix}`;\n}\n\n/**\n * 单个约束 → zod 链式方法字符串\n *\n * @param constraint 约束\n * @param typeKind 字段类型 kind(用于区分 string/array 的 max/min 含义)\n */\nfunction constraintToZodChain(constraint: TypeConstraint, _typeKind: RuntimeType['kind']): string {\n switch (constraint.kind) {\n case 'max':\n return `.max(${constraint.value})`;\n case 'min':\n return `.min(${constraint.value})`;\n case 'int':\n return '.int()';\n case 'positive':\n return '.positive()';\n case 'negative':\n return '.negative()';\n case 'nonnegative':\n return '.nonnegative()';\n case 'nonpositive':\n return '.nonpositive()';\n case 'maxLength':\n return `.max(${constraint.value})`;\n case 'minLength':\n return `.min(${constraint.value})`;\n case 'length':\n return `.length(${constraint.value})`;\n case 'regex': {\n const flags = constraint.flags ?? '';\n // 用 new RegExp 构造,避免 pattern 中含 / 字符导致的字面量语法错误\n return `.regex(new RegExp(${JSON.stringify(constraint.pattern)}${flags ? `, ${JSON.stringify(flags)}` : ''}))`;\n }\n case 'email':\n return '.email()';\n case 'url':\n return '.url()';\n case 'uuid':\n return '.uuid()';\n }\n}\n\n/**\n * 生成基础 zod 表达式(不含 coerce 包裹)\n */\nfunction baseExpression(type: RuntimeType, ctx: CodeGenContext): string {\n switch (type.kind) {\n case 'string':\n return 'z.string()';\n case 'number':\n return 'z.number()';\n case 'boolean':\n return 'z.boolean()';\n case 'bigint':\n // AST 提取阶段已抛错,这里不会到达\n return 'z.never()';\n case 'null':\n return 'z.null()';\n case 'undefined':\n return 'z.undefined()';\n case 'any':\n case 'unknown':\n return 'z.unknown()';\n case 'literal':\n return `z.literal(${JSON.stringify(type.value)})`;\n case 'array':\n return `z.array(${runtimeTypeToZodExpression(type.element, ctx)})`;\n case 'tuple':\n return generateTupleExpression(type.elements, ctx);\n case 'object':\n return generateObjectExpression(type, ctx);\n case 'union':\n return generateUnionExpression(type.members, ctx);\n case 'date':\n // z.preprocess 限制只接受 string/Date:string → new Date(string),其他原样传入\n // z.coerce.date() 会把 number 也转为 Date(new Date(123)),不符合需求\n // date 类型本身已用 preprocess,coerce 模式下无需再包一层\n return 'z.preprocess((v) => (typeof v === \"string\" ? new Date(v) : v), z.date())';\n case 'record':\n return `z.record(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)})`;\n case 'map':\n // Map<K,V> — JSON 序列化为 entries 数组,coerceMap 还原为 Map 实例后再交给 z.map 校验\n // coerce=false(body)场景也需要 preprocess:JSON.parse 出来是数组,不是 Map 实例\n return `z.preprocess(coerceMap, z.map(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)}))`;\n case 'set':\n // Set<T> — JSON 序列化为数组,coerceSet 还原为 Set 实例后再交给 z.set 校验\n // coerce=false(body)场景也需要 preprocess:JSON.parse 出来是数组,不是 Set 实例\n return `z.preprocess(coerceSet, z.set(${runtimeTypeToZodExpression(type.element, ctx)}))`;\n case 'ref':\n // ref 引用命名类型,直接用 NameSchema 变量\n // 入口类型的自引用需用 exportName(exportName 可能与 typeInfo.name 不同)\n if (type.name === ctx.entryTypeName) {\n return `${ctx.entryExportName}Schema`;\n }\n return `${type.name}Schema`;\n }\n}\n\n/**\n * coerceNumber 公用函数源码(ESM export 格式)\n *\n * 用于 query/params 的 string → number 转换:\n * - 合法数字字符串(\"1\"、\"3.14\")转为 number\n * - 空串/NaN 保留原值,让 z.number() 报错(避免 Number(\"\") = 0 的陷阱)\n *\n * 写入 dist 根部的 faapi-helpers.js,各 zod.js 通过相对路径 import 复用。\n */\nexport const COERCE_NUMBER_HELPER =\n 'export const coerceNumber = (v) => typeof v === \"string\" && v.trim() !== \"\" && !isNaN(Number(v)) ? Number(v) : v;';\n\n/**\n * coerceBoolean 公用函数源码(ESM export 格式)\n *\n * 用于 query/params 的 string → boolean 转换:\n * - 'true'/'1' → true\n * - 'false'/'0' → false\n * - 其他值保留原值,让 z.boolean() 报错\n *\n * 写入 dist 根部的 faapi-helpers.js,各 zod.js 通过相对路径 import 复用。\n */\nexport const COERCE_BOOLEAN_HELPER =\n 'export const coerceBoolean = (v) => {\\n' +\n ' const lower = typeof v === \"string\" ? v.toLowerCase() : v;\\n' +\n ' return lower === \"true\" || lower === \"1\" ? true : lower === \"false\" || lower === \"0\" ? false : v;\\n' +\n '};';\n\n/**\n * coerceMap 公用函数源码(ESM export 格式)\n *\n * 用于 Map<K,V> 的 JSON 还原:JSON 序列化为 entries 数组,JSON.parse 后还原为 Map 实例\n * - entries 数组 [[\"k\",v],...] → new Map(entries)\n * - Map 实例 → 原样返回\n * - 普通对象 { k: v } → new Map(Object.entries(v))\n * - 其他值保留原值,让 z.map() 报错\n *\n * 与 coerceNumber/coerceBoolean 不同,coerceMap 在 coerce=false(body 场景)下也使用:\n * JSON.parse 出来的是数组/对象,不是 Map 实例,必须还原才能交给 z.map() 校验。\n */\nexport const COERCE_MAP_HELPER =\n 'export const coerceMap = (v) => Array.isArray(v) ? new Map(v) : v instanceof Map ? v : (v && typeof v === \"object\" ? new Map(Object.entries(v)) : v);';\n\n/**\n * coerceSet 公用函数源码(ESM export 格式)\n *\n * 用于 Set<T> 的 JSON 还原:JSON 序列化为数组,JSON.parse 后还原为 Set 实例\n * - Set 实例 → 原样返回\n * - 数组 [item,...] → new Set(array)\n * - 其他值保留原值,让 z.set() 报错\n *\n * 与 coerceNumber/coerceBoolean 不同,coerceSet 在 coerce=false(body 场景)下也使用:\n * JSON.parse 出来的是数组,不是 Set 实例,必须还原才能交给 z.set() 校验。\n */\nexport const COERCE_SET_HELPER =\n 'export const coerceSet = (v) => v instanceof Set ? v : (Array.isArray(v) ? new Set(v) : v);';\n\n/**\n * faapi-helpers.js 文件名(生成在 dist 根部)\n */\nexport const HELPERS_FILENAME = 'faapi-helpers.js';\n\n/**\n * 生成 faapi-helpers.js 文件源码\n *\n * 包含 coerceNumber / coerceBoolean / coerceMap / coerceSet 四个公用函数(ESM export)。\n * 由 generateSchemaFiles 按需生成:query/params 场景的 number/boolean 字段触发 coerceNumber/coerceBoolean;\n * Map/Set 字段(query/params 与 body 均包含)触发 coerceMap/coerceSet。\n */\nexport function generateHelpersFileSource(): string {\n return [\n '// faapi-helpers.js — faapi 自动生成的公用函数(请勿手动编辑)',\n COERCE_NUMBER_HELPER,\n COERCE_BOOLEAN_HELPER,\n COERCE_MAP_HELPER,\n COERCE_SET_HELPER,\n '',\n ].join('\\n');\n}\n\n/**\n * 检测代码是否引用了 coerceNumber / coerceBoolean / coerceMap / coerceSet 变量\n *\n * 用于 generateSchemaFiles 判断是否需要生成 faapi-helpers.js。\n * 注意:Map/Set 在 coerce=false(body 场景)下也会引用 coerceMap/coerceSet,\n * 故仅凭此检测即可覆盖所有需要生成 helpers 的场景。\n */\nexport function usesCoerceHelpers(code: string): boolean {\n return (\n code.includes('coerceNumber') ||\n code.includes('coerceBoolean') ||\n code.includes('coerceMap') ||\n code.includes('coerceSet')\n );\n}\n\n/**\n * 为 number/boolean 表达式包裹 z.preprocess(用于 query coerce)\n *\n * 引用从 faapi-helpers.js import 的 coerceNumber / coerceBoolean 变量,\n * 避免每个字段都内联一长串函数。\n */\nfunction wrapCoercePreprocess(kind: 'number' | 'boolean', inner: string): string {\n if (kind === 'number') {\n return `z.preprocess(coerceNumber, ${inner})`;\n }\n return `z.preprocess(coerceBoolean, ${inner})`;\n}\n\n/**\n * 生成元组 zod 表达式\n *\n * zod 的 tuple 不直接支持可选元素,需要用 union 模拟:\n * - 全部必填:`z.tuple([e0, e1, ...])`\n * - 含可选元素:`z.union([z.tuple([e0]), z.tuple([e0, e1]), ...])`(从短到长)\n * - 含 rest:`z.tuple([e0, ...]).rest(eRest)`\n */\nfunction generateTupleExpression(elements: TupleElement[], ctx: CodeGenContext): string {\n const fixedExprs: string[] = [];\n const fixedOptional: boolean[] = [];\n let restExpression = '';\n let restStarted = false;\n\n for (const el of elements) {\n if (el.rest) {\n restExpression = runtimeTypeToZodExpression(el.type, ctx);\n restStarted = true;\n } else if (!restStarted) {\n fixedExprs.push(runtimeTypeToZodExpression(el.type, ctx));\n fixedOptional.push(el.optional);\n }\n }\n\n // 含 rest 元素:z.tuple([fixed...]).rest(restType)。\n // 可选前缀保留(zod v4 原生支持 z.tuple([X.optional()]).rest(...))——\n // 此前丢弃 fixedOptional 导致 [string?, ...number[]] 的空数组被误拒\n if (restExpression) {\n const fixedWithOptional = fixedExprs.map((expr, i) =>\n fixedOptional[i] ? `${expr}.optional()` : expr,\n );\n return `z.tuple([${fixedWithOptional.join(', ')}]).rest(${restExpression})`;\n }\n\n // 无可选元素:z.tuple([e0, e1, ...])\n const hasOptional = fixedOptional.some((o) => o);\n if (!hasOptional) {\n return `z.tuple([${fixedExprs.join(', ')}])`;\n }\n\n // 含可选元素:从后往前截断可选元素,生成 union\n // TS 语法要求可选元素在必填元素之后,所以只需从后往前去掉可选元素\n const variants: string[] = [];\n for (let len = fixedExprs.length; len >= 0; len--) {\n // len === 0 时只有全部可选才有效(空元组)\n // len > 0 时,第 len-1 个元素必须存在(必填或可选都行)\n // 截断点:去掉 fixedExprs[len..end],这些必须都是可选的\n const removed = fixedOptional.slice(len);\n if (removed.length > 0 && removed.some((o) => !o)) {\n // 被截断的元素中有必填元素,无效截断\n break;\n }\n const subset = fixedExprs.slice(0, len);\n variants.push(`z.tuple([${subset.join(', ')}])`);\n }\n\n // 从短到长排序(更自然的校验顺序)\n variants.reverse();\n if (variants.length === 1) {\n return variants[0]!;\n }\n return `z.union([${variants.join(', ')}])`;\n}\n\n/**\n * 生成对象 zod 表达式\n *\n * 可选字段用 .optional(),约束链在 .optional() 之前生成。\n * 带索引签名的对象(catchall)生成 z.object({...}).catchall(valueSchema)——\n * 索引签名与属性共存时属性保留(resolveTypeNode 不再丢弃)。\n */\nfunction generateObjectExpression(\n type: Extract<RuntimeType, { kind: 'object' }>,\n ctx: CodeGenContext,\n): string {\n const fields = type.properties.map((prop) => {\n const expr = runtimeTypeToZodExpression(prop.type, ctx, prop.constraints);\n const finalExpr = prop.optional ? `${expr}.optional()` : expr;\n return `${JSON.stringify(prop.name)}: ${finalExpr}`;\n });\n const objectExpr = `z.object({ ${fields.join(', ')} })`;\n return type.catchall !== undefined\n ? `${objectExpr}.catchall(${runtimeTypeToZodExpression(type.catchall, ctx)})`\n : objectExpr;\n}\n\n/**\n * 生成联合类型 zod 表达式\n *\n * 优化:如果联合中包含 null,用 .nullable() 简化\n * 否则用 z.union([m0, m1, ...])\n */\nfunction generateUnionExpression(members: RuntimeType[], ctx: CodeGenContext): string {\n const hasNull = members.some((m) => m.kind === 'null');\n const nonNull = members.filter((m) => m.kind !== 'null');\n\n // 只有一个非 null 成员 + null → .nullable()\n if (hasNull && nonNull.length === 1) {\n return `${runtimeTypeToZodExpression(nonNull[0]!, ctx)}.nullable()`;\n }\n\n // 多个成员 + null → z.union([...]).nullable()\n if (hasNull) {\n const unionInner = nonNull.map((m) => runtimeTypeToZodExpression(m, ctx)).join(', ');\n return `z.union([${unionInner}]).nullable()`;\n }\n\n // 无 null → z.union([...])\n const unionInner = members.map((m) => runtimeTypeToZodExpression(m, ctx)).join(', ');\n return `z.union([${unionInner}])`;\n}\n\n/**\n * 生成命名类型的 zod schema 声明\n *\n * - 无循环引用:`const NameSchema = z.object({ ... })`\n * - 有循环引用:`const NameSchema = z.lazy(() => z.object({ ... }))`\n */\nfunction generateNamedTypeDeclaration(\n name: string,\n type: RuntimeType,\n ctx: CodeGenContext,\n): string {\n const expr = runtimeTypeToZodExpression(type, ctx);\n const hasRef = containsRef(type, new Set([name]));\n if (hasRef) {\n // 循环引用:用 z.lazy 延迟求值\n return `const ${name}Schema = z.lazy(() => ${expr});`;\n }\n return `const ${name}Schema = ${expr};`;\n}\n\n/**\n * 检查 RuntimeType 是否包含对自身的 ref(循环引用)\n */\nfunction containsRef(type: RuntimeType, visited: Set<string>): boolean {\n switch (type.kind) {\n case 'ref':\n return visited.has(type.name);\n case 'array':\n return containsRef(type.element, visited);\n case 'tuple':\n return type.elements.some((el) => containsRef(el.type, visited));\n case 'object':\n return type.properties.some((prop) => containsRef(prop.type, visited));\n case 'union':\n return type.members.some((m) => containsRef(m, visited));\n case 'record':\n return containsRef(type.key, visited) || containsRef(type.value, visited);\n case 'map':\n return containsRef(type.key, visited) || containsRef(type.value, visited);\n case 'set':\n return containsRef(type.element, visited);\n default:\n return false;\n }\n}\n\n/**\n * 生成单个类型的 zod schema 代码(含命名类型声明)\n *\n * 返回的代码包含:\n * - `import { z } from 'zod'`\n * - 内部命名类型的 const 声明(带 z.lazy 循环引用保护)\n * - `export const NameSchema = ...` 入口导出\n *\n * @param typeInfo 类型信息\n * @param resolveType 类型解析器(用于解析 ref 的实际类型)\n * @param exportName 导出的 schema 变量名(不含 Schema 后缀)。默认用 typeInfo.name。\n * validateInput 按 `${schemaName}Schema` 查找,需与导出名一致。\n * @param coerce 是否生成 coerce 逻辑(query/params 场景)。默认 false。\n * true 时为 number/boolean 字段(含嵌套元素)包 z.preprocess。\n */\nexport function generateZodSchemaSource(\n typeInfo: HandlerTypeInfo,\n resolveType: TypeResolver,\n exportName?: string,\n coerce = false,\n): string {\n const { namedTypeDeclarations, entryDeclaration } = generateZodSchemaSourceParts(\n typeInfo,\n resolveType,\n exportName,\n coerce,\n );\n\n const lines: string[] = [];\n lines.push(\"import { z } from 'zod';\");\n lines.push('');\n\n for (const { declaration } of namedTypeDeclarations) {\n lines.push(declaration);\n }\n if (namedTypeDeclarations.length > 0) lines.push('');\n\n lines.push(entryDeclaration);\n\n return lines.join('\\n');\n}\n\n/**\n * 生成单个类型的 zod schema 代码片段(结构化形式)\n *\n * 与 generateZodSchemaSource 的区别:不拼 import 头,命名类型声明与入口导出分开返回,\n * 供文件级组装按名去重——同一 handler 文件的多个方法引用同一命名类型时,命名类型\n * 声明只允许出现一次,否则拼接产物含重复 const 声明,import 即 SyntaxError。\n *\n * 语义与 generateZodSchemaSource 完全一致(含 coerce / z.lazy 循环引用处理)。\n */\nexport interface ZodSchemaSourceParts {\n /** 命名类型声明(按首次引用顺序排列;name 为不含 Schema 后缀的类型名) */\n namedTypeDeclarations: Array<{ name: string; declaration: string }>;\n /** 入口 schema 导出(`export const NameSchema = ...;`) */\n entryDeclaration: string;\n}\n\nexport function generateZodSchemaSourceParts(\n typeInfo: HandlerTypeInfo,\n resolveType: TypeResolver,\n exportName?: string,\n coerce = false,\n): ZodSchemaSourceParts {\n const ctx = new CodeGenContext(resolveType);\n const name = exportName ?? typeInfo.name;\n ctx.entryTypeName = typeInfo.name;\n ctx.entryExportName = name;\n ctx.coerce = coerce;\n\n // 收集入口类型中的所有命名类型(不含入口类型本身,避免重复声明)\n collectNamedTypes(typeInfo.runtimeType, ctx);\n ctx.namedTypes.delete(typeInfo.name);\n\n const namedTypeDeclarations = [...ctx.namedTypes].map(([n, type]) => ({\n name: n,\n declaration: generateNamedTypeDeclaration(n, type, ctx),\n }));\n\n // 入口 schema 导出(含循环引用时用 z.lazy)\n const entryExpr = runtimeTypeToZodExpression(typeInfo.runtimeType, ctx);\n const hasSelfRef = containsRef(typeInfo.runtimeType, new Set([typeInfo.name]));\n const entryDeclaration = hasSelfRef\n ? `export const ${name}Schema = z.lazy(() => ${entryExpr});`\n : `export const ${name}Schema = ${entryExpr};`;\n\n return { namedTypeDeclarations, entryDeclaration };\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport { compileDevRoutes } from './compileDevRoutes';\nimport { generateSchemaFiles } from './generateSchemaFiles';\nimport { getRuntimeSchemaPath } from './generateSchemaFiles';\nimport { collectRelativeImports } from './collectImports';\nimport type { RouteManifest } from '../router/routeTypes';\n\n/**\n * 按需编译(Vite 风格):仅在请求时编译被访问的 handler.ts 及其依赖闭包 + 生成 zod.js\n *\n * 设计目标:dev 启动时零编译、零 schema 生成,handler.js / zod.js 首次被需要时才触发。\n * 与阶段 1(scanRoutes 去 import)配合,dev 冷启动近乎瞬开。\n *\n * 依赖闭包:`bundle: false` 逐文件编译不分析 import 关系,只编译 handler 单文件时,\n * handler 引用的共享模块(如 `../../lib/db`)没有产物,首次请求 import 即\n * ERR_MODULE_NOT_FOUND。因此 ensureCompiled 先收集依赖闭包\n * (collectRelativeImports,相对 + tsconfig paths 别名),跳过产物已新鲜的文件后\n * 批量编译。src 外依赖不收集(outbase=src 语义限制,与 watcher 增量编译一致)。\n *\n * 四种触发路径:\n * 1. `loadRouteModule` 先调 `ensureCompiled` 编译源码 → 再 import handler.js\n * 2. `loadWsHandler` 同上(WS handler.js)\n * 3. `createServer` 调 `ensureSchemaGenerated` 检查 zod.js 是否存在/最新 → 不存在则生成\n * 4. watcher 文件变化 → 增量编译该文件(保留现有逻辑)+ 删除 stale zod.js\n * 5. `createServer` / `handleWsUpgrade` 首次加载目录中间件前调 `ensureMiddlewaresCompiled`\n *\n * mtime 缓存(阶段 4):\n * - `ensureCompiled`:handler.js 存在且 mtime ≥ 源码 mtime → 跳过编译\n * - `ensureSchemaGenerated`:zod.js 存在且 mtime ≥ 源码 mtime → 跳过生成\n * - 首次调用后写入内存 Set,后续直接跳过(避免 fs.statSync 调用)\n * - watcher 触发时清除内存 Set,mtime 重新评估\n *\n * 并发去重(mutex):\n * - 同一文件的并发编译请求会共享同一个 in-flight Promise,避免重复触发 esbuild\n * - 同一 schemaPath 的并发生成请求同理\n * - watcher 触发 clear 时同步清空 in-flight Map\n */\n\n/**\n * 比较源文件和产物文件的 mtime\n *\n * @returns true 表示产物是最新的(可复用),false 表示需要重新生成\n */\nfunction isProductFresh(sourceAbsPath: string, productAbsPath: string): boolean {\n try {\n const srcStat = fs.statSync(sourceAbsPath);\n const prodStat = fs.statSync(productAbsPath);\n return prodStat.mtimeMs >= srcStat.mtimeMs;\n } catch {\n return false;\n }\n}\n\n// ─── DevOnDemand 状态封装 ────────────────────────────────────────────\n\n/**\n * 按需编译运行时状态\n *\n * 封装原本散落的 4 个模块级可变状态 + 2 个 mutex Map,避免全局污染 +\n * 便于测试隔离(_resetDevOnDemandState 重置回初始状态)。\n *\n * faapi 单进程单 server 设计下,单例 state 已足够。多实例测试场景通过\n * _resetDevOnDemandState 在 beforeEach / afterEach 中清空状态。\n */\ninterface DevOnDemandState {\n /** 是否启用 dev 按需编译模式(prod 始终 false) */\n enabled: boolean;\n /** dev 模式产物目录(仅 enabled 时使用) */\n distDir: string | undefined;\n /** 已编译文件缓存(源码绝对路径) */\n compiledFiles: Set<string>;\n /** 已生成 schema 缓存(schemaPath 绝对路径) */\n generatedSchemas: Set<string>;\n /** handler.js 编译 mutex:同一 sourceAbsPath 的并发请求共享同一 Promise */\n inFlightCompilations: Map<string, Promise<void>>;\n /** zod.js 生成 mutex:同一 schemaPath 的并发请求共享同一 Promise */\n inFlightSchemaGenerations: Map<string, Promise<void>>;\n}\n\nfunction createDevOnDemandState(): DevOnDemandState {\n return {\n enabled: false,\n distDir: undefined,\n compiledFiles: new Set(),\n generatedSchemas: new Set(),\n inFlightCompilations: new Map(),\n inFlightSchemaGenerations: new Map(),\n };\n}\n\nconst state: DevOnDemandState = createDevOnDemandState();\n\n/**\n * 重置 dev on demand 状态(仅测试用)\n *\n * 清空所有缓存 + mutex + 标记位,让下一次测试从干净状态开始。\n * 生产代码不应调用此函数——会丢失已编译产物的缓存。\n */\nexport function _resetDevOnDemandState(): void {\n state.enabled = false;\n state.distDir = undefined;\n state.compiledFiles.clear();\n state.generatedSchemas.clear();\n state.inFlightCompilations.clear();\n state.inFlightSchemaGenerations.clear();\n}\n\n// ─── handler.js 按需编译 ────────────────────────────────────────────\n\n/**\n * 清空所有按需编译缓存(reloadRoutes 时调用)\n *\n * 同时清空 in-flight mutex Map + 产物→源码映射缓存(源文件新建/删除后映射需重建),\n * 避免 watcher 重置后旧 Promise 永久阻塞。\n */\nexport function clearCompiledFiles(): void {\n state.compiledFiles.clear();\n state.inFlightCompilations.clear();\n sourcePathCache.clear();\n}\n\n/**\n * 确保源文件及其依赖闭包已编译为产物,未编译则触发批量编译\n *\n * 调用方(`loadRouteModule` / `loadWsHandler`)在 import 产物之前调用此函数,确保产物已生成。\n *\n * 依赖闭包:handler 引用的项目内共享模块(相对 import + tsconfig paths 别名)一并编译,\n * 跳过产物已新鲜的文件(watcher 已编译或此前请求已编译),只补缺口。\n *\n * mtime 缓存(阶段 4):\n * 1. 内存 Set 命中 → 跳过(最快路径)\n * 2. 产物存在且 mtime ≥ 源码 mtime → 跳过(复用已有产物,如 watcher 已编译)\n * 3. 产物不存在或 stale → 编译(含 stale 的依赖闭包文件) → 加入内存 Set\n *\n * 并发去重(mutex):\n * - 同一 sourceAbsPath 的并发请求共享同一 in-flight Promise\n * - 第二个请求 await 后返回 false(表示「别的请求已触发编译,本次不需要再触发」)\n *\n * @param sourceAbsPath 源码 .ts 绝对路径\n * @param rootDir 项目根目录\n * @param dist 产物目录(dev 模式为 '.faapi')\n * @returns 是否实际触发了编译(false 表示已编译过或产物已最新或源文件不存在)\n * @throws 编译失败时抛错(带原始 cause),调用方可据此给出精确错误信息\n */\nexport async function ensureCompiled(\n sourceAbsPath: string,\n rootDir: string,\n dist: string,\n): Promise<boolean> {\n // mutex: 同一文件正在被别的请求编译 → 等待并返回 false\n const inFlight = state.inFlightCompilations.get(sourceAbsPath);\n if (inFlight) {\n await inFlight.catch(() => {\n // 别的请求编译失败时不在这里抛——让本请求按正常流程自己重试\n });\n return false;\n }\n\n // 内存缓存命中:跳过\n if (state.compiledFiles.has(sourceAbsPath)) {\n return false;\n }\n\n // 源文件不存在:无法编译\n if (!fs.existsSync(sourceAbsPath)) {\n return false;\n }\n\n // mtime 缓存:产物已存在且最新 → 复用(watcher 已编译过的情况)\n const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);\n if (productPath && isProductFresh(sourceAbsPath, productPath)) {\n state.compiledFiles.add(sourceAbsPath);\n return false;\n }\n\n // 触发编译:注册 in-flight Promise 防止并发重复编译\n const compilePromise = (async () => {\n // 依赖闭包:入口本身 + src 内全部传递依赖;产物已新鲜的文件跳过(只补缺口)\n const { insideFiles } = await collectRelativeImports([sourceAbsPath], rootDir);\n const files = [sourceAbsPath];\n for (const dep of insideFiles) {\n if (state.compiledFiles.has(dep)) continue;\n const depProduct = prodSourcePathToProductPath(dep, rootDir, dist);\n if (depProduct && isProductFresh(dep, depProduct)) continue;\n files.push(dep);\n }\n\n await compileDevRoutes({\n rootDir,\n dist,\n files,\n logLevel: 'silent',\n });\n for (const file of files) {\n state.compiledFiles.add(file);\n }\n state.compiledFiles.add(sourceAbsPath);\n })();\n\n state.inFlightCompilations.set(sourceAbsPath, compilePromise);\n try {\n await compilePromise;\n return true;\n } finally {\n state.inFlightCompilations.delete(sourceAbsPath);\n }\n}\n\n/**\n * 确保目录中间件文件已编译(dev 按需模式)\n *\n * middlewarePaths 是产物绝对路径(`.faapi/api/users/middlewares.js`),dev 按需模式下\n * 产物在首次请求前不存在——若不先编译,`loadMergedMiddlewares` import 即\n * ERR_MODULE_NOT_FOUND。通过 `prodPathToSourcePath` 反推源码路径后走 `ensureCompiled`\n * (含依赖闭包)。prod 模式(按需开关关闭)直接返回——产物由 build 固化。\n *\n * @param middlewarePaths 中间件产物绝对路径列表(scanRoutes 收集)\n * @param rootDir 项目根目录\n */\nexport async function ensureMiddlewaresCompiled(\n middlewarePaths: string[],\n rootDir: string,\n): Promise<void> {\n if (!isDevOnDemandEnabled() || middlewarePaths.length === 0) return;\n const dist = getDevDist();\n if (!dist) return;\n\n for (const mwPath of middlewarePaths) {\n const sourcePath = prodPathToSourcePath(mwPath, rootDir, dist);\n try {\n await ensureCompiled(sourcePath, rootDir, dist);\n } catch (err) {\n // 中间件编译失败不阻断请求——loadMiddlewaresFile 对 import 失败已有\n // console.error + 空 bundle 降级语义(鉴权失效由 onError 感知),此处保持一致\n console.error(`[faapi] Failed to compile middleware source ${sourcePath}:`, err);\n }\n }\n}\n\n/**\n * 从源码绝对路径推算产物绝对路径\n *\n * 源码 `<rootDir>/src/api/hello/handler.ts` → 产物 `<rootDir>/<dist>/api/hello/handler.js`\n */\nfunction prodSourcePathToProductPath(\n sourceAbsPath: string,\n rootDir: string,\n dist: string,\n): string | null {\n const rel = path.relative(rootDir, sourceAbsPath).replace(/\\\\/g, '/');\n if (!rel.startsWith('src/')) return null;\n const relWithoutSrc = rel.slice(4); // 去掉 src/\n const jsRel = relWithoutSrc.replace(/\\.ts$/, '.js');\n return path.resolve(rootDir, dist, jsRel);\n}\n\n// ─── zod.js 按需生成 ────────────────────────────────────────────────\n\n/**\n * 清空所有 schema 生成缓存(reloadRoutes 时调用)\n *\n * 同时清空 in-flight mutex Map。\n */\nexport function clearGeneratedSchemas(): void {\n state.generatedSchemas.clear();\n state.inFlightSchemaGenerations.clear();\n}\n\n/**\n * 确保 zod.js 已生成,未生成则触发单文件 schema 生成\n *\n * 调用方(createServer)在 validateInput 之前调用此函数。\n *\n * route.filePath 在 dev/prod 模式下均为产物路径(如 '.faapi/api/hello/handler.js'),\n * 需通过 prodPathToSourcePath 反推源码 .ts 路径用于 AST 分析和 mtime 比较。\n *\n * mtime 缓存(阶段 4):\n * 1. 内存 Set 命中 → 跳过\n * 2. zod.js 存在且 mtime ≥ 源码 mtime → 跳过(复用已有产物)\n * 3. zod.js 不存在或 stale → 生成 → 加入内存 Set\n *\n * 并发去重(mutex):\n * - 同一 schemaPath 的并发请求共享同一 in-flight Promise\n * - 第二个请求 await 后返回 false\n *\n * @param schemaPath zod.js 绝对路径\n * @param routeFilePath route.filePath(产物路径,如 '.faapi/api/hello/handler.js')\n * @param routes 完整路由清单(用于过滤同文件的所有方法)\n * @param rootDir 项目根目录\n * @param dist 产物目录\n * @returns 是否实际触发了生成(false 表示已生成过或产物已最新或源文件不存在)\n * @throws schema 生成失败时抛错(带原始 cause),由 createServer 错误处理链接管\n */\nexport async function ensureSchemaGenerated(\n schemaPath: string,\n routeFilePath: string,\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<boolean> {\n // mutex: 同一 schemaPath 正在被别的请求生成 → 等待并返回 false\n const inFlight = state.inFlightSchemaGenerations.get(schemaPath);\n if (inFlight) {\n await inFlight.catch(() => {\n // 别的请求生成失败时不在这里抛——让本请求按正常流程自己重试\n });\n return false;\n }\n\n // 内存缓存命中:跳过\n if (state.generatedSchemas.has(schemaPath)) {\n return false;\n }\n\n // route.filePath 是产物路径,反推源码绝对路径(用于 mtime 比较和 AST 分析)\n const prodAbsPath = path.resolve(rootDir, routeFilePath);\n const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);\n if (!fs.existsSync(sourceAbsPath)) {\n return false;\n }\n\n // mtime 缓存:zod.js 已存在且最新 → 复用\n if (isProductFresh(sourceAbsPath, schemaPath)) {\n state.generatedSchemas.add(schemaPath);\n return false;\n }\n\n // 过滤同文件的所有路由(一个 handler.ts 可能有 GET/POST/WS 多个方法)\n const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);\n if (fileRoutes.length === 0) {\n return false;\n }\n\n // 把产物 filePath 转为源码 filePath(generateSchemaFiles → collectRouteSchemaSources\n // 需要源码 .ts 路径做 AST 分析)\n const sourceRelPath = path.relative(rootDir, sourceAbsPath).replace(/\\\\/g, '/');\n const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));\n\n // 触发生成:注册 in-flight Promise 防止并发重复生成\n const generatePromise = (async () => {\n // 生成失败时抛错(不吞错误),由 createServer 错误处理链接管\n await generateSchemaFiles(sourceRoutes, rootDir, dist);\n state.generatedSchemas.add(schemaPath);\n })();\n\n state.inFlightSchemaGenerations.set(schemaPath, generatePromise);\n try {\n await generatePromise;\n return true;\n } finally {\n state.inFlightSchemaGenerations.delete(schemaPath);\n }\n}\n\n/**\n * 删除指定路由清单对应的所有 zod.js 文件(reloadRoutes 时调用)\n *\n * watcher 文件变化后,旧 zod.js 可能 stale(类型引用变化等)。\n * 删除后下次请求触发 ensureSchemaGenerated 重新生成。\n *\n * 仅删除文件,不删除目录(faapi-helpers.js 保留,内容确定性不变)。\n */\nexport async function deleteSchemaFiles(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<void> {\n const deleted = new Set<string>();\n for (const route of routes) {\n const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);\n if (deleted.has(schemaPath)) continue;\n deleted.add(schemaPath);\n try {\n await fs.promises.unlink(schemaPath);\n } catch {\n // 文件不存在:忽略\n }\n }\n}\n\n// ─── 路径转换 ────────────────────────────────────────────────────────\n\n/**\n * 从产物绝对路径反推源码绝对路径(带映射缓存)\n *\n * 源码 `<rootDir>/src/api/hello/handler.ts` → 产物 `<rootDir>/<dist>/api/hello/handler.js`\n *\n * dev 按需模式下**每请求**都会调此函数(loadRouteModule / ensureSchemaGenerated),\n * 缓存映射后稳态请求零 fs 访问。映射仅取决于源文件的存在性——源文件新建/删除\n * 通过 `clearCompiledFiles()`(reloadRoutes 时调用)清缓存。\n */\nconst sourcePathCache = new Map<string, string>();\n\nexport function prodPathToSourcePath(prodAbsPath: string, rootDir: string, dist: string): string {\n const cached = sourcePathCache.get(prodAbsPath);\n if (cached) return cached;\n\n const rel = path.relative(rootDir, prodAbsPath).replace(/\\\\/g, '/');\n // 去掉 <dist>/ 前缀\n let relWithoutDist = rel;\n if (relWithoutDist.startsWith(`${dist}/`)) {\n relWithoutDist = relWithoutDist.slice(dist.length + 1);\n }\n // 加 src/ 前缀\n const srcRel = `src/${relWithoutDist}`;\n // .js → .ts(.ts 不存在时回退 .js)\n const tsRel = srcRel.replace(/\\.js$/, '.ts');\n const tsAbs = path.resolve(rootDir, tsRel);\n let result: string;\n if (fs.existsSync(tsAbs)) {\n result = tsAbs;\n } else {\n // .ts 不存在,保持 .js(极少见,可能是用户直接放 .js 源码)\n result = path.resolve(rootDir, srcRel);\n }\n sourcePathCache.set(prodAbsPath, result);\n return result;\n}\n\n// ─── Dev 按需模式标记 ────────────────────────────────────────────────\n\n/**\n * Dev 按需编译模式标记\n *\n * 由 devCommand 在按需模式启动时设置为 true,loadRouteModule / loadWsHandler /\n * createServer 通过此标记判断是否启用按需编译/schema 生成回退。\n *\n * prod 模式(node dist/main)始终为 false——产物在 build 阶段已固化,import 失败即报错。\n */\nexport function setDevOnDemandEnabled(enabled: boolean): void {\n state.enabled = enabled;\n}\n\nexport function isDevOnDemandEnabled(): boolean {\n return state.enabled;\n}\n\n/**\n * Dev 模式产物目录(仅 devOnDemandEnabled 时使用)\n *\n * 由 devCommand 设置,loadRouteModule / loadWsHandler / createServer 通过 getDevDist() 读取。\n */\nexport function setDevDist(dist: string): void {\n state.distDir = dist;\n}\n\nexport function getDevDist(): string | undefined {\n return state.distDir;\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport fg from 'fast-glob';\nimport { buildAliasPlugins } from './aliasPlugin';\nimport { APP_DIR } from '../utils/prodPaths';\n\n/**\n * 编译结果\n */\nexport interface CompileResult {\n /** 已编译的文件列表(绝对路径) */\n compiledFiles: string[];\n}\n\nexport interface CompileSourceFilesOptions {\n /** 项目根目录 */\n rootDir: string;\n /** 输出目录(dev 为 `.faapi`,build 为 `dist`) */\n dist: string;\n /**\n * 增量编译:传入要编译的文件列表(绝对路径)。\n * 不传则全量编译 src 下所有 .ts(排除测试文件和声明文件)。\n */\n files?: string[];\n /** 是否输出 esbuild 日志(默认静默) */\n logLevel?: 'silent' | 'info';\n /**\n * prod 语义:`define` 把 `process.env.NODE_ENV` 编译期替换为 `\"production\"` +\n * `minifySyntax` 删除死分支(两者在 `bundle: false` 下均生效,单文件级别优化)。\n */\n production?: boolean;\n /**\n * dev 语义:原子写(write: false + 临时文件 + rename),避免 watch 模式下\n * esbuild 非原子写期间运行时 import 读到半成品产物。\n */\n atomicWrite?: boolean;\n}\n\n/**\n * dev/prod 共用的逐文件 TypeScript 编译实现\n *\n * 每个 `.ts` 独立编译为 `.js`,不分析 import 关系(`bundle: false`——\n * 保证 config 与 routes 共享同一运行时对象,`instanceof` 跨边界生效)。\n * 产物**打平 src/ 前缀**:`src/api/hello/handler.ts` → `<dist>/api/hello/handler.js`。\n *\n * 别名在编译时重写为相对路径,运行时无需 loader。\n *\n * 通过 `compileDevRoutes` / `compileBuildRoutes` 调用,见 ./compileSourceFiles.md\n * 的选项矩阵。\n */\nexport async function compileSourceFiles(\n options: CompileSourceFilesOptions,\n): Promise<CompileResult> {\n const { rootDir, dist, files, logLevel = 'silent', production, atomicWrite } = options;\n\n // 收集要编译的文件:files 优先,否则全量扫描 src 下所有 .ts\n const entryPoints =\n files ??\n (await fg([`${APP_DIR}/**/*.ts`], {\n cwd: rootDir,\n onlyFiles: true,\n absolute: true,\n ignore: ['**/*.test.ts', '**/*.e2e.test.ts', '**/*.d.ts'],\n }));\n\n if (entryPoints.length === 0) {\n return { compiledFiles: [] };\n }\n\n // 确保输出目录存在\n const absDist = path.resolve(rootDir, dist);\n await fs.promises.mkdir(absDist, { recursive: true });\n\n // 构造别名重写插件(无 tsconfig/paths 时为空,相对路径重写仍生效)\n const plugins = buildAliasPlugins(rootDir);\n\n // 逐文件编译,outbase 设为 src 以打平产物结构:\n // `src/api/hello/handler.ts` → `<dist>/api/hello/handler.js`(去掉 src/ 前缀)\n const esbuild = await import('esbuild');\n const outbase = path.resolve(rootDir, APP_DIR);\n const result = await esbuild.build({\n entryPoints,\n outdir: absDist,\n outbase,\n bundle: false,\n platform: 'node',\n format: 'esm',\n sourcemap: true,\n packages: 'external',\n plugins,\n // build 语义:编译期 NODE_ENV 替换 + 死分支删除(见 AGENTS.md §5.3)\n ...(production\n ? { define: { 'process.env.NODE_ENV': '\"production\"' }, minifySyntax: true }\n : {}),\n logLevel,\n // dev 语义:esbuild 返回内存内容,由下方原子写落盘\n ...(atomicWrite ? { write: false as const } : {}),\n });\n\n // 原子写:逐个写临时文件 + rename。rename 在同一文件系统上是原子的(POSIX),\n // HTTP 请求要么看到旧文件要么看到新文件\n if (atomicWrite && result.outputFiles) {\n await Promise.all(\n result.outputFiles.map(async (file) => {\n await fs.promises.mkdir(path.dirname(file.path), { recursive: true });\n const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n await fs.promises.writeFile(tmp, file.contents);\n await fs.promises.rename(tmp, file.path);\n }),\n );\n }\n\n return { compiledFiles: entryPoints };\n}\n\n/**\n * 全量扫描 src 下的 .ts 文件(排除测试文件和声明文件)\n *\n * 与 compileSourceFiles 的全量分支使用同一 ignore 规则,供 watch 等场景独立扫描。\n */\nexport async function collectSourceFiles(rootDir: string): Promise<string[]> {\n return fg([`${APP_DIR}/**/*.ts`], {\n cwd: rootDir,\n onlyFiles: true,\n absolute: true,\n ignore: ['**/*.test.ts', '**/*.e2e.test.ts', '**/*.d.ts'],\n });\n}\n","import type { Plugin } from 'esbuild';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { resolveAlias } from '../utils/resolveAlias';\nimport { APP_DIR, isInsideDir, toProdExtension, toRealPath } from '../utils/prodPaths';\nimport { readTsconfig, type TsconfigPathsConfig } from '../utils/readTsconfig';\n\n// 兼容 re-export:toProdExtension 此前由本模块导出\nexport { toProdExtension };\n\n/**\n * 把候选源文件路径转为产物 import 路径(相对 importer,POSIX 风格,带 .js 后缀)\n */\nfunction toProdImportPath(sourceFile: string, importer: string): string {\n const importerDir = path.dirname(importer);\n let rel = path.relative(importerDir, sourceFile);\n rel = rel.split(path.sep).join('/');\n if (!rel.startsWith('.')) rel = './' + rel;\n return toProdExtension(rel);\n}\n\n/**\n * 把 src 下的源文件路径转为剥离 src/ 前缀的产物 import 路径(POSIX 风格,带 .js 后缀)\n *\n * 用于 config 文件(位于 rootDir,不在 src 下)引用 src 内模块的场景:\n * - 源文件 `<rootDir>/src/lib/errors.ts` → 产物 `dist/lib/errors.js`\n * - config 产物位于 `dist/faapi.config.js`(dist 根)\n * - import 路径相对 dist 根:`./lib/errors.js`\n *\n * 与 `toProdImportPath` 的区别:后者相对 importer 目录(适用于 importer 也在 src 内的场景,\n * outbase 打平后相对结构不变);本函数相对 src 根(适用于 importer 在 src 外的场景,\n * 需要剥离 src/ 前缀以匹配 compileDevRoutes 的打平产物结构)。\n *\n * 内部用 `toRealPath` 规范化 appDirAbs,兼容 macOS 符号链接(esbuild onLoad 传入的\n * args.path 已是 realpath,未规范化的 appDirAbs 会导致前缀比较失败)。\n */\nfunction toStrippedProdImportPath(sourceFile: string, rootDir: string): string {\n const appDirAbs = toRealPath(path.resolve(rootDir, APP_DIR));\n const sourceReal = toRealPath(sourceFile);\n let rel = path.relative(appDirAbs, sourceReal);\n rel = rel.split(path.sep).join('/');\n if (!rel.startsWith('.')) rel = './' + rel;\n return toProdExtension(rel);\n}\n\n/**\n * aliasPlugin 扩展选项\n *\n * - `rootDir`:启用 src/ 前缀剥离。当 importer 不在 src 下(如 config 文件)\n * 且引用的源文件在 src 下时,重写为剥离前缀的产物路径(相对 dist 根),以匹配\n * `compileDevRoutes`/`compileBuildRoutes` 的打平产物结构。\n */\nexport interface AliasPluginOptions {\n /** 项目根目录(启用剥离时必填) */\n rootDir?: string;\n}\n\n/**\n * 产物后缀集合(已带这些后缀的 specifier 视为产物路径,不重写)\n */\nconst PROD_EXTS = ['.js', '.mjs', '.cjs'];\n\n/**\n * 源文件后缀集合(重写时尝试这些后缀定位实际文件)\n */\nconst SOURCE_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];\n\n/**\n * index 文件后缀集合(specifier 指向目录时尝试)\n */\nconst INDEX_EXTS = [\n '/index.ts',\n '/index.tsx',\n '/index.js',\n '/index.jsx',\n '/index.mjs',\n '/index.cjs',\n];\n\n/**\n * 解析相对 specifier 到实际源文件绝对路径\n *\n * - 已带产物后缀(.js/.mjs/.cjs)且文件存在 → 返回该路径(不重写)\n * - 已带源后缀(.ts/.tsx/.jsx)且文件存在 → 返回该路径(重写为 .js)\n * - 无后缀 → 尝试各源后缀 + index 文件\n *\n * @param importer 导入文件绝对路径\n * @param specifier 相对 specifier(./xxx 或 ../xxx)\n * @returns 解析到的源文件绝对路径,或 null(未解析到)\n */\nexport function resolveRelativeSpecifier(importer: string, specifier: string): string | null {\n const importerDir = path.dirname(importer);\n const base = path.resolve(importerDir, specifier);\n\n // 已带产物后缀:文件存在则返回(视为产物路径,不重写)\n if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {\n return fs.existsSync(base) ? base : null;\n }\n\n // 已带源后缀:文件存在则返回(将重写为产物后缀)\n if (/\\.(ts|tsx|jsx)$/.test(specifier)) {\n return fs.existsSync(base) ? base : null;\n }\n\n // 无后缀:尝试各源后缀\n for (const ext of SOURCE_EXTS) {\n const file = base + ext;\n if (fs.existsSync(file)) return file;\n }\n\n // 无后缀:尝试 index 文件\n for (const indexExt of INDEX_EXTS) {\n const file = base + indexExt;\n if (fs.existsSync(file)) return file;\n }\n\n return null;\n}\n\n/**\n * esbuild specifier 重写插件(dev/build/config 编译共用)\n *\n * 在 `onLoad` 阶段把源码 import/export 中的 specifier 重写为产物相对路径(带 `.js` 后缀):\n *\n * 1. **相对 specifier**(`./xxx`、`../xxx`):解析到实际源文件,重写为产物相对路径。\n * - 无后缀 → 解析 + 加 `.js`\n * - `.ts`/`.tsx`/`.jsx` → 改为 `.js`\n * - `.js`/`.mjs`/`.cjs` → 不处理(视为产物路径)\n * - 解析失败(文件不存在)→ 原样保留\n *\n * 2. **别名 specifier**(tsconfig.paths,如 `@/xxx`):调 `resolveAlias` 解析候选路径,\n * 命中则重写为产物相对路径。\n *\n * `bundle: false` 模式下 esbuild 不递归解析依赖(`onResolve` 不触发),specifier 会原样\n * 保留到产物 `.js`,运行时 Node.js ESM loader 无法解析(无后缀推断)。本插件通过\n * `onLoad` 介入,确保产物中所有相对 import 都带 `.js` 后缀。\n *\n * **src/ 前缀剥离**(可选,通过 `options.rootDir` 启用):\n * 当 importer 不在 src 下(如 `faapi.config.ts` 位于 rootDir)且引用的源文件在 src 下时,\n * 重写为剥离 src/ 前缀的产物路径(相对 dist 根),以匹配 `compileDevRoutes`/`compileBuildRoutes`\n * 的打平产物结构。典型场景:config 引用项目模块(如 `./src/lib/errors` → `./lib/errors.js`),\n * 使 config 与 routes 共享同一份模块产物,`instanceof` 跨边界生效。\n *\n * 覆盖的 import 形式:\n * - `import { x } from './base'` / `import { x } from '@/base'`\n * - `export { x } from './base'`(含 `from`,被同一正则匹配)\n * - `import('./base')`(动态)\n *\n * 绝对路径(`/`)、`file:` URL、`node:` 协议不处理,交 esbuild 默认。\n */\nexport function createAliasPlugin(\n config: TsconfigPathsConfig,\n options?: AliasPluginOptions,\n): Plugin {\n // 匹配 from '...' / from \"...\" 和 import('...') / import(\"...\")\n const SPEC_RE = /(\\bfrom\\s*|import\\s*\\(\\s*)(['\"])([^'\"]+)\\2/g;\n // 预计算 src 绝对路径(realpath 规范化,用于判断 importer 和 resolved 是否在 src 内)\n // 规范化是为了兼容 macOS 符号链接:esbuild onLoad 传入的 args.path 是 realpath,\n // 未规范化的 appDirAbs 会导致 isInsideDir 比较失败(/var/folders vs /private/var/folders)\n const appDirAbs = options?.rootDir ? toRealPath(path.resolve(options.rootDir, APP_DIR)) : null;\n return {\n name: 'faapi-alias',\n setup(build) {\n build.onLoad({ filter: /\\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {\n let source: string;\n try {\n source = fs.readFileSync(args.path, 'utf8');\n } catch {\n return undefined;\n }\n const importer = args.path;\n // importer 是否在 src 外(启用剥离时才计算,用 isInsideDir 兼容符号链接)\n const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;\n let modified = false;\n const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {\n // 绝对路径 / file: URL / node: 协议不处理\n if (\n specifier.startsWith('/') ||\n specifier.startsWith('file:') ||\n specifier.startsWith('node:')\n ) {\n return full;\n }\n\n // 相对 specifier(./ 或 ../):解析到实际文件并重写\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n const resolved = resolveRelativeSpecifier(importer, specifier);\n if (resolved) {\n // 已带产物后缀的 specifier 不重写(resolveRelativeSpecifier 返回原路径)\n if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {\n return full;\n }\n // src 前缀剥离:importer 在 src 外、resolved 在 src 内\n // → 重写为剥离前缀的产物路径(相对 dist 根)\n if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {\n modified = true;\n return `${prefix}${quote}${toStrippedProdImportPath(\n resolved,\n options!.rootDir!,\n )}${quote}`;\n }\n modified = true;\n return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;\n }\n return full;\n }\n\n // 别名 specifier:调 resolveAlias 解析\n const candidates = resolveAlias(specifier, config);\n for (const candidate of candidates) {\n for (const ext of SOURCE_EXTS) {\n const file = candidate + ext;\n if (fs.existsSync(file)) {\n modified = true;\n // 别名解析到的文件如果在 src 内且 importer 在 src 外,同样剥离前缀\n if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {\n return `${prefix}${quote}${toStrippedProdImportPath(\n file,\n options!.rootDir!,\n )}${quote}`;\n }\n return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;\n }\n }\n for (const indexExt of INDEX_EXTS) {\n const file = candidate + indexExt;\n if (fs.existsSync(file)) {\n modified = true;\n if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {\n return `${prefix}${quote}${toStrippedProdImportPath(\n file,\n options!.rootDir!,\n )}${quote}`;\n }\n return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;\n }\n }\n }\n return full;\n });\n if (!modified) return undefined;\n return { contents: newSource, loader: 'default' };\n });\n },\n };\n}\n\n/**\n * 读取 tsconfig paths 并构造 specifier 重写插件\n *\n * 始终返回含本插件的数组:相对路径重写不依赖 tsconfig(`bundle: false` 下 Node ESM 必需),\n * 别名重写依赖 tsconfig.paths(无 paths 时别名不重写)。\n *\n * @param rootDir 项目根目录。传入时启用 src/ 前缀剥离:importer 在 src 外\n * 且引用 src 内文件时,重写为剥离前缀的产物路径。主要用于 compileConfig\n * 编译 config 文件(位于 rootDir)引用项目模块(位于 src)的场景。\n */\nexport function buildAliasPlugins(rootDir: string): Plugin[] {\n const tsconfig = readTsconfig(rootDir);\n return [createAliasPlugin(tsconfig ?? { baseUrl: '.', paths: {} }, { rootDir })];\n}\n","import type { TsconfigPathsConfig } from './readTsconfig';\n\n/**\n * 按 tsconfig paths 配置把 import specifier 解析为候选绝对路径\n *\n * 纯函数,不检查文件存在性。调用方需自行尝试 .ts/.js/index 等后缀。\n * 支持精确匹配(无 *)和单个 * 通配匹配。\n */\nexport function resolveAlias(specifier: string, config: TsconfigPathsConfig): string[] {\n const candidates: string[] = [];\n\n for (const [pattern, targets] of Object.entries(config.paths)) {\n const wildcardIndex = pattern.indexOf('*');\n\n if (wildcardIndex === -1) {\n // 精确匹配\n if (specifier === pattern) {\n candidates.push(...targets);\n }\n continue;\n }\n\n // 通配匹配:按 * 分割为 prefix / suffix\n const prefix = pattern.slice(0, wildcardIndex);\n const suffix = pattern.slice(wildcardIndex + 1);\n\n if (\n specifier.startsWith(prefix) &&\n specifier.endsWith(suffix) &&\n specifier.length >= prefix.length + suffix.length\n ) {\n const captured = specifier.slice(prefix.length, specifier.length - suffix.length);\n for (const target of targets) {\n candidates.push(target.replace('*', captured));\n }\n }\n }\n\n return candidates;\n}\n","import ts from 'typescript';\nimport path from 'node:path';\nimport fs from 'node:fs';\n\n/**\n * tsconfig paths 别名配置(已规范化为绝对路径)\n *\n * - baseUrl:绝对路径(无 baseUrl 时为 tsconfig 所在目录)\n * - paths:pattern -> 绝对路径数组(保留 * 通配符,供 resolveAlias 替换)\n */\nexport interface TsconfigPathsConfig {\n baseUrl: string;\n paths: Record<string, string[]>;\n}\n\n/**\n * 读取项目 tsconfig.json,提取 baseUrl + paths,规范化为绝对路径配置\n *\n * 用 TypeScript Compiler API 解析(处理注释、extends 合并)。\n * 仅读取 rootDir 下的 tsconfig.json,不向上递归查找。\n * 无 paths 时返回 null。\n *\n * 按 tsconfig mtime 缓存:每次编译(watcher 每轮重建、每次首请求按需编译)都会\n * 调本函数,Compiler API 的 readConfigFile + extends 链合并在长 extends 链项目上\n * 开销可观。tsconfig 变化时 mtime 变化 → 缓存自动失效,无需手动清理。\n */\ninterface TsconfigCacheEntry {\n mtimeMs: number;\n config: TsconfigPathsConfig | null;\n}\nconst tsconfigCache = new Map<string, TsconfigCacheEntry>();\n\nexport function readTsconfig(rootDir: string): TsconfigPathsConfig | null {\n const tsconfigPath = path.resolve(rootDir, 'tsconfig.json');\n if (!fs.existsSync(tsconfigPath)) return null;\n\n // mtime 缓存:tsconfig 未变化时直接复用解析结果\n let mtimeMs: number;\n try {\n mtimeMs = fs.statSync(tsconfigPath).mtimeMs;\n } catch {\n return null;\n }\n const cached = tsconfigCache.get(tsconfigPath);\n if (cached && cached.mtimeMs === mtimeMs) {\n return cached.config;\n }\n\n const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n if (configFile.error || !configFile.config) return null;\n\n // parseJsonConfigFileContent 处理 extends 合并、解析 baseUrl 为绝对路径\n const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, rootDir);\n\n // baseUrl:parseJsonConfigFileContent 已解析为绝对路径;未设置时回退到 rootDir\n const baseUrl = parsed.options.baseUrl ?? rootDir;\n\n const rawPaths = parsed.options.paths;\n const config: TsconfigPathsConfig | null = rawPaths\n ? (() => {\n // paths 目标:原始字符串相对 baseUrl,解析为绝对路径(保留 *)\n const paths: Record<string, string[]> = {};\n for (const [pattern, targets] of Object.entries(rawPaths)) {\n paths[pattern] = targets.map((t) => path.resolve(baseUrl, t));\n }\n return { baseUrl, paths };\n })()\n : null;\n\n tsconfigCache.set(tsconfigPath, { mtimeMs, config });\n return config;\n}\n","import { compileSourceFiles, collectSourceFiles, type CompileResult } from './compileSourceFiles';\n\nexport { collectSourceFiles };\nexport type { CompileResult };\n\n/**\n * dev 编译选项\n */\nexport interface CompileDevOptions {\n /** 项目根目录 */\n rootDir: string;\n /** 输出目录(dev 模式为 `.faapi`) */\n dist: string;\n /**\n * 增量编译:传入要编译的文件列表(绝对路径)。\n * 不传则全量编译 src 下所有 .ts(排除测试文件和声明文件)。\n */\n files?: string[];\n /** 是否输出 esbuild 日志(dev 模式默认静默) */\n logLevel?: 'silent' | 'info';\n}\n\n/**\n * dev 模式编译 TypeScript:逐文件编译,启动快、增量编译友好\n *\n * 共享实现见 [compileSourceFiles](./compileSourceFiles.ts);dev 差异仅两点:\n * 不传 `define`(`process.env.NODE_ENV` 运行时读取环境变量,便于热替换)+\n * **原子写**(写临时文件 + rename,避免 watch 模式下运行时 import 读到半成品产物,\n * 详见 compileDevRoutes.md 的\"dev 原子写\"章节)。\n *\n * @example\n * // 全量编译\n * await compileDevRoutes({ rootDir, dist: '.faapi' });\n * // watch 增量:只编译变化的文件\n * await compileDevRoutes({ rootDir, dist: '.faapi', files: changedFiles });\n */\nexport async function compileDevRoutes(options: CompileDevOptions): Promise<CompileResult> {\n return compileSourceFiles({ ...options, atomicWrite: true });\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport { isInsideDir, toRealPath } from '../utils/prodPaths';\nimport { resolveRelativeSpecifier } from './aliasPlugin';\nimport { resolveAlias } from '../utils/resolveAlias';\nimport { readTsconfig } from '../utils/readTsconfig';\n\n/**\n * 匹配 from '...' / from \"...\" / import('...') / import(\"...\") 中的 specifier\n */\nconst SPEC_RE = /(\\bfrom\\s*|import\\s*\\(\\s*)(['\"])([^'\"]+)\\2/g;\n\n/**\n * 从源文件内容中提取所有 import specifier\n *\n * 覆盖相对 specifier(./xxx、../xxx)与 tsconfig paths 别名 specifier(如 @/xxx)。\n * 动态 import(import('...'))同样覆盖。\n */\nfunction extractImportSpecifiers(source: string): string[] {\n const specifiers: string[] = [];\n let match: RegExpExecArray | null;\n SPEC_RE.lastIndex = 0;\n while ((match = SPEC_RE.exec(source)) !== null) {\n specifiers.push(match[3]!);\n }\n return specifiers;\n}\n\n/**\n * 从目录解析 specifier 为源文件绝对路径(探测 .ts/.tsx 等源后缀与 index 文件)\n *\n * 复用 resolveRelativeSpecifier 的探测逻辑:它按 importer 文件所在目录解析,\n * 传入目录内的合成文件名即可从指定目录解析。\n */\nfunction resolveSpecifierFromDir(dir: string, specifier: string): string | null {\n return resolveRelativeSpecifier(path.join(dir, '__faapi_probe__.ts'), specifier);\n}\n\n/**\n * 递归收集入口文件的所有项目内 import 依赖(含传递依赖),按是否位于 appDir 内分组\n *\n * 供两类调用方使用:\n * - `compileConfig`:编译 config 源 + 其引用的项目模块,使 config 产物的 import\n * 在运行时能解析到实际文件(与 routes 编译的同一产物共享,instanceof 跨边界生效)\n * - `compileOnDemand.ensureCompiled`:dev 按需编译 handler 时补齐依赖闭包,\n * 否则 handler 引用的共享模块(如 `../../lib/db`)产物不存在,首次请求即\n * ERR_MODULE_NOT_FOUND\n *\n * 解析规则:\n * - 相对 specifier:`resolveRelativeSpecifier` 探测源后缀与 index 文件\n * - 别名 specifier(tsconfig paths,如 `@/lib/db`):`resolveAlias` 解析候选目标后\n * 同样探测源后缀(无 tsconfig/paths 时跳过)\n * - 已带产物后缀(.js/.mjs/.cjs)的 specifier 不递归(视为已编译产物,源码不在此处)\n * - 项目外依赖(node_modules、rootDir 外文件)不收集——按 external 语义运行时解析\n *\n * @param entryFiles 起始文件(绝对路径)\n * @param rootDir 项目根目录\n * @returns 收集到的文件,分为 src 内(打平前缀编译)和 src 外(保留结构编译)两组\n * (绝对路径,去重,不含入口文件本身)\n */\nexport async function collectRelativeImports(\n entryFiles: string[],\n rootDir: string,\n): Promise<{ insideFiles: string[]; outsideFiles: string[] }> {\n // 用 realpath 规范化 src 绝对路径,兼容 macOS 符号链接(esbuild 传入的路径已是 realpath)\n const appDirAbs = toRealPath(path.resolve(rootDir, 'src'));\n const tsconfig = readTsconfig(rootDir);\n const visited = new Set<string>();\n const insideFiles = new Set<string>();\n const outsideFiles = new Set<string>();\n\n async function collect(filePath: string): Promise<void> {\n if (visited.has(filePath)) return;\n visited.add(filePath);\n\n let source: string;\n try {\n source = await fs.promises.readFile(filePath, 'utf8');\n } catch {\n return;\n }\n\n for (const specifier of extractImportSpecifiers(source)) {\n // 已带产物后缀:运行时 import 的就是产物(或第三方 .js),源码不递归\n if (/\\.(js|mjs|cjs)$/.test(specifier)) continue;\n\n let resolved: string | null;\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n resolved = resolveRelativeSpecifier(filePath, specifier);\n } else if (tsconfig) {\n // 别名 specifier:解析候选目标(相对 rootDir)后探测源文件\n resolved = null;\n for (const candidate of resolveAlias(specifier, tsconfig)) {\n const probed = resolveSpecifierFromDir(rootDir, candidate);\n if (probed) {\n resolved = probed;\n break;\n }\n }\n } else {\n resolved = null;\n }\n\n if (!resolved) continue;\n // 项目外文件(node_modules / rootDir 外)不收集。\n // 归类判断用 realpath 归一化两侧(tmpdir 符号链接 /var → /private/var 会误判),\n // 但收集结果保留原始解析路径——esbuild 的 outbase 基于调用方传入的 rootDir 形式,\n // entry 路径必须与 outbase 字符串前缀一致,否则产物落入 _.._ 垃圾目录\n if (!isInsideDir(toRealPath(resolved), toRealPath(path.resolve(rootDir)))) continue;\n\n if (isInsideDir(toRealPath(resolved), appDirAbs)) {\n insideFiles.add(resolved);\n } else {\n outsideFiles.add(resolved);\n }\n await collect(resolved);\n }\n }\n\n for (const entry of entryFiles) {\n await collect(entry);\n }\n\n return {\n insideFiles: Array.from(insideFiles),\n outsideFiles: Array.from(outsideFiles),\n };\n}\n","import { getSchemaName } from './schemaName';\nimport { InternalError } from '../errors/httpErrors';\nimport type { ValidationIssue, ValidationErrorCode } from '../errors/httpErrors';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport { isDevOnDemandEnabled } from '../cli/compileOnDemand';\n\n/**\n * 校验结果类型\n *\n * data 为校验/解析后的原始值(通常为对象;数组 body 为数组,\n * 顶层 primitive body——如 `type POSTBody = string`——为对应原始值)。\n */\nexport interface ValidationResult {\n valid: boolean;\n issues: ValidationIssue[];\n data: unknown;\n}\n\n/**\n * zod.js 模块导出格式\n *\n * 每个 handler 目录下的 zod.js 导出:\n * - `<SchemaName>Schema`:zod schema 对象(用于 safeParse 校验)\n *\n * query/params 的 schema 已在代码生成阶段用 z.preprocess 包裹了字符串转换逻辑\n * (number/boolean 字段),无需运行时再做 coerce。\n *\n * 无类型声明的方法不导出对应 Schema(值为 undefined)。\n */\ninterface SchemaModule {\n [key: string]: unknown;\n}\n\n/**\n * schema 模块缓存:schemaPath → SchemaModule\n *\n * dev 模式下 watch 触发 reloadRoutes 时通过 invalidateSchemaCache() 清空,\n * 下次请求重新 import(importWithCacheBust 会拼接时间戳绕过 ESM 缓存)。\n */\nconst moduleCache = new Map<string, SchemaModule>();\n\n/**\n * 清空 schema 模块缓存(dev watch 模式下文件变化时调用)\n */\nexport function invalidateSchemaCache(): void {\n moduleCache.clear();\n}\n\n/**\n * 加载 schema 模块(带缓存)\n *\n * 首次调用时 import zod.js 并缓存,后续直接返回缓存。\n * dev 模式下 watch 触发 invalidateSchemaCache 后,下次调用重新 import。\n */\nasync function loadSchemaModule(schemaPath: string): Promise<SchemaModule> {\n let mod = moduleCache.get(schemaPath);\n if (!mod) {\n // dev 按需模式下走 Node 原生 import 绕过 Vite SSR 缓存\n // (zod.js 是已编译产物,不需要 Vite alias 解析)\n mod = (await importWithCacheBust(schemaPath, isDevOnDemandEnabled())) as SchemaModule;\n moduleCache.set(schemaPath, mod);\n }\n return mod;\n}\n\n/**\n * 校验输入参数\n *\n * 流程:从 schemaPath import zod.js → zod safeParse\n *\n * schema 来源由调用方确保已生成:\n * - dev 模式:createApp 启动时 + watch 时调 generateSchemaFiles 生成 zod.js\n * - prd 模式:faapi build 时调 generateSchemaFiles 生成 zod.js\n *\n * coerce 说明:\n * - query/params 的 schema 在代码生成阶段已用 z.preprocess 包裹字符串转换逻辑\n * (number: \"1\" → 1,boolean: \"true\" → true),运行时直接 safeParse 即可\n * - body 是 JSON 解析的天然 JS 类型,schema 不含 preprocess\n *\n * 三种状态:\n * - Schema 导出存在:执行 zod safeParse 校验\n * - Schema 导出 undefined(无类型声明):跳过校验\n * - zod.js 文件不存在或 import 失败:抛 InternalError\n *\n * @param schemaPath zod.js 文件绝对路径\n * @param method HTTP 方法\n * @param inputType 输入类型\n * @param input 输入值\n */\nexport async function validateInput(\n schemaPath: string,\n method: string,\n inputType: 'query' | 'body' | 'params',\n input: unknown,\n): Promise<ValidationResult> {\n const schemaName = getSchemaName(method, inputType);\n const schemaKey = `${schemaName}Schema`;\n\n let mod: SchemaModule;\n try {\n mod = await loadSchemaModule(schemaPath);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new InternalError(`Schema 模块加载失败: ${schemaPath}: ${reason}`);\n }\n\n const schema = mod[schemaKey];\n\n // 无类型声明:跳过校验,input 原样透传(数组/原始值 body 不再静默替换为 {})\n if (schema === undefined || schema === null) {\n return { valid: true, issues: [], data: input };\n }\n\n // schema 必须是带 safeParse 的 zod schema\n if (\n typeof schema !== 'object' ||\n typeof (schema as { safeParse?: unknown }).safeParse !== 'function'\n ) {\n throw new InternalError(`Schema 不是有效的 zod schema: ${schemaPath}#${schemaName}`);\n }\n\n // zod safeParse 校验:input 原样传入(schema 生成端支持数组/顶层 primitive body,\n // 预先替换数组会使命中 z.array 的合法 body 永远失败且 issue 误导为 received object)\n // query/params 的 preprocess 已在 schema 生成阶段内联\n const zodSchema = schema as { safeParse: (v: unknown) => ZodSafeParseResult };\n const result = zodSchema.safeParse(input);\n\n if (result.success) {\n // 解析后的数据原样返回(z.array schema 的 data 是数组,顶层 primitive 是原始值)\n return { valid: true, issues: [], data: result.data };\n }\n\n // 将 zod issues 转为 ValidationIssue\n const issues = mapZodIssues(result.error);\n return { valid: false, issues, data: input };\n}\n\n/**\n * zod safeParse 成功结果\n */\ninterface ZodSafeParseSuccess {\n success: true;\n data: unknown;\n}\n\n/**\n * zod safeParse 失败结果\n */\ninterface ZodSafeParseError {\n success: false;\n error: {\n issues: Array<{\n path: (string | number)[];\n code: string;\n expected?: string;\n received?: string;\n message: string;\n }>;\n };\n}\n\ntype ZodSafeParseResult = ZodSafeParseSuccess | ZodSafeParseError;\n\n/**\n * 将 zod v4 error issues 映射为框架的 ValidationIssue\n *\n * zod code → 框架 ValidationErrorCode 映射:\n * - invalid_type(received 非 undefined) / invalid_union → TYPE_MISMATCH(422)\n * - invalid_type(received === 'undefined',即缺失必填字段)→ MISSING_FIELD(400)\n * - unrecognized_keys → INVALID_FORMAT(400)\n * - invalid_value / invalid_format(v4 的 email/url/uuid/regex 等格式检查)/\n * invalid_key / invalid_element(v4 的 record/map 元素检查)/\n * too_small / too_big / invalid_intersection_types / not_multiple_of / custom\n * → INVALID_VALUE(422)\n *\n * path 数组转为 dot 路径(如 ['user', 'address', 'city'] → 'user.address.city')。\n */\nfunction mapZodIssues(error: ZodSafeParseError['error']): ValidationIssue[] {\n return error.issues.map((issue) => {\n const code = mapZodCode(issue);\n const path = issue.path.map(String).join('.') || '';\n return {\n path,\n code,\n expected: issue.expected ?? mapExpectedFromMessage(issue.message),\n received: issue.received ?? mapReceivedFromMessage(issue.message),\n message: issue.message,\n };\n });\n}\n\ninterface ZodIssueLike {\n code: string;\n expected?: string;\n received?: string;\n message: string;\n}\n\n/**\n * 映射 zod v4 issue code 到框架 ValidationErrorCode\n *\n * zod v4 已移除 invalid_string(v3 遗留),格式类检查(email/url/uuid/regex)\n * 产出 invalid_format;record/map 的键/元素检查产出 invalid_key / invalid_element。\n */\nfunction mapZodCode(issue: ZodIssueLike): ValidationErrorCode {\n switch (issue.code) {\n case 'invalid_type':\n // zod v4 对缺失必填字段产出 invalid_type,无独立 missing code;\n // received 字段缺失,\"received undefined\" 只出现在 message 中\n if (issue.received === 'undefined' || /received undefined/i.test(issue.message)) {\n return 'MISSING_FIELD';\n }\n return 'TYPE_MISMATCH';\n case 'invalid_union':\n case 'invalid_union_discriminator':\n return 'TYPE_MISMATCH';\n case 'unrecognized_keys':\n return 'INVALID_FORMAT';\n case 'invalid_value':\n case 'invalid_format':\n case 'invalid_key':\n case 'invalid_element':\n case 'too_small':\n case 'too_big':\n case 'invalid_intersection_types':\n case 'not_multiple_of':\n case 'custom':\n return 'INVALID_VALUE';\n default:\n return 'INVALID_VALUE';\n }\n}\n\n/**\n * 从 zod message 中提取期望类型(兜底)\n */\nfunction mapExpectedFromMessage(message: string): string {\n // zod v3 message 示例:\"Expected string, received number\"\n const match = message.match(/Expected\\s+(\\w+)/i);\n return match ? match[1]!.toLowerCase() : 'unknown';\n}\n\n/**\n * 从 zod message 中提取实际类型(兜底)\n */\nfunction mapReceivedFromMessage(message: string): string {\n const match = message.match(/received\\s+(\\w+)/i);\n return match ? match[1]!.toLowerCase() : 'unknown';\n}\n","import {\n createServer as createHttpServer,\n type Server,\n type IncomingMessage,\n type ServerResponse,\n} from 'node:http';\nimport { createSecureServer as createHttp2SecureServer } from 'node:http2';\nimport { readFileSync } from 'node:fs';\nimport { Readable } from 'node:stream';\nimport path from 'node:path';\nimport type { RouteManifest, RouteMatch, WsRouteManifest, RoutesRef } from '../router/routeTypes';\nimport { matchRoute, findAllowedMethods } from '../router/matchRoute';\nimport { loadRouteModule } from '../loader/loadRouteModule';\nimport { createContextFromUrl } from '../runtime/createContext';\nimport { resolveInputFromUrl, resolveBodyForQueryMethod } from '../runtime/resolveInput';\nimport { invokeHandler, compose, mergeMeta } from '../runtime/invokeHandler';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { sendNodeResponse } from '../response/sendNodeResponse';\nimport {\n RouteNotFoundError,\n MethodNotAllowedError,\n ValidationError,\n PayloadTooLargeError,\n} from '../errors/httpErrors';\nimport { validateInput } from '../validator/validateInput';\nimport { getInputTypeForMethod, hasBody } from '../runtime/inputType';\nimport { getClientIp } from '../utils/getClientIp';\nimport { cors, type CorsOptions } from '../middleware/cors';\nimport { helmet, type HelmetOptions } from '../middleware/helmet';\nimport { compression, type CompressionOptions } from '../middleware/compression';\nimport { etag, type EtagOptions } from '../middleware/etag';\nimport type { AppRegistries } from '../injection/registries';\nimport { logger as loggerMiddleware, type LoggerOptions } from '../middleware/logger';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { attachWebSocket } from './handleWsUpgrade';\nimport { nodeHttpToWebHeaders, buildErrorResponse } from './serverUtils';\nimport { getRuntimeSchemaPath } from '../cli/generateSchemaFiles';\nimport {\n ensureSchemaGenerated,\n ensureMiddlewaresCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n} from '../cli/compileOnDemand';\nimport { loadMergedMiddlewares } from '../middleware/loadMiddlewares';\n\n/**\n * 将 Node.js IncomingMessage 转为 Web Request 对象\n *\n * 协议判断:\n * 1. 优先使用 X-Forwarded-Proto 头(反向代理场景)\n * 2. 回退到 http(HTTPS 由外部代理处理)\n */\nconst DEFAULT_BODY_LIMIT = 10 * 1024 * 1024; // 10MB\n\nfunction toWebRequest(\n req: IncomingMessage,\n bodyLimit: number = DEFAULT_BODY_LIMIT,\n): {\n request: Request;\n /** 已解析的 URL(pathname/searchParams 由调用方复用,免重复 new URL) */\n url: URL;\n} {\n // 协议判断:优先 X-Forwarded-Proto(反向代理),否则 http\n const forwardedProto = req.headers['x-forwarded-proto'];\n const protocol = Array.isArray(forwardedProto)\n ? (forwardedProto[0]?.split(',')[0]?.trim() ?? 'http')\n : (forwardedProto?.split(',')[0]?.trim() ?? 'http');\n const host = req.headers.host ?? 'localhost';\n // 全请求唯一一次 URL 解析——pathname/searchParams 由 ctx/routePipeline 复用\n const url = new URL(req.url ?? '/', `${protocol}://${host}`);\n\n const headers = nodeHttpToWebHeaders(req);\n\n const method = req.method ?? 'GET';\n\n // GET/HEAD 不应该有 body\n if (method === 'GET' || method === 'HEAD') {\n return { request: new Request(url.toString(), { method, headers }), url };\n }\n\n // content-length 快速判定:声明长度超限直接抛 PayloadTooLargeError,\n // 免去流包装 + 逐 chunk 读取(chunked 无此头,仍走 limitStreamSize 流式限流)\n const contentLength = req.headers['content-length'];\n if (contentLength !== undefined) {\n const declared = Number(Array.isArray(contentLength) ? contentLength[0] : contentLength);\n if (Number.isFinite(declared) && declared > bodyLimit) {\n throw new PayloadTooLargeError(bodyLimit);\n }\n }\n\n // 将 Node.js IncomingMessage 转为 Web ReadableStream\n // 并限制请求体大小(防止 DoS)\n const stream = Readable.toWeb(req) as ReadableStream<Uint8Array>;\n const limitedStream = limitStreamSize(stream, bodyLimit);\n return {\n request: new Request(url.toString(), {\n method,\n headers,\n body: limitedStream,\n duplex: 'half',\n } as RequestInit),\n url,\n };\n}\n\n/**\n * 限制 ReadableStream 的总字节数,超过限制时通过 controller.error 抛 PayloadTooLargeError\n *\n * 错误传播路径:controller.error → Request body 读取方(resolveInput)抛 →\n * handleRequest catch → formatErrorResponse(命中 PayloadTooLargeError 分支)→ 413 响应\n *\n * 健壮性处理:\n * - reader.read() 抛错(客户端断开等)→ controller.error + releaseLock,避免泄漏\n * - 超限或异常后释放 reader lock,避免悬挂引用\n * - 取消时同步 cancel 上游 reader\n */\nfunction limitStreamSize(\n stream: ReadableStream<Uint8Array>,\n maxSize: number,\n): ReadableStream<Uint8Array> {\n let totalSize = 0;\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n let errored = false;\n\n const releaseReader = (): void => {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // 锁已释放或 reader 已 closed,忽略\n }\n reader = undefined;\n }\n };\n\n const failStream = (\n controller: ReadableStreamDefaultController<Uint8Array>,\n err: unknown,\n ): void => {\n if (errored) return;\n errored = true;\n controller.error(err instanceof Error ? err : new Error(String(err)));\n releaseReader();\n };\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (!reader) reader = stream.getReader();\n try {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n releaseReader();\n return;\n }\n totalSize += value.byteLength;\n if (totalSize > maxSize) {\n // error 让流进入 errored 状态,下游 read() 会 reject\n failStream(controller, new PayloadTooLargeError(maxSize));\n return;\n }\n controller.enqueue(value);\n } catch (err) {\n // reader.read() 抛错(客户端断开、底层流异常等)\n failStream(controller, err);\n }\n },\n cancel(reason) {\n if (reader) {\n try {\n reader.cancel(reason);\n } catch {\n // 忽略上游 cancel 失败\n }\n releaseReader();\n }\n },\n });\n}\n\n/**\n * 查找路径的所有允许方法(用于 405 响应)\n *\n * 实现移至 [matchRoute](../router/matchRoute.ts)(共享路由索引:静态段 O(1) 直查,\n * 仅动态段线性扫描)。404 非热路径,但扫描器/探活探测的 404 高频场景下\n * 索引化仍有收益。\n */\n\nexport interface CreateServerOptions {\n routes: RouteManifest;\n rootDir: string;\n /** 产物输出目录(如 '.faapi' 或 'dist'),用于计算 schema 路径 */\n dist: string;\n cors?: CorsOptions | boolean;\n /** 请求错误钩子(在错误响应生成后调用,用于副作用;不修改已发出的响应) */\n onError?: (error: unknown, ctx: FaapiContext) => Promise<void> | void;\n /** 自定义业务配置(来自 faapi.config.ts,注入到 ctx.config) */\n config?: Record<string, unknown>;\n /** WebSocket 路由清单(空数组则不挂载 WS 支持) */\n wsRoutes?: WsRouteManifest;\n /** 全局中间件(来自 faapi.config.ts,对所有路由生效,最外层) */\n middlewares?: FaapiMiddleware[];\n /** 全局注入器(来自 faapi.config.ts,对所有路由 handler 参数注入生效) */\n injectors?: InjectorMap;\n /** 安全头配置 */\n helmet?: HelmetOptions | boolean;\n /** 响应压缩(gzip/deflate/br 协商),默认关闭,详见 middleware/compression.md */\n compression?: CompressionOptions | boolean;\n /** ETag/304 条件请求协商,默认关闭,详见 middleware/etag.md */\n etag?: EtagOptions | boolean;\n /** app 级注册表(tool/agent/skill/agentHandle)——经 FaapiContext 进入请求链路 */\n registries?: AppRegistries;\n /** 请求日志配置,默认启用(与 cors 一致) */\n logger?: LoggerOptions | boolean;\n /** 请求体大小限制(字节) */\n bodyLimit?: number;\n /** HTTP/2 配置,启用时需提供 SSL 证书路径 */\n http2?: Http2Options | boolean;\n /**\n * 是否信任反向代理头(X-Forwarded-For),默认 false\n *\n * true 时 `ctx.ip` 取 `x-forwarded-for` 第一个 IP(nginx/CDN 场景);\n * false(默认)时直取 socket 地址——直连部署下 XFF 可被伪造,安全默认不信任。\n */\n trustedProxy?: boolean;\n}\n\nexport interface Http2Options {\n key?: string;\n cert?: string;\n}\n\n/**\n * 创建 faapi HTTP server\n *\n * @param options 路由清单、根目录\n * @returns Node.js Server 实例\n */\nexport function createServer(options: CreateServerOptions): {\n server: Server;\n routesRef: RoutesRef;\n} {\n const {\n routes,\n rootDir,\n dist,\n cors: corsOption,\n onError,\n config,\n wsRoutes,\n middlewares: globalMiddlewares,\n injectors: globalInjectors,\n helmet: helmetOption,\n compression: compressionOption,\n etag: etagOption,\n registries,\n logger: loggerOption,\n bodyLimit = DEFAULT_BODY_LIMIT,\n http2: http2Option,\n trustedProxy = false,\n } = options;\n\n // 路由可变引用容器(watch 模式热替换时 reloadRoutes 更新 .current/.wsCurrent)\n const routesRef: RoutesRef = { current: routes, wsCurrent: wsRoutes ?? [] };\n\n // Build middleware chain from config options\n const configMiddlewares: FaapiMiddleware[] = [];\n\n // Compression — 显式启用;链最外层,包住完整链路使最终响应被压缩\n if (compressionOption) {\n const compOpts = typeof compressionOption === 'object' ? compressionOption : {};\n configMiddlewares.push(compression(compOpts));\n }\n\n // CORS\n const corsMiddleware: FaapiMiddleware | null =\n corsOption === false\n ? null\n : corsOption === true || corsOption === undefined\n ? cors()\n : cors(corsOption);\n if (corsMiddleware) configMiddlewares.push(corsMiddleware);\n\n // Helmet — enabled only when explicitly configured\n if (helmetOption) {\n const helmOpts = typeof helmetOption === 'object' ? helmetOption : {};\n configMiddlewares.push(helmet(helmOpts));\n }\n\n // Logger — 默认启用(与 cors 一致),false 禁用,LoggerOptions 自定义\n const loggerMiddlewareInst: FaapiMiddleware | null =\n loggerOption === false\n ? null\n : loggerOption === true || loggerOption === undefined\n ? loggerMiddleware()\n : loggerMiddleware(loggerOption);\n if (loggerMiddlewareInst) configMiddlewares.push(loggerMiddlewareInst);\n\n // ETag — 显式启用;位于 compression 内层:先算 ETag/304 再压缩,\n // 弱 ETag 基于未压缩表示计算(304 无 body 时压缩自动跳过)\n if (etagOption) {\n const etagOpts = typeof etagOption === 'object' ? etagOption : {};\n configMiddlewares.push(etag(etagOpts));\n }\n\n // 外层中间件链启动期组装一次(CORS → helmet → logger → 全局),\n // 每请求不再重复 spread 重组数组\n const outerMiddlewares: FaapiMiddleware[] = [...configMiddlewares];\n if (globalMiddlewares && globalMiddlewares.length > 0) {\n outerMiddlewares.push(...globalMiddlewares);\n }\n\n const server = ((): Server => {\n if (http2Option) {\n const h2Opts = typeof http2Option === 'object' ? http2Option : {};\n return createHttp2SecureServer({\n key: h2Opts.key ? readFileSync(h2Opts.key) : undefined,\n cert: h2Opts.cert ? readFileSync(h2Opts.cert) : undefined,\n allowHTTP1: true,\n }) as unknown as Server;\n }\n return createHttpServer();\n })();\n\n server.on('request', (req: IncomingMessage, res: ServerResponse) => {\n // 每次请求读取最新的路由状态(支持 watch 模式热更新)\n const currentRoutes = routesRef.current;\n\n handleRequest(\n currentRoutes,\n rootDir,\n dist,\n req,\n res,\n outerMiddlewares,\n onError,\n config,\n globalInjectors,\n bodyLimit,\n trustedProxy,\n registries,\n ).catch(() => {\n res.statusCode = 500;\n res.end();\n });\n });\n\n // 挂载 WebSocket 升级处理(无条件挂载:upgrade 处理器内部对无匹配路由返回 404,\n // 空清单成本一次函数调用。若按初始清单条件挂载,dev watch 中新增第一个 WS 路由后\n // upgrade 监听器不会补挂,WS 路由永远 404)\n attachWebSocket({\n server,\n routesRef,\n rootDir,\n config,\n globalMiddlewares,\n trustedProxy,\n registries,\n });\n\n return { server, routesRef };\n}\n\n/**\n * 准备请求上下文:Node IncomingMessage → Web Request + FaapiContext\n *\n * 提取为独立函数,让 handleRequest 主流程聚焦于路由 + 中间件调度,\n * 便于单测与未来扩展(如自定义 context 字段来源)。\n *\n * URL 全请求只解析一次(toWebRequest 内),pathname/searchParams\n * 由 ctx / routePipeline 共享,避免一次请求重复 new URL 3~4 次。\n */\nfunction prepareRequest(\n req: IncomingMessage,\n config: Record<string, unknown> | undefined,\n bodyLimit: number,\n trustedProxy: boolean,\n registries?: AppRegistries,\n): {\n request: Request;\n url: URL;\n ctx: FaapiContext;\n meta: ResponseMeta;\n method: string;\n urlPath: string;\n} {\n const { request, url } = toWebRequest(req, bodyLimit);\n const method = request.method.toUpperCase();\n const urlPath = url.pathname;\n const ctx = createContextFromUrl(\n request,\n url,\n {},\n config,\n getClientIp(req, trustedProxy),\n registries,\n );\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n return { request, url, ctx, meta, method, urlPath };\n}\n\n/**\n * 路由匹配——命中返回 MatchResult,未命中抛 RouteNotFoundError / MethodNotAllowedError\n *\n * 抛错语义让调用方 try/catch 即可,无需在主流程里分支处理 null。\n */\nfunction resolveRouteOrThrow(routes: RouteManifest, method: string, urlPath: string): RouteMatch {\n const match = matchRoute(routes, method, urlPath);\n if (match) return match;\n\n // 检查是否有其他方法匹配该路径 → 405\n const allowedMethods = findAllowedMethods(routes, urlPath);\n if (allowedMethods.length > 0) {\n throw new MethodNotAllowedError(method, urlPath, allowedMethods);\n }\n\n // 路由未匹配 → 404\n throw new RouteNotFoundError(urlPath);\n}\n\n/**\n * 路由执行管线:作为外层中间件链的 finalHandler\n *\n * 包含:路由匹配 → handler.js 加载 → 参数解析 → zod.js 按需生成 + 校验 →\n * 中间件按需加载 → 注入器合并 → handler 调用。\n *\n * 错误抛出由外层 handleRequest 的 try/catch 接管,经 formatErrorResponse 转换为响应。\n */\n// 路由静态派生路径缓存(绝对路径 / schema 路径对固定 route 恒定,免每请求字符串运算)。\n// WeakMap 按 route 对象弱键——reloadRoutes 整体替换清单后旧对象可 GC,无需手动失效\nconst routePathCache = new WeakMap<\n RouteManifest[number],\n { absFilePath: string; schemaPath: string }\n>();\n\nfunction getRoutePaths(\n route: RouteManifest[number],\n rootDir: string,\n dist: string,\n): { absFilePath: string; schemaPath: string } {\n let cached = routePathCache.get(route);\n if (!cached) {\n cached = {\n absFilePath: path.resolve(rootDir, route.filePath),\n schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir),\n };\n routePathCache.set(route, cached);\n }\n return cached;\n}\n\nfunction createRoutePipeline(opts: {\n routes: RouteManifest;\n method: string;\n urlPath: string;\n url: URL;\n ctx: FaapiContext;\n request: Request;\n rootDir: string;\n dist: string;\n globalInjectors: InjectorMap | undefined;\n}): () => Promise<Response> {\n const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;\n return async () => {\n // 1. 路由匹配(未命中抛 RouteNotFound / MethodNotAllowed)\n const match = resolveRouteOrThrow(routes, method, urlPath);\n ctx.params = match.params;\n const { route } = match;\n const { absFilePath, schemaPath } = getRoutePaths(route, rootDir, dist);\n\n // 2. 加载 handler.js(dev 按需编译 + import,prod 直接 import)\n const routeModule = await loadRouteModule(absFilePath, route.method, rootDir);\n\n // 3. 参数解析(query / body / form / files 等)——复用已解析的 URL\n const input = await resolveInputFromUrl(route.method, request, url);\n\n // 4. schema 校验(运行时按 route.filePath 计算 zod.js 路径 + safeParse)\n const inputType = getInputTypeForMethod(route.method);\n // schemaPath 已在 getRoutePaths 中缓存\n\n // Dev 按需模式:zod.js 不存在或 stale 时触发按需生成\n if (isDevOnDemandEnabled()) {\n const devDist = getDevDist();\n if (devDist) {\n await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist);\n }\n }\n\n const result = await validateInput(schemaPath, route.method, inputType, input);\n if (!result.valid) {\n throw new ValidationError('参数校验失败', result.issues);\n }\n // body 计算与主输入分流:\n // - POST/PUT/PATCH:主输入就是 body,用校验后的值\n // - DELETE:主输入是 query(校验 DELETEQuery),body 单独解析注入——\n // 若把校验后的 query 当 body 传入,handler 声明 body 会静默拿到 query;\n // 同时请求体流不被消费,keep-alive 连接无法复用\n // - GET/HEAD:无 body\n const body =\n inputType === 'query' && hasBody(route.method)\n ? await resolveBodyForQueryMethod(request)\n : hasBody(route.method)\n ? result.data\n : undefined;\n\n // 5. 中间件按需加载(Vite 风格):route.middlewares 为 undefined 时从 middlewarePaths 加载\n // 首次请求加载后缓存到 route 上,后续请求直接复用\n if (route.middlewares === undefined && route.injectors === undefined && route.middlewarePaths) {\n // dev 按需模式:先编译中间件源码(含依赖闭包)再 import\n await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);\n const bundle = await loadMergedMiddlewares(route.middlewarePaths);\n if (bundle) {\n route.middlewares = bundle.middlewares;\n route.injectors = bundle.injectors;\n } else {\n // 标记为已加载(空中间件),避免重复加载\n route.middlewares = [];\n route.injectors = {};\n }\n }\n\n // 6. 注入器合并:全局注入器为基线,目录注入器覆盖同名\n const mergedInjectors = globalInjectors\n ? { ...globalInjectors, ...route.injectors }\n : route.injectors;\n\n // 7. handler 调用(含目录中间件洋葱模型 + 自动响应包装)\n return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);\n };\n}\n\n/**\n * 发送成功响应\n */\nasync function sendSuccessResponse(response: Response, res: ServerResponse): Promise<void> {\n await sendNodeResponse(response, res);\n}\n\n/**\n * 发送错误响应 + 触发 onError 副作用\n *\n * 错误处理兜底链(参考 Fastify 语义):\n * 1. 框架内置 formatErrorResponse 兜底(handler 抛错时)——读 ctx.config.response.fail\n * 自定义包装函数,确保错误格式与 ctx.fail() 主动错误响应一致\n * 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n * 3. 响应发出后 → onError 触发副作用(不修改已发出的响应)\n * 注:业务方如需进一步自定义错误响应,在全局中间件中 try/catch next() 即可\n *\n * `ctx` 可为 undefined——请求准备阶段抛错(如 content-length 超限)时尚未构造 ctx,\n * 此时错误格式用默认 fail 包装,onError 不触发(与原\"裸 500\"路径一致)。\n */\nasync function sendErrorResponse(\n err: unknown,\n meta: ResponseMeta,\n res: ServerResponse,\n onError: ((error: unknown, ctx: FaapiContext) => Promise<void> | void) | undefined,\n ctx: FaapiContext | undefined,\n): Promise<void> {\n await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx?.config), meta), res);\n\n // 响应已发出,触发 onError 副作用(日志/告警/链路追踪),自身抛错被忽略\n if (onError && ctx) {\n try {\n await onError(err, ctx);\n } catch {\n // onError 自身抛错不影响已发出的响应\n }\n }\n}\n\nasync function handleRequest(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n req: IncomingMessage,\n res: ServerResponse,\n outerMiddlewares: FaapiMiddleware[],\n onError: ((error: unknown, ctx: FaapiContext) => Promise<void> | void) | undefined,\n config: Record<string, unknown> | undefined,\n globalInjectors: InjectorMap | undefined,\n bodyLimit: number,\n trustedProxy: boolean,\n registries?: AppRegistries,\n): Promise<void> {\n // meta/ctx 兜底:请求准备阶段抛错(如 content-length 超限的 413)时尚无 ctx\n let meta: ResponseMeta = { headers: {}, setCookies: [] };\n let ctx: FaapiContext | undefined;\n try {\n // 1. 准备请求上下文(toWebRequest + createContext)——URL 全请求只解析一次\n const prepared = prepareRequest(req, config, bodyLimit, trustedProxy, registries);\n ctx = prepared.ctx;\n meta = prepared.meta;\n const { request, url, method, urlPath } = prepared;\n\n // 2. 创建路由执行管线(路由匹配 + 校验 + 中间件加载 + handler 调用)\n const routePipeline = createRoutePipeline({\n routes,\n method,\n urlPath,\n url,\n ctx,\n request,\n rootDir,\n dist,\n globalInjectors,\n });\n\n // 3. 执行外层中间件链(CORS → helmet → logger → 全局 → routePipeline,\n // 数组已在 createServer 启动期组装,此处仅按需 compose)\n const response =\n outerMiddlewares.length > 0\n ? await compose(outerMiddlewares, ctx, routePipeline)\n : await routePipeline();\n // 4. 发送响应\n await sendSuccessResponse(response, res);\n } catch (err: unknown) {\n // 客户端已断开或响应已完成:网络中断/连接销毁不是服务端错误,\n // 不向已销毁的连接写 500(写入无效),也不触发 onError 误报\n if (res.destroyed || res.writableEnded) return;\n await sendErrorResponse(err, meta, res, onError, ctx);\n }\n}\n","import type { RouteManifest, RouteMatch, WsRouteManifest, WsRouteMatch } from './routeTypes';\n\n/**\n * 路由匹配索引——把 O(n) 线性扫描降为静态路由 O(1) 命中\n *\n * 每个请求都要做路由匹配,原实现逐条遍历清单(静态路由字符串比较、动态路由\n * split('/') 比对),大清单下是热路径开销。索引把清单分为两部分:\n * - **静态路由**:`method|urlPath` → route 的 Map,一次查找命中\n * - **动态路由**:保持清单顺序的数组(`sortRoutes` 保证静态恒在动态之前,\n * 且同一 `(method, path)` 至多一条静态路由,静态优先查与原 first-match\n * 语义完全等价)\n *\n * 索引按**清单数组身份**缓存(WeakMap):`reloadRoutes` / `hydrateRoutes`\n * 整体替换清单数组(`routesRef.current = newRoutes`),旧数组被替换后索引\n * 自动失效可被 GC,无需手动清理——无生命周期侵入。\n *\n * 详见 [matchRoute.md](./matchRoute.md)。\n */\n\n/**\n * HTTP 路由匹配索引\n */\ninterface HttpRoutesIndex {\n /** 静态路由:`method|urlPath` → route */\n static: Map<string, RouteManifest[number]>;\n /** 静态路径 → 方法集合(405 findAllowedMethods 反查用,免再扫静态段) */\n methodsByStaticPath: Map<string, Set<string>>;\n /** 动态路由(含 catch-all),保持清单顺序,pattern 已预编译 */\n dynamics: DynamicEntry[];\n}\n\n/** 动态路由条目(pattern segments 预编译,免去每请求对固定 pattern 重复 split) */\ninterface DynamicEntry {\n route: RouteManifest[number];\n /** 预编译的模式段(pattern.split('/').filter(Boolean),对固定 route 恒定) */\n segments: string[];\n}\n\n/** WS 路由匹配索引(无 HTTP 方法维度) */\ninterface WsRoutesIndex {\n /** 静态路由:urlPath → route */\n static: Map<string, WsRouteManifest[number]>;\n /** 动态路由(含 catch-all),保持清单顺序 */\n dynamics: WsRouteManifest;\n}\n\nconst httpIndexCache = new WeakMap<RouteManifest, HttpRoutesIndex>();\nconst wsIndexCache = new WeakMap<WsRouteManifest, WsRoutesIndex>();\n\n/** 构建 HTTP 路由索引(惰性,每个清单数组只构建一次) */\nfunction getHttpIndex(routes: RouteManifest): HttpRoutesIndex {\n let index = httpIndexCache.get(routes);\n if (index) return index;\n\n index = { static: new Map(), methodsByStaticPath: new Map(), dynamics: [] };\n for (const route of routes) {\n if (route.isDynamic) {\n index.dynamics.push({\n route,\n segments: route.urlPath.split('/').filter(Boolean),\n });\n } else {\n index.static.set(`${route.method}|${route.urlPath}`, route);\n let methods = index.methodsByStaticPath.get(route.urlPath);\n if (!methods) {\n methods = new Set();\n index.methodsByStaticPath.set(route.urlPath, methods);\n }\n methods.add(route.method);\n }\n }\n\n httpIndexCache.set(routes, index);\n return index;\n}\n\n/** 构建 WS 路由索引(惰性,每个清单数组只构建一次) */\nfunction getWsIndex(routes: WsRouteManifest): WsRoutesIndex {\n let index = wsIndexCache.get(routes);\n if (index) return index;\n\n index = { static: new Map(), dynamics: [] };\n for (const route of routes) {\n if (route.isDynamic) {\n index.dynamics.push(route);\n } else {\n index.static.set(route.urlPath, route);\n }\n }\n\n wsIndexCache.set(routes, index);\n return index;\n}\n\n/**\n * 根据请求路径和方法匹配路由\n *\n * HEAD 回退:无显式 HEAD 路由时复用 GET handler(Node ServerResponse 对 HEAD\n * 自动丢弃 body)。探活、CDN 健康检查、HTTP 客户端预检常用 HEAD——没有回退时\n * 只定义 GET 的路由对 HEAD 返回 405,监控大面积误报。\n *\n * @param routes 已排序的路由清单\n * @param method HTTP 方法\n * @param path 请求路径\n * @returns 匹配结果,包含路由记录和参数;无匹配返回 null\n */\nexport function matchRoute(routes: RouteManifest, method: string, path: string): RouteMatch | null {\n const index = getHttpIndex(routes);\n const upper = method.toUpperCase();\n\n const hit = matchByMethod(index, upper, path);\n if (hit) return hit;\n\n if (upper === 'HEAD') {\n return matchByMethod(index, 'GET', path);\n }\n\n return null;\n}\n\n/** 按方法匹配(静态 O(1) → 动态线性),matchRoute 内部复用(HEAD 回退 GET) */\nfunction matchByMethod(index: HttpRoutesIndex, method: string, path: string): RouteMatch | null {\n // 静态路由 O(1) 命中(sortRoutes 保证静态优先,等价于原遍历的首个命中)\n const staticHit = index.static.get(`${method}|${path}`);\n if (staticHit) {\n return { route: staticHit, params: {} };\n }\n\n // 动态路由按清单顺序匹配(pattern segments 已预编译)\n for (const entry of index.dynamics) {\n const route = entry.route;\n if (route.method !== method) {\n continue;\n }\n const params = matchSegments(entry.segments, path, route.paramNames, route.isCatchAll);\n if (params !== null) {\n return { route, params };\n }\n }\n\n return null;\n}\n\n/**\n * 匹配 WebSocket 路由(无 HTTP 方法维度)\n *\n * WS 路由只按路径匹配,协议升级时调用。\n * @param wsRoutes WebSocket 路由清单\n * @param path 请求路径\n */\nexport function matchWsRoute(wsRoutes: WsRouteManifest, path: string): WsRouteMatch | null {\n const index = getWsIndex(wsRoutes);\n\n const staticHit = index.static.get(path);\n if (staticHit) {\n return { route: staticHit, params: {} };\n }\n\n for (const route of index.dynamics) {\n const params = matchDynamicPath(route.urlPath, path, route.paramNames, route.isCatchAll);\n if (params !== null) {\n return { route, params };\n }\n }\n\n return null;\n}\n\n/**\n * 查找路径的所有允许方法(用于 405 响应)\n *\n * 静态段直接查索引(O(1)),仅动态段线性扫描——原实现对全部路由做\n * O(n) 遍历 + 静态路径比较,404/405 高频场景(扫描器、探活探测)开销显著。\n */\nexport function findAllowedMethods(routes: RouteManifest, path: string): string[] {\n const index = getHttpIndex(routes);\n const methods = new Set<string>();\n\n // 静态路由:索引直查\n const staticMethods = index.methodsByStaticPath.get(path);\n if (staticMethods) {\n for (const method of staticMethods) {\n methods.add(method);\n }\n }\n\n // 动态路由:线性扫描(segments 已预编译)\n for (const entry of index.dynamics) {\n const params = matchSegments(\n entry.segments,\n path,\n entry.route.paramNames,\n entry.route.isCatchAll,\n );\n if (params !== null) {\n methods.add(entry.route.method);\n }\n }\n\n // RFC 9110:GET 允许时 HEAD 必然允许(matchRoute 有 HEAD→GET 回退),\n // 405 的 Allow 头如实反映,避免客户端误判 HEAD 不可用\n if (methods.has('GET')) {\n methods.add('HEAD');\n }\n\n return Array.from(methods);\n}\n\n/**\n * 动态路径匹配\n * 将路由模式(如 /user/:id)与请求路径(如 /user/123)匹配\n * 支持 catch-all 路由(如 /shop/:...slug 匹配 /shop/clothes/tops)\n * 返回提取的参数对象,不匹配返回 null\n */\nexport function matchDynamicPath(\n pattern: string,\n path: string,\n paramNames: string[],\n isCatchAll?: boolean,\n): Record<string, string> | null {\n return matchSegments(pattern.split('/').filter(Boolean), path, paramNames, isCatchAll);\n}\n\n/**\n * 基于**预编译 segments** 的动态匹配(热路径内部用)\n *\n * 与 matchDynamicPath 语义一致,但模式段由索引构建期一次性 split,\n * 免去每请求对固定 pattern 重复字符串分割\n */\nfunction matchSegments(\n patternSegments: string[],\n path: string,\n paramNames: string[],\n isCatchAll?: boolean,\n): Record<string, string> | null {\n const pathSegments = path.split('/').filter(Boolean);\n\n // catch-all 路由:最后一个模式段为 :...slug\n if (isCatchAll) {\n // catch-all 前面的静态/动态段必须匹配\n // catch-all 段至少匹配一个路径段\n const nonCatchAllCount = patternSegments.length - 1;\n if (pathSegments.length <= nonCatchAllCount) {\n return null;\n }\n\n const params: Record<string, string> = {};\n\n for (let i = 0; i < nonCatchAllCount; i++) {\n // i < nonCatchAllCount ≤ 两数组长度,索引必然存在\n const patternSeg = patternSegments[i]!;\n const pathSeg = pathSegments[i]!;\n\n if (patternSeg.startsWith(':')) {\n const paramName = patternSeg.slice(1);\n params[paramName] = pathSeg;\n } else if (patternSeg !== pathSeg) {\n return null;\n }\n }\n\n // catch-all 段:剩余所有路径段用 / 连接\n const catchAllValue = pathSegments.slice(nonCatchAllCount).join('/');\n // 前置检查 pathSegments.length > nonCatchAllCount 保证 catch-all 段存在\n const catchAllParamName = patternSegments[nonCatchAllCount]!.slice(4); // 去掉 ':...'\n params[catchAllParamName] = catchAllValue;\n\n if (Object.keys(params).length !== paramNames.length) {\n return null;\n }\n\n return params;\n }\n\n // 普通动态路由:段数必须一致\n if (patternSegments.length !== pathSegments.length) {\n return null;\n }\n\n const params: Record<string, string> = {};\n\n for (let i = 0; i < patternSegments.length; i++) {\n // 段数一致性检查后索引必然存在\n const patternSeg = patternSegments[i]!;\n const pathSeg = pathSegments[i]!;\n\n if (patternSeg.startsWith(':')) {\n // 动态段,提取参数值\n const paramName = patternSeg.slice(1);\n params[paramName] = pathSeg;\n } else if (patternSeg !== pathSeg) {\n // 静态段不匹配\n return null;\n }\n }\n\n // 确保所有参数名都被提取到\n if (Object.keys(params).length !== paramNames.length) {\n return null;\n }\n\n return params;\n}\n","/**\n * 从模块中解析出指定名称的导出\n *\n * 解析顺序:\n * 1. 具名导出:export function GET() {}\n * 2. 默认导出的对象属性:export default { GET() {} }\n *\n * @param module 动态 import 得到的模块对象\n * @param exportName 导出名(如 'GET')\n */\nexport function resolveExport(module: Record<string, unknown>, exportName: string): unknown {\n // 1. 具名导出\n if (exportName in module && typeof module[exportName] !== 'undefined') {\n return module[exportName];\n }\n\n // 2. 默认导出的对象属性\n const defaultExport = module.default;\n if (defaultExport !== null && typeof defaultExport === 'object') {\n const value = (defaultExport as Record<string, unknown>)[exportName];\n if (value !== undefined) {\n return value;\n }\n }\n\n return undefined;\n}\n","/**\n * 校验导出值是否为合法的 handler 函数\n */\nexport function validateRouteModule(\n value: unknown,\n method: string,\n filePath: string,\n): asserts value is (...args: unknown[]) => unknown {\n if (typeof value !== 'function') {\n throw new Error(\n `Route module \"${filePath}\" does not export a valid handler for method \"${method}\". ` +\n `Expected a function, got ${typeof value}.`,\n );\n }\n}\n","import type { HttpMethod } from '../router/constants';\nimport { resolveExport } from './resolveExports';\nimport { validateRouteModule } from './validateRouteModule';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport {\n ensureCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n prodPathToSourcePath,\n} from '../cli/compileOnDemand';\n\nexport interface RouteModule {\n handler: (...args: unknown[]) => unknown;\n method: HttpMethod;\n}\n\n/**\n * 动态 import 路由文件并提取 handler\n *\n * Dev 按需编译模式(Vite 风格):先确保编译再 import,避免 import 不存在的文件。\n * Prod 模式:产物在 build 阶段已固化,直接 import,失败即报错。\n *\n * 错误传递:\n * - 编译失败 → 抛出编译错误(\"Failed to compile route module\")\n * - import 失败 → 抛出加载错误(\"Failed to load route module\")\n *\n * @param filePath 路由文件的绝对路径\n * @param method HTTP 方法名(也是导出名)\n * @param rootDir 项目根目录(按需编译模式用,可选)\n */\nexport async function loadRouteModule(\n filePath: string,\n method: HttpMethod,\n rootDir?: string,\n): Promise<RouteModule> {\n // Dev 按需模式:先确保编译再 import\n // 避免\"import 失败 → 编译 → 重试 import\"模式——首次 import 不存在的文件会污染\n // Vite SSR 内部状态,导致编译创建文件后重试 import 仍失败(CI Linux 上复现)\n //\n // 不在此处 existsSync(sourcePath):ensureCompiled 内部对缺失源文件直接返回 false,\n // 外层检查是冗余的每请求同步 IO(编译完成一次后内存 Set 命中即短路)\n if (isDevOnDemandEnabled() && rootDir) {\n const dist = getDevDist();\n if (dist) {\n const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);\n if (sourcePath) {\n try {\n await ensureCompiled(sourcePath, rootDir, dist);\n } catch (compileErr) {\n const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);\n throw new Error(`Failed to compile route module \"${sourcePath}\": ${reason}`, {\n cause: compileErr,\n });\n }\n }\n }\n }\n\n // import 模块(dev 按需模式下走 Node 原生 import 绕过 Vite SSR 缓存)\n let module: Record<string, unknown>;\n try {\n module = await importWithCacheBust(filePath, isDevOnDemandEnabled());\n } catch (err: unknown) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to load route module \"${filePath}\": ${reason}`, { cause: err });\n }\n\n const handler = resolveExport(module, method);\n validateRouteModule(handler, method, filePath);\n\n return { handler, method };\n}\n","export type JsonParseResult = { success: true; data: unknown } | { success: false; error: string };\n\nexport function parseJsonBody(text: string): JsonParseResult {\n try {\n const data = JSON.parse(text);\n return { success: true, data };\n } catch {\n return { success: false, error: 'Invalid JSON body' };\n }\n}\n","/**\n * 解析 multipart/form-data 请求\n * 使用 Web 标准 Request.formData() API\n */\n\nexport interface UploadedFile {\n name: string; // 字段名\n filename: string; // 原始文件名\n type: string; // MIME 类型\n size: number; // 文件大小(字节)\n arrayBuffer: () => Promise<ArrayBuffer>; // 获取文件内容\n}\n\nexport interface MultipartResult {\n /** 表单字段(同名字段用数组收集,单值保持 string) */\n fields: Record<string, string | string[]>;\n files: UploadedFile[];\n}\n\n/**\n * 解析 multipart/form-data\n *\n * 同名字段的处理:\n * - 第一次出现:string\n * - 第二次出现:string[](包含所有同名字段的值)\n * - 文件始终收集到 files 数组(通过 name 字段标识归属)\n */\nexport async function parseMultipart(request: Request): Promise<MultipartResult> {\n const formData = await request.formData();\n const fields: Record<string, string | string[]> = {};\n const files: UploadedFile[] = [];\n\n for (const [key, value] of formData.entries()) {\n if (value instanceof File) {\n files.push({\n name: key,\n filename: value.name,\n type: value.type,\n size: value.size,\n arrayBuffer: () => value.arrayBuffer(),\n });\n } else {\n // 同名字段收集为数组\n if (key in fields) {\n const existing = fields[key];\n if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n // in 检查已证明存在\n fields[key] = [existing!, value];\n }\n } else {\n fields[key] = value;\n }\n }\n }\n\n return { fields, files };\n}\n","import { queryToObject } from '../utils/queryToObject';\nimport { parseJsonBody } from '../utils/parseJsonBody';\nimport { parseMultipart } from '../utils/parseMultipart';\nimport { getInputTypeForMethod } from './inputType';\nimport { ValidationError } from '../errors/httpErrors';\n\n/**\n * 根据 HTTP 方法解析主输入\n *\n * - GET / DELETE / HEAD:主输入是 query(URL 查询参数)\n * - POST / PUT / PATCH:主输入是 body(请求体)\n *\n * 注意:DELETE 也可能有 body,但主输入(用于校验)是 query。\n * body 由 createServer 调 resolveBodyForQueryMethod 单独解析后注入\n * (同时消费请求体流,保证 keep-alive 连接可复用)。\n *\n * HTTP 传输语义:\n * - 空请求体(或纯空白)视为无 body,返回 null(handler 可不声明 body 参数)\n * - 请求体非空但 JSON 格式非法,抛 ValidationError(code=INVALID_FORMAT),\n * 不再静默返回 null 导致后续报\"字段缺失\"\n *\n * @param method HTTP 方法\n * @param request Request 对象\n * @returns 解析后的输入值(query 对象或 body 对象)\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveInput(method: string, request: Request): Promise<unknown> {\n return resolveInputFromUrl(method, request, new URL(request.url));\n}\n\n/**\n * resolveInput 的热路径变体:URL 由调用方解析一次后传入\n *\n * createServer 每请求已持有解析好的 URL(pathname/searchParams 复用),\n * 通过此变体避免 query 分支的重复 `new URL(request.url)`。\n */\nexport async function resolveInputFromUrl(\n method: string,\n request: Request,\n url: URL,\n): Promise<unknown> {\n const inputType = getInputTypeForMethod(method);\n\n // 主输入是 body 的方法:解析请求体\n if (inputType === 'body') {\n const contentType = request.headers.get('content-type') ?? '';\n\n // multipart/form-data:字段 + 文件\n if (contentType.includes('multipart/form-data')) {\n return parseMultipart(request);\n }\n\n // application/x-www-form-urlencoded:表单字段\n if (contentType.includes('application/x-www-form-urlencoded')) {\n const text = await request.text();\n if (isBlankText(text)) return null;\n const params = new URLSearchParams(text);\n const obj: Record<string, string> = {};\n for (const [key, value] of params) {\n obj[key] = value;\n }\n return obj;\n }\n\n // 默认按 JSON 解析(application/json 及其它未明确类型)\n const text = await request.text();\n\n // 空请求体:视为无 body(handler 可不声明 body 参数)\n if (isBlankText(text)) {\n return null;\n }\n\n // 非空请求体:必须能解析为 JSON,否则是格式错误\n const result = parseJsonBody(text);\n if (!result.success) {\n throw new ValidationError('请求体不是合法的 JSON', [\n {\n path: 'body',\n code: 'INVALID_FORMAT',\n expected: 'JSON',\n received: 'text',\n message: '请求体不是合法的 JSON',\n },\n ]);\n }\n return result.data;\n }\n\n // 主输入是 query 的方法(GET / DELETE / HEAD):从 URL 提取 query\n return queryToObject(url.searchParams);\n}\n\n/** 空白文本判断(length 短路 + 正则扫描,避免 trim 的全量字符串拷贝) */\nfunction isBlankText(text: string): boolean {\n return text.length === 0 || !/\\S/.test(text);\n}\n\n/**\n * 解析\"主输入是 query 但允许携带 body\"的方法(DELETE)的请求体\n *\n * DELETE 的主输入(用于校验)是 query,但请求体流必须被消费(keep-alive 连接\n * 上有未读 body 时 Node 只能断开连接,无法复用),且 handler 声明 `body` 参数\n * 时应注入真正的请求体而非 query。\n *\n * 与主输入 body 的方法不同:DELETE body 无对应 schema(schema 提取只产出\n * DELETEQuery),不做校验,原样注入。空请求体返回 undefined(handler 未声明\n * body 参数时注入 undefined,行为一致)。\n *\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveBodyForQueryMethod(request: Request): Promise<unknown> {\n const text = await request.text();\n if (isBlankText(text)) return undefined;\n const result = parseJsonBody(text);\n if (!result.success) {\n throw new ValidationError('请求体不是合法的 JSON', [\n {\n path: 'body',\n code: 'INVALID_FORMAT',\n expected: 'JSON',\n received: 'text',\n message: '请求体不是合法的 JSON',\n },\n ]);\n }\n return result.data;\n}\n","import type { ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { consumePendingMetaHeaders } from './pendingMeta';\n\n/**\n * 将 Web Response 写入 Node.js ServerResponse\n *\n * 使用 stream pipe 处理背压,避免高并发下内存膨胀。\n *\n * 断连语义:客户端中断(res 'error' / 提前 'close')时销毁源流并按正常完成收尾——\n * 网络中断不是服务端错误,不进入 500 兜底、不触发 onError;destroy 使底层\n * web ReadableStream 触发 cancel(),上游生产者(如 SseWriter)感知 aborted\n * 停止推送,避免数据堆积在无消费者的流 buffer 中(内存泄漏)。\n * 源流自身错误(handler 流式写失败)仍 reject,走正常错误响应路径。\n */\nexport async function sendNodeResponse(response: Response, res: ServerResponse): Promise<void> {\n // 设置状态码\n res.statusCode = response.status;\n\n // 设置 headers\n for (const [key, value] of response.headers) {\n if (key.toLowerCase() === 'set-cookie') {\n // Set-Cookie 使用 appendHeader 支持多个值\n res.appendHeader(key, value);\n } else {\n res.setHeader(key, value);\n }\n }\n // 延迟落头(headers-only meta 的零重建通道,见 pendingMeta.ts)——\n // 后写覆盖,与 Headers.set 语义一致\n const deferred = consumePendingMetaHeaders(response);\n if (deferred) {\n for (const [key, value] of Object.entries(deferred)) {\n res.setHeader(key, value);\n }\n }\n\n // 写入 body(使用 pipe 处理背压)\n if (response.body) {\n // TS 5.7 lib.dom 的 ReadableStream 与 node:stream/web 的 ReadableStream 是不同类型,cast 绕过\n const nodeStream = Readable.fromWeb(response.body as never);\n await new Promise<void>((resolve, reject) => {\n // 源流错误:真实错误 → reject(handleRequest catch 走错误响应)\n nodeStream.on('error', reject);\n\n // 客户端断开:销毁源流(触发底层 cancel)+ resolve(不当作服务端错误)\n const abort = () => {\n nodeStream.destroy();\n resolve();\n };\n res.on('error', abort);\n res.on('close', () => {\n // 正常完成也会触发 'close'(在 'finish' 之后,此时 writableEnded=true),不视为断连\n if (!res.writableEnded) abort();\n });\n\n res.on('finish', resolve);\n nodeStream.pipe(res);\n });\n return;\n }\n\n res.end();\n}\n","import type { IncomingMessage } from 'node:http';\n\n/**\n * 从 IncomingMessage 提取客户端 IP\n *\n * 优先级:\n * 1. `trustedProxy=true` 时,`x-forwarded-for` 第一个 IP(反向代理场景,如 nginx/CDN)\n * 2. `req.socket.remoteAddress`(直连场景,默认路径)\n *\n * `x-forwarded-for` 格式:`client, proxy1, proxy2`,取第一个即客户端真实 IP。\n *\n * **默认不信任该 header**(`trustedProxy=false`):客户端直连时 XFF 可被任意伪造,\n * 污染 `ctx.ip`(影响限流/日志/风控)。部署在受信任的反向代理之后时,通过\n * `faapi.config.ts` 的 `trustedProxy: true` 显式开启。\n *\n * @param trustedProxy 是否信任反向代理头(默认 false——安全默认,直连不可伪造)\n * @returns 客户端 IP,无法获取时返回空字符串\n */\nexport function getClientIp(req: IncomingMessage, trustedProxy = false): string {\n // 1. x-forwarded-for(仅显式信任反向代理时)\n if (trustedProxy) {\n const xff = req.headers['x-forwarded-for'];\n if (typeof xff === 'string' && xff.length > 0) {\n const first = xff.split(',')[0]?.trim();\n if (first) return first;\n }\n }\n\n // 2. 直连 socket IP\n const remote = req.socket?.remoteAddress;\n if (remote) {\n // 去掉 IPv6 前缀 ::ffff:,统一返回 IPv4 形式\n if (remote.startsWith('::ffff:')) {\n return remote.slice(7);\n }\n return remote;\n }\n\n return '';\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\n\nexport interface CorsOptions {\n origin?: string | string[] | true;\n methods?: string[];\n allowedHeaders?: string[];\n exposeHeaders?: string[];\n credentials?: boolean;\n maxAge?: number;\n}\n\nconst DEFAULT_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];\n\n/**\n * 创建 CORS 中间件(洋葱模型)\n *\n * - origin=true: 允许所有来源(反射请求的 Origin)\n * - origin=string: 允许指定来源\n * - origin=string[]: 允许多个来源\n *\n * OPTIONS 预检请求直接返回 204,不调用 next()。\n */\nexport function cors(options: CorsOptions = {}): FaapiMiddleware {\n const {\n origin = true,\n methods = DEFAULT_METHODS,\n allowedHeaders,\n exposeHeaders,\n credentials = false,\n maxAge,\n } = options;\n\n return async (ctx, next) => {\n const reqOrigin = ctx.headers.get('origin');\n if (!reqOrigin) {\n await next();\n return;\n }\n\n // 确定允许的来源\n let allowOrigin: string | null = null;\n if (origin === true) {\n allowOrigin = reqOrigin; // 反射 Origin\n } else if (typeof origin === 'string') {\n allowOrigin = reqOrigin === origin ? origin : null;\n } else if (Array.isArray(origin)) {\n allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;\n }\n\n // 当 origin 为动态值(true 或数组)时,响应内容(是否带 ACAO)随 Origin 头变化\n // ——不匹配被拒的响应同样必须带 Vary: Origin。缺了它,CDN/浏览器按 URL 缓存\n // \"无 ACAO 的拒绝响应\"后,可能服务给后续合法 Origin 的请求(缓存污染面)\n if (origin === true || Array.isArray(origin)) {\n const existingVary = ctx.headers.get('vary');\n if (existingVary) {\n if (!existingVary.toLowerCase().includes('origin')) {\n ctx.setHeader('Vary', `${existingVary}, Origin`);\n }\n } else {\n ctx.setHeader('Vary', 'Origin');\n }\n }\n\n if (!allowOrigin) {\n await next();\n return;\n }\n\n // 设置 CORS 响应头\n ctx.setHeader('Access-Control-Allow-Origin', allowOrigin);\n\n ctx.setHeader('Access-Control-Allow-Methods', methods.join(', '));\n\n if (allowedHeaders) {\n ctx.setHeader('Access-Control-Allow-Headers', allowedHeaders.join(', '));\n } else {\n // 反射请求头\n const requestHeaders = ctx.headers.get('access-control-request-headers');\n if (requestHeaders) {\n ctx.setHeader('Access-Control-Allow-Headers', requestHeaders);\n }\n }\n\n if (exposeHeaders && exposeHeaders.length > 0) {\n ctx.setHeader('Access-Control-Expose-Headers', exposeHeaders.join(', '));\n }\n\n if (credentials) {\n ctx.setHeader('Access-Control-Allow-Credentials', 'true');\n }\n\n if (maxAge !== undefined) {\n ctx.setHeader('Access-Control-Max-Age', String(maxAge));\n }\n\n // 处理 preflight 请求:直接返回 204,不调用 next()\n if (ctx.method === 'OPTIONS') {\n return new Response(null, { status: 204 });\n }\n\n await next();\n };\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\n\nexport interface HelmetOptions {\n contentSecurityPolicy?: string | false;\n xFrameOptions?: 'DENY' | 'SAMEORIGIN' | false;\n xContentTypeOptions?: boolean;\n referrerPolicy?: string | false;\n strictTransportSecurity?: string | false;\n xDnsPrefetchControl?: boolean;\n xDownloadOptions?: boolean;\n xPermittedCrossDomainPolicies?: string | false;\n crossOriginOpenerPolicy?: string | false;\n crossOriginResourcePolicy?: string | false;\n crossOriginEmbedderPolicy?: string | false;\n originAgentCluster?: boolean;\n xPoweredBy?: boolean;\n}\n\nconst DEFAULTS: Required<Omit<HelmetOptions, 'contentSecurityPolicy'>> & {\n contentSecurityPolicy: string | false;\n} = {\n contentSecurityPolicy: \"default-src 'self'\",\n xFrameOptions: 'SAMEORIGIN',\n xContentTypeOptions: true,\n referrerPolicy: 'no-referrer',\n strictTransportSecurity: 'max-age=31536000; includeSubDomains',\n xDnsPrefetchControl: true,\n xDownloadOptions: true,\n xPermittedCrossDomainPolicies: 'none',\n crossOriginOpenerPolicy: 'same-origin',\n crossOriginResourcePolicy: 'same-origin',\n crossOriginEmbedderPolicy: false,\n originAgentCluster: true,\n xPoweredBy: true,\n};\n\nexport function helmet(options: HelmetOptions = {}): FaapiMiddleware {\n const opts = { ...DEFAULTS, ...options };\n\n return async (ctx, next) => {\n if (opts.contentSecurityPolicy !== false) {\n ctx.setHeader('Content-Security-Policy', opts.contentSecurityPolicy);\n }\n if (opts.xFrameOptions !== false) {\n ctx.setHeader('X-Frame-Options', opts.xFrameOptions);\n }\n if (opts.xContentTypeOptions) {\n ctx.setHeader('X-Content-Type-Options', 'nosniff');\n }\n if (opts.referrerPolicy !== false) {\n ctx.setHeader('Referrer-Policy', opts.referrerPolicy);\n }\n if (opts.strictTransportSecurity !== false) {\n ctx.setHeader('Strict-Transport-Security', opts.strictTransportSecurity);\n }\n if (opts.xDnsPrefetchControl) {\n ctx.setHeader('X-DNS-Prefetch-Control', 'off');\n }\n if (opts.xDownloadOptions) {\n ctx.setHeader('X-Download-Options', 'noopen');\n }\n if (opts.xPermittedCrossDomainPolicies !== false) {\n ctx.setHeader('X-Permitted-Cross-Domain-Policies', opts.xPermittedCrossDomainPolicies);\n }\n if (opts.crossOriginOpenerPolicy !== false) {\n ctx.setHeader('Cross-Origin-Opener-Policy', opts.crossOriginOpenerPolicy);\n }\n if (opts.crossOriginResourcePolicy !== false) {\n ctx.setHeader('Cross-Origin-Resource-Policy', opts.crossOriginResourcePolicy);\n }\n if (opts.crossOriginEmbedderPolicy !== false) {\n ctx.setHeader('Cross-Origin-Embedder-Policy', opts.crossOriginEmbedderPolicy);\n }\n if (opts.originAgentCluster) {\n ctx.setHeader('Origin-Agent-Cluster', '?1');\n }\n if (opts.xPoweredBy) {\n ctx.setHeader('X-Powered-By', 'faapi');\n }\n\n return await next();\n };\n}\n","import zlib from 'node:zlib';\nimport { promisify } from 'node:util';\nimport type { FaapiMiddleware } from './middlewareTypes';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\n\nconst gzipAsync = promisify(zlib.gzip);\nconst deflateAsync = promisify(zlib.deflate);\nconst brotliAsync = promisify(zlib.brotliCompress);\n\nexport interface CompressionOptions {\n /**\n * 最小压缩字节数,body 低于该值不压缩(小 payload 压缩后反而变大)\n * @default 1024\n */\n threshold?: number;\n}\n\nconst DEFAULT_THRESHOLD = 1024;\n\n/** 可压缩的 Content-Type(text/event-stream 除外——流式语义不应缓冲) */\nconst COMPRESSIBLE_CHECKS: Array<(contentType: string) => boolean> = [\n (ct) => ct === 'application/json',\n (ct) => ct === 'application/javascript',\n (ct) => ct === 'image/svg+xml',\n (ct) => ct.startsWith('text/') && ct !== 'text/event-stream',\n];\n\nfunction isCompressible(contentType: string): boolean {\n const ct = contentType.split(';')[0]!.trim().toLowerCase();\n if (ct === '') return false;\n return COMPRESSIBLE_CHECKS.some((check) => check(ct));\n}\n\ninterface AcceptedEncoding {\n encoding: string;\n quality: number;\n}\n\n/**\n * 解析 Accept-Encoding 头(含 q 值)\n *\n * `gzip, br;q=0.8, identity;q=0, *;q=0.5` → [{gzip,1},{br,0.8},{identity,0},{*,0.5}]\n */\nexport function parseAcceptEncoding(header: string): AcceptedEncoding[] {\n return header\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part !== '')\n .map((part) => {\n const [encoding, ...params] = part.split(';');\n let quality = 1;\n for (const param of params) {\n const trimmed = param.trim();\n if (trimmed.startsWith('q=')) {\n const q = Number(trimmed.slice(2));\n if (Number.isFinite(q)) quality = q;\n }\n }\n return { encoding: (encoding ?? '').trim().toLowerCase(), quality };\n })\n .filter((e) => e.encoding !== '' && e.quality > 0);\n}\n\n/**\n * 从已接受的编码中选择压缩算法\n *\n * 服务器偏好 br > gzip > deflate(同等 q 值下);`*` 通配按 gzip 处理。\n * 未命中返回 null(客户端不接受压缩)。\n */\nexport function selectEncoding(accepted: AcceptedEncoding[]): 'br' | 'gzip' | 'deflate' | null {\n const byName = new Map<string, number>();\n for (const { encoding, quality } of accepted) {\n // 同一编码出现多次取最高 q\n byName.set(encoding, Math.max(byName.get(encoding) ?? 0, quality));\n }\n // q=0 表示明确不接受\n for (const [encoding, quality] of byName) {\n if (quality === 0) byName.delete(encoding);\n }\n\n for (const candidate of ['br', 'gzip', 'deflate'] as const) {\n if ((byName.get(candidate) ?? 0) > 0) return candidate;\n }\n if ((byName.get('*') ?? 0) > 0) return 'gzip';\n return null;\n}\n\nasync function compressBody(\n encoding: 'br' | 'gzip' | 'deflate',\n body: string,\n): Promise<Uint8Array> {\n const buf = Buffer.from(body, 'utf-8');\n switch (encoding) {\n case 'br':\n return brotliAsync(buf);\n case 'gzip':\n return gzipAsync(buf);\n case 'deflate':\n return deflateAsync(buf);\n }\n}\n\n/** 向 meta.headers 合并 Vary 值(CORS 等中间件可能已设置 Vary: Origin,不能覆盖) */\nfunction mergeVary(meta: ResponseMeta, value: string): void {\n const existing = meta.headers['Vary'] ?? meta.headers['vary'];\n if (!existing) {\n meta.headers['Vary'] = value;\n return;\n }\n // 大小写不敏感地检查是否已包含\n if (!existing.toLowerCase().includes(value.toLowerCase())) {\n const key = meta.headers['Vary'] !== undefined ? 'Vary' : 'vary';\n meta.headers[key] = `${existing}, ${value}`;\n }\n}\n\n/**\n * 创建响应压缩中间件(内建,默认关闭,`config.compression` 显式启用)\n *\n * 位于外层中间件链最前(包住 CORS/helmet/logger/全局),`await next()` 后拿到\n * 最终 Response:满足压缩条件(可压缩类型 + 大小达标 + 客户端接受)时缓冲 body\n * 异步压缩后构造新 Response 替换;否则透传。`Vary: Accept-Encoding` 通过\n * meta.headers 合并(与 CORS 的 Vary: Origin 共存),两种路径都生效。\n */\nexport function compression(options: CompressionOptions = {}): FaapiMiddleware {\n const threshold = options.threshold ?? DEFAULT_THRESHOLD;\n\n return async (ctx, next) => {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n const response = await next();\n if (!response) return response;\n\n // 无条件协商 Vary(响应内容随 Accept-Encoding 变化,与是否压缩本响应无关)\n mergeVary(meta, 'Accept-Encoding');\n\n // 跳过条件:客户端不接受 / 不可压缩类型 / 已编码 / no-transform / 204、304 等\n const acceptEncoding = ctx.request.headers.get('accept-encoding') ?? '';\n const encoding = selectEncoding(parseAcceptEncoding(acceptEncoding));\n const contentType = response.headers.get('content-type') ?? '';\n if (\n !encoding ||\n !isCompressible(contentType) ||\n response.headers.has('content-encoding') ||\n (response.headers.get('cache-control') ?? '').includes('no-transform') ||\n response.status === 204 ||\n response.status === 304 ||\n response.body === null\n ) {\n return response;\n }\n\n // 缓冲 body(toResponse 的 JSON body 本就是字符串);SSE 已被 content-type 排除\n // 延迟落头(headers-only meta)需并入重建的 Response,否则发送层取不到\n const deferredHeaders = consumePendingMetaHeaders(response);\n\n const bodyText = await response.text();\n if (bodyText.length < threshold) {\n // 未达阈值:body 已被 text() 消费,需重建等价 Response\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n const compressed = await compressBody(encoding, bodyText);\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n headers.set('Content-Encoding', encoding);\n // Content-Length 由 Response 构造时按压缩后字节数自动设置(先删避免重复)\n headers.delete('Content-Length');\n return new Response(compressed as unknown as BodyInit, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n","import { createHash } from 'node:crypto';\nimport type { FaapiMiddleware } from './middlewareTypes';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\n\nexport interface EtagOptions {\n /**\n * 生成弱 ETag(`W/\"<hash>\"`)。弱校验器允许压缩等表示差异下的 304 协商,\n * 与 compression 中间件配合正确;强 ETag 需对每个表示(编码)单独生成\n * @default true\n */\n weak?: boolean;\n}\n\nconst DEFAULTS: Required<EtagOptions> = { weak: true };\n\n/** 计算 ETag:SHA-1 指纹 base64;weak=true 生成弱校验器(W/ 前缀) */\nfunction computeEtag(body: string, weak: boolean): string {\n const hash = createHash('sha1').update(body).digest('base64');\n return weak ? `W/\"${hash}\"` : `\"${hash}\"`;\n}\n\n/** If-None-Match 比对:weak 模式用 RFC 7232 弱比较(忽略 W/ 前缀);strong 模式精确比对 */\nfunction ifNoneMatchMatches(ifNoneMatch: string, etag: string, weak: boolean): boolean {\n const normalize = (tag: string) => tag.trim().replace(/^W\\//i, '');\n if (weak) {\n const target = normalize(etag);\n return ifNoneMatch.split(',').some((tag) => {\n const trimmed = tag.trim();\n return trimmed === '*' || normalize(trimmed) === target;\n });\n }\n // 强比较:ETag 原样相等(含 W/ 前缀语义,弱 ETag 永不匹配强比较)\n return ifNoneMatch.split(',').some((tag) => tag.trim() === etag || tag.trim() === '*');\n}\n\n/**\n * 创建 ETag/304 协商中间件(内建,默认关闭,`config.etag` 显式启用)\n *\n * `await next()` 后对 GET/HEAD 2xx 响应计算弱 ETag 并写入 meta(handler 显式\n * `ctx.setETag()` 时不覆盖);请求 `If-None-Match` 弱比较命中时返回 304(无 body)。\n *\n * 中间件位置在 compression 内层:先算 ETag/304 再压缩——304 无 body 压缩自动跳过;\n * 弱 ETag 基于未压缩表示计算,语义正确。\n */\nexport function etag(options: EtagOptions = {}): FaapiMiddleware {\n const opts = { ...DEFAULTS, ...options };\n\n return async (ctx, next) => {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n const response = await next();\n if (!response) return response;\n\n // 仅协商幂等方法的 2xx 响应\n const method = ctx.request.method.toUpperCase();\n if (method !== 'GET' && method !== 'HEAD') return response;\n if (response.status < 200 || response.status >= 300) return response;\n\n // handler 已显式设置 ETag(ctx.setETag 写 meta.headers.etag)→ 控制权归 handler\n if (meta.headers['etag'] !== undefined || response.headers.has('etag')) {\n return response;\n }\n // 流式响应(SSE 等)不缓冲\n if (\n response.body === null ||\n (response.headers.get('content-type') ?? '').includes('text/event-stream')\n ) {\n return response;\n }\n\n const bodyText = await response.text();\n const etagValue = computeEtag(bodyText, opts.weak);\n\n // If-None-Match 弱比较命中 → 304(无 body,携带 ETag)\n const ifNoneMatch = ctx.request.headers.get('if-none-match');\n if (ifNoneMatch && ifNoneMatchMatches(ifNoneMatch, etagValue, opts.weak)) {\n return new Response(null, {\n status: 304,\n statusText: response.statusText,\n headers: { ETag: etagValue },\n });\n }\n\n // 200 路径:body 已被 text() 消费,重建响应;ETag 经 meta 传递(mergeMeta 兜底应用)\n meta.headers['etag'] = etagValue;\n const headers = new Headers(response.headers);\n // 延迟落头并入重建的 Response(etag 中间件位于 compression 内层,先消费)\n const deferredHeaders = consumePendingMetaHeaders(response);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\n\nexport type LoggerFn = (messageOrObj: string | Record<string, unknown>, message?: string) => void;\n\nexport interface LoggerOptions {\n /**\n * 自定义日志函数\n *\n * - 传入 `console.log`(默认):纯文本格式 `GET /api/users 200 12ms`\n * - 传入 pino logger:结构化日志 `logger.info({ method, path, status, durationMs }, 'request completed')`\n * - 传入 winston logger:`logger.info('GET /api/users 200 12ms', { method, path })`\n */\n log?: LoggerFn;\n}\n\n/**\n * 创建请求日志中间件(洋葱模型)\n *\n * 日志格式(文本模式):GET /api/users 200 12ms\n * 错误格式(文本模式):POST /api/users 400 45ms - Error: ...\n *\n * 结构化模式:传入 pino/winston 等 logger 实例时,会自动传递结构化字段。\n *\n * before/after 一体,闭包变量共享开始时间,无需污染 ctx。\n * 错误用 try/catch 捕获,记录后重新抛出(让上层处理)。\n * 成功时从 next() 返回的 Response 读取状态码。\n *\n * log 函数每次请求时读取(options.log ?? console.log),运行时替换 console.log 会生效。\n */\nexport function logger(options: LoggerOptions = {}): FaapiMiddleware {\n return async (ctx, next) => {\n const log = options.log ?? console.log;\n const start = Date.now();\n try {\n const response = await next();\n const duration = Date.now() - start;\n const entry = {\n method: ctx.method,\n path: ctx.path,\n status: response.status,\n durationMs: duration,\n };\n log(entry, `${ctx.method} ${ctx.path} ${response.status} ${duration}ms`);\n return response;\n } catch (err) {\n const duration = Date.now() - start;\n const message = err instanceof Error ? err.message : String(err);\n const status = (err as { statusCode?: number })?.statusCode ?? 500;\n const entry = {\n method: ctx.method,\n path: ctx.path,\n status,\n durationMs: duration,\n error: message,\n };\n log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);\n throw err;\n }\n };\n}\n","/**\n * WebSocket 协议升级处理\n *\n * 监听 HTTP server 的 upgrade 事件,匹配 WS 路由后调用 ws 库完成协议升级,\n * 加载 handler.ts 的 WS 导出,绑定事件回调。\n *\n * 握手阶段复用洋葱中间件链(鉴权/CORS/限流/日志等),与同目录 HTTP 路由共享中间件。\n * 中间件塞入 ctx 的值(如 ctx.user)传入 WS handler。连接建立后切到事件模型,不走中间件。\n *\n * @see wsHandler.md WS handler 约定与握手中间件链设计\n */\nimport type { IncomingMessage } from 'node:http';\nimport type { Server } from 'node:http';\nimport type { Socket } from 'node:net';\nimport fs from 'node:fs';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport type { RawData } from 'ws';\nimport path from 'node:path';\nimport type { WsRouteMatch, RoutesRef } from '../router/routeTypes';\nimport { matchWsRoute } from '../router/matchRoute';\nimport { createContext } from '../runtime/createContext';\nimport { compose, mergeMeta } from '../runtime/invokeHandler';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport { getClientIp } from '../utils/getClientIp';\nimport { nodeHttpToWebHeaders, buildErrorResponse } from './serverUtils';\nimport {\n ensureCompiled,\n ensureMiddlewaresCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n prodPathToSourcePath,\n} from '../cli/compileOnDemand';\nimport { loadMergedMiddlewares } from '../middleware/loadMiddlewares';\nimport type { AppRegistries } from '../injection/registries';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\nimport {\n wrapWsSocket,\n type WsContext,\n type WsEventHandlers,\n type WsHandler,\n} from '../runtime/wsHandler';\n\n/**\n * 从 IncomingMessage 提取 URL pathname(不含 query)\n */\nfunction getPathname(req: IncomingMessage): string {\n const url = req.url ?? '/';\n const idx = url.indexOf('?');\n return idx >= 0 ? url.slice(0, idx) : url;\n}\n\n/**\n * 加载 WS handler 并执行,返回事件回调对象(可能为空)\n *\n * 直接动态 import handler.ts 并取 `WS` 导出(不经过 loadRouteModule,\n * 因为 WS 不是 HTTP 方法,loadRouteModule 的 method 维度不适用)。\n * watch 模式下通过时间戳 query string 绕过 ESM 缓存。\n *\n * Dev 按需编译模式:先确保编译再 import,避免 import 不存在的文件污染 Vite SSR 状态。\n */\nasync function loadWsHandler(\n filePath: string,\n ctx: WsContext,\n rootDir?: string,\n): Promise<WsEventHandlers | void> {\n // Dev 按需模式:先确保编译再 import(同 loadRouteModule 逻辑)\n if (isDevOnDemandEnabled() && rootDir) {\n const dist = getDevDist();\n if (dist) {\n const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);\n if (sourcePath && fs.existsSync(sourcePath)) {\n await ensureCompiled(sourcePath, rootDir, dist);\n }\n }\n }\n\n const module = (await importWithCacheBust(filePath, isDevOnDemandEnabled())) as Record<\n string,\n unknown\n >;\n\n const handler = module['WS'];\n if (typeof handler !== 'function') {\n throw new Error(`WS export not found in ${filePath}`);\n }\n return (handler as WsHandler)(ctx);\n}\n\n/**\n * 绑定 ws 库原生事件到 faapi 的 WsEventHandlers\n *\n * 注意:wss.handleUpgrade 回调触发时 socket 已经处于 OPEN 状态,\n * 'open' 事件不会再触发,需要直接同步调用 onOpen。\n */\nfunction bindEvents(rawSocket: WebSocket, handlers: WsEventHandlers | void): void {\n if (!handlers) return;\n const ws = wrapWsSocket(rawSocket);\n\n if (handlers.onOpen) {\n // handleUpgrade 回调触发时 socket 已是 OPEN,直接同步调用\n if (rawSocket.readyState === WebSocket.OPEN) {\n handlers.onOpen!(ws);\n } else {\n rawSocket.once('open', () => handlers.onOpen!(ws));\n }\n }\n if (handlers.onMessage) {\n rawSocket.on('message', (data: RawData, isBinary: boolean) => {\n // ws 库 message 事件传 Buffer | ArrayBuffer | Buffer[],归一化为 Buffer;\n // 二进制帧透传 Buffer(utf8 解码不可逆,protobuf/msgpack 等二进制协议会损坏),\n // 文本帧转 utf8 字符串——与 onMessage 类型声明 string | Buffer 一致\n const buf = toWsBuffer(data);\n handlers.onMessage!(ws, isBinary ? buf : buf.toString('utf8'));\n });\n }\n if (handlers.onClose) {\n rawSocket.on('close', (code: number, reason: Buffer) => {\n handlers.onClose!(ws, code, reason.toString('utf8'));\n });\n }\n if (handlers.onError) {\n rawSocket.on('error', (err: Error) => {\n handlers.onError!(ws, err);\n });\n }\n}\n\n/** ws 库 RawData(Buffer | ArrayBuffer | Buffer[])归一化为 Buffer */\nfunction toWsBuffer(data: RawData): Buffer {\n if (Buffer.isBuffer(data)) return data;\n if (Array.isArray(data)) return Buffer.concat(data);\n return Buffer.from(data as ArrayBuffer);\n}\n\n/**\n * 把 Web Response 写回原始 socket(握手被中间件拦截或出错时使用)\n *\n * socket 未升级为 WebSocket 时,按 HTTP 响应格式写回并销毁。\n */\nasync function sendResponseToSocket(socket: Socket, response: Response): Promise<void> {\n const body = await response.text().catch(() => '');\n const statusLine = `HTTP/1.1 ${response.status} ${response.statusText || ''}\\r\\n`;\n const headerLines: string[] = [];\n let hasContentLength = false;\n // 延迟落头(headers-only meta)并入写回的 header 行\n const deferredHeaders = consumePendingMetaHeaders(response);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n if (key.toLowerCase() === 'content-length') hasContentLength = true;\n headerLines.push(`${key}: ${value}`);\n }\n for (const [key, value] of response.headers) {\n if (key.toLowerCase() === 'content-length') {\n hasContentLength = true;\n }\n headerLines.push(`${key}: ${value}`);\n }\n if (!hasContentLength) {\n headerLines.push(`Content-Length: ${Buffer.byteLength(body)}`);\n }\n socket.write(statusLine + headerLines.join('\\r\\n') + '\\r\\n\\r\\n' + body);\n socket.destroy();\n}\n\nexport interface AttachWsOptions {\n /** HTTP server 实例(已由 createServer 创建) */\n server: Server;\n /** 路由可变引用容器(watch 模式热替换时 routesRef.wsCurrent 被更新) */\n routesRef: RoutesRef;\n /** 项目根目录,用于解析路由文件绝对路径 */\n rootDir: string;\n /** 业务配置,注入到 WsContext.config */\n config?: Record<string, unknown>;\n /** 全局中间件(来自 faapi.config.ts,WS 握手最外层) */\n globalMiddlewares?: FaapiMiddleware[];\n /** 全局注入器(来自 faapi.config.ts,WS handler 可通过 ctx 间接访问全局依赖) */\n globalInjectors?: InjectorMap;\n /**\n * 是否信任反向代理头(X-Forwarded-For),默认 false\n *\n * true 时 `ctx.ip` 取 `x-forwarded-for` 第一个 IP;false(默认)时直取 socket 地址。\n */\n trustedProxy?: boolean;\n /** app 级注册表——经 WsContext 进入 WS 握手链路 */\n registries?: AppRegistries;\n}\n\n/**\n * 在 HTTP server 上挂载 WebSocket 升级处理\n *\n * 创建独立的 WebSocketServer(noServer 模式),监听 server 的 upgrade 事件。\n * 路由匹配成功后,走洋葱中间件链:\n * - 中间件放行(await next):finalHandler 内调用 ws.handleUpgrade 完成协议升级、绑定事件回调\n * - 中间件拦截(返回 Response):把 Response 写回 socket 后销毁,不进行协议升级\n * - 中间件抛错:由 formatErrorResponse/内置兜底生成错误 Response,写回 socket 后销毁\n *\n * 中间件链顺序:全局中间件(外)→ 目录中间件(内)→ finalHandler。\n * watch 模式下 wsRoutes 通过 routesRef 引用更新。\n */\nexport function attachWebSocket(options: AttachWsOptions): WebSocketServer {\n const {\n server,\n routesRef,\n rootDir,\n config,\n globalMiddlewares,\n trustedProxy = false,\n registries,\n } = options;\n\n const wss = new WebSocketServer({ noServer: true });\n\n server.on('upgrade', async (req: IncomingMessage, socket: Socket, head: Buffer) => {\n try {\n await handleUpgradeRequest(req, socket, head);\n } catch (err) {\n // 兜底 catch:upgrade 监听器是 async 函数,路由匹配 / header 构造 / 上下文创建等\n // 抛出的未捕获错误会变成 unhandled rejection(Node 15+ 默认崩掉进程)。\n // HTTP 路径有 catch 兜底,WS 路径保持对称——写回 500 后销毁 socket。\n console.error('[faapi] WS upgrade 处理失败:', err);\n if (!socket.destroyed) {\n try {\n if (!socket.writableEnded) {\n socket.write('HTTP/1.1 500 Internal Server Error\\r\\n\\r\\n');\n }\n } catch {\n // socket 已不可写,忽略\n }\n socket.destroy();\n }\n }\n });\n\n async function handleUpgradeRequest(\n req: IncomingMessage,\n socket: Socket,\n head: Buffer,\n ): Promise<void> {\n const currentWsRoutes = routesRef.wsCurrent;\n const pathname = getPathname(req);\n const match: WsRouteMatch | null = matchWsRoute(currentWsRoutes, pathname);\n\n if (!match) {\n // 路由不匹配:返回 404 并销毁 socket\n socket.write('HTTP/1.1 404 Not Found\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n\n const { route, params } = match;\n const headers = nodeHttpToWebHeaders(req);\n const host = req.headers.host ?? 'localhost';\n const url = `http://${host}${req.url ?? '/'}`;\n\n // 构造 Web Request 与 FaapiContext(与 HTTP 请求一致,供中间件使用)\n const request = new Request(url, { method: 'GET', headers });\n const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n\n // 标记握手是否已完成协议升级(用于判断 socket 是否可写)\n let upgraded = false;\n\n // finalHandler:加载 WS handler + 协议升级 + 绑定事件\n const finalHandler = async (): Promise<Response> => {\n let handlers: WsEventHandlers | void;\n try {\n const absoluteFilePath = path.resolve(rootDir, route.filePath);\n // WsContext 是 FaapiContext 的结构子集,直接复用 ctx 实例\n // 中间件塞入的字段(如 ctx.user)对 WS handler 可见\n handlers = await loadWsHandler(absoluteFilePath, ctx as unknown as WsContext, rootDir);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.error(`[faapi] WS handler 加载失败 ${route.filePath}: ${reason}`);\n return new Response('Internal Server Error', { status: 500 });\n }\n\n // 协议升级:等 wss.handleUpgrade 回调完成 bindEvents\n await new Promise<void>((resolve, reject) => {\n wss.handleUpgrade(req, socket, head, (rawSocket: WebSocket) => {\n try {\n bindEvents(rawSocket, handlers);\n wss.emit('connection', rawSocket, req);\n upgraded = true;\n resolve();\n } catch (err) {\n reject(err);\n }\n });\n });\n\n // 占位 Response,表示握手成功(socket 已升级,不会被写出)\n return new Response(null, { status: 200 });\n };\n\n // 执行中间件链:全局中间件(外)+ 目录中间件(内)\n let response: Response;\n try {\n // 按需加载中间件:route.middlewares 为 undefined 时从 middlewarePaths 加载(Vite 风格)\n if (route.middlewares === undefined && route.middlewarePaths) {\n // dev 按需模式:先编译中间件源码(含依赖闭包)再 import\n await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);\n const bundle = await loadMergedMiddlewares(route.middlewarePaths);\n if (bundle) {\n route.middlewares = bundle.middlewares;\n } else {\n route.middlewares = [];\n }\n }\n const dirMiddlewares = route.middlewares ?? [];\n const allMiddlewares =\n globalMiddlewares && globalMiddlewares.length > 0\n ? [...globalMiddlewares, ...dirMiddlewares]\n : dirMiddlewares;\n if (allMiddlewares.length > 0) {\n response = await compose(allMiddlewares, ctx, finalHandler);\n } else {\n response = await finalHandler();\n }\n } catch (err) {\n if (upgraded) {\n // 握手已完成(socket 已被 ws 库接管),中间件 after 阶段抛错无法改写响应\n console.error('[faapi] WS 握手后中间件抛错:', err);\n return;\n }\n // 错误兜底链:内置 formatErrorResponse 兜底\n // 业务方如需自定义错误响应,在全局中间件中 try/catch next() 即可\n response = buildErrorResponse(err);\n }\n\n // 握手成功:socket 已升级,中间件 after 阶段已执行完,无需再写\n if (upgraded) {\n return;\n }\n\n // 中间件拦截或错误:把 Response 写回 socket 后销毁\n await sendResponseToSocket(socket, mergeMeta(response, meta));\n }\n\n return wss;\n}\n","import type { IncomingMessage } from 'node:http';\nimport { formatErrorResponse } from '../errors/formatErrorResponse';\n\n/**\n * 将 Node.js IncomingMessage 的 headers 转为 Web Headers\n *\n * 处理数组型 header 值(如 set-cookie)和 undefined 值。\n */\nexport function nodeHttpToWebHeaders(req: IncomingMessage): Headers {\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const v of value) headers.append(key, v);\n } else {\n headers.set(key, value);\n }\n }\n return headers;\n}\n\n/**\n * 构建错误响应(兜底链)\n *\n * 1. 框架内置 formatErrorResponse 处理(handler 抛错时)——读 config.response.fail\n * 自定义包装函数,确保错误格式与 ctx.fail() 主动错误响应一致\n * 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n *\n * 业务方如需进一步自定义错误响应,在全局中间件中 try/catch next() 即可。\n *\n * @param err handler 抛出的错误\n * @param config 业务方配置(用于读取 response.fail,可选——未传时走框架默认 fail 函数)\n */\nexport function buildErrorResponse(\n err: unknown,\n config?: Record<string, unknown> | undefined,\n): Response {\n try {\n return formatErrorResponse(err, config);\n } catch {\n // 极端情况:内置兜底也失败,返回最简 500\n return new Response(\n JSON.stringify({ error: { code: 'INTERNAL_ERROR', message: 'Internal Server Error' } }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n}\n","/**\n * WebSocket Handler 类型定义与 socket 封装\n *\n * 用户在 handler.ts 中导出 WS 函数,返回事件回调对象。\n * 框架在协议升级成功后调用对应回调,传递封装后的 WsSocket。\n *\n * @see wsHandler.md 功能说明\n */\n\n/**\n * faapi 封装的 WebSocket socket 抽象\n *\n * 不直接暴露 ws 库的原生 socket,提供更安全、易用的 API。\n * send 对象时自动 JSON.stringify。\n */\nexport interface WsSocket {\n /** 发送数据(string/Buffer 直发,对象自动 JSON.stringify) */\n send(data: string | Buffer | object): void;\n /** 关闭连接 */\n close(code?: number, reason?: string | Buffer): void;\n /** 连接状态:0=connecting, 1=open, 2=closing, 3=closed */\n readonly readyState: number;\n}\n\n/**\n * WebSocket 事件回调集合\n *\n * 用户在 WS handler 中返回此对象,框架在对应事件触发时调用。\n * 所有回调可选,未提供则忽略事件。\n */\nexport interface WsEventHandlers {\n /** 连接建立时触发 */\n onOpen?: (ws: WsSocket) => void;\n /** 收到客户端消息时触发 */\n onMessage?: (ws: WsSocket, message: string | Buffer) => void;\n /** 连接关闭时触发 */\n onClose?: (ws: WsSocket, code: number, reason: string) => void;\n /** 发生错误时触发 */\n onError?: (ws: WsSocket, error: Error) => void;\n}\n\n/**\n * WebSocket 上下文(握手阶段构造)\n *\n * 与 HTTP FaapiContext 类似但精简,提供路由参数、查询参数、请求头、配置。\n * 可通过 declare module '@faapi/faapi' 增强自定义字段。\n */\nexport interface WsContext {\n /** 动态路由参数(如 [id] → params.id) */\n params: Record<string, string>;\n /** URL 查询参数 */\n query: URLSearchParams;\n /** 请求头 */\n headers: Headers;\n /** 业务配置(来自 faapi.config.ts) */\n config: Record<string, unknown>;\n /** 中间件塞入的用户信息(鉴权等) */\n user?: unknown;\n /** 允许通过 declare module 扩展 */\n [key: string]: unknown;\n}\n\n/**\n * WS handler 签名:接收 WsContext,返回事件回调对象(或无返回)\n */\nexport type WsHandler = (ctx: WsContext) => WsEventHandlers | void;\n\n/**\n * 将 ws 库的原生 WebSocket 封装为 faapi 的 WsSocket\n *\n * - send 对象时自动 JSON.stringify\n * - close/re.readyState 透传原生 socket\n */\nexport function wrapWsSocket(rawSocket: {\n send: (data: string | Buffer, cb?: (err?: Error) => void) => void;\n close: (code?: number, reason?: string | Buffer) => void;\n readonly readyState: number;\n}): WsSocket {\n return {\n send(data: string | Buffer | object): void {\n const payload =\n typeof data === 'string' || Buffer.isBuffer(data) ? data : JSON.stringify(data);\n rawSocket.send(payload);\n },\n close(code?: number, reason?: string | Buffer): void {\n rawSocket.close(code, reason);\n },\n get readyState(): number {\n return rawSocket.readyState;\n },\n };\n}\n","import { WebSocket, type RawData } from 'ws';\n\n/**\n * WebSocket 测试客户端\n *\n * 公开导出 connectWs + MessageQueue + waitForWsOpen,业务方测试 WS 路由时\n * 免去手写\"消息竞态防护 + 三事件监听 + 端口拼接\"样板代码。\n *\n * 详见 src/wsTestClient.md。\n */\n\n/**\n * connectWs 入参\n */\nexport interface WsTestClientOptions {\n /** 等待 open 的超时(ms),默认 2000 */\n timeout?: number;\n /** 握手请求头(如 authorization) */\n headers?: Record<string, string>;\n /** WS 子协议 */\n protocols?: string | string[];\n}\n\n/**\n * connectWs 返回值\n *\n * 业务方通过 ws.send() 发消息,queue.next() 取消息,close() 关闭。\n */\nexport interface WsTestClient {\n /** ws 库原生实例,业务方可直接 ws.send() / ws.close() */\n ws: WebSocket;\n /** 已开始缓冲的消息队列,调 next(timeout?) 取下一条 */\n queue: MessageQueue;\n /**\n * 关闭 ws 并等待 'close' 事件\n *\n * 内部:\n * 1. 若 ws 仍 OPEN/CLOSING,调 ws.close()\n * 2. 等待 'close' 事件(超时 1000ms 强制 resolve)\n *\n * 幂等:重复调用不抛错。\n */\n close(): Promise<void>;\n}\n\n/**\n * 消息队列:避免 once('message') 与服务端 onOpen 推送的竞态\n *\n * 服务端在 handleUpgrade 回调里同步触发 onOpen 并 send('connected'),\n * 客户端 'open' 事件触发后到注册 once('message') 之间存在窗口,\n * 若 'connected' 在此窗口内到达,once 会错过。\n *\n * 队列在创建 ws 时立即监听 'message',按 FIFO 顺序消费。\n */\nexport class MessageQueue {\n private queue: string[] = [];\n private waiters: Array<(msg: string) => void> = [];\n private listener: (data: RawData) => void;\n\n constructor(ws: WebSocket) {\n this.listener = (data: RawData) => {\n const msg = normalizeRawData(data);\n const waiter = this.waiters.shift();\n if (waiter) {\n waiter(msg);\n } else {\n this.queue.push(msg);\n }\n };\n ws.on('message', this.listener);\n }\n\n /**\n * 取下一条消息\n *\n * 队列有则立即 resolve,无则注册 waiter 等待下一条 'message' 事件。\n * 超时未到 → reject('WebSocket message timeout'),waiter 被清理。\n *\n * @param timeout 超时毫秒,默认 2000\n */\n next(timeout = 2000): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n // 先定义 wrapped,再定义引用 wrapped 的 timer,避免 TDZ 风格可读性问题\n const wrapped = (msg: string) => {\n clearTimeout(timer);\n resolve(msg);\n };\n const timer = setTimeout(() => {\n const idx = this.waiters.indexOf(wrapped);\n if (idx >= 0) this.waiters.splice(idx, 1);\n reject(new Error('WebSocket message timeout'));\n }, timeout);\n\n const msg = this.queue.shift();\n if (msg !== undefined) {\n wrapped(msg);\n } else {\n this.waiters.push(wrapped);\n }\n });\n }\n}\n\n/**\n * 将 ws 库的 RawData 统一转为 utf8 字符串\n *\n * RawData 可能形态:\n * - Buffer(最常见)\n * - Buffer[](消息被分片,需先 concat)\n * - ArrayBuffer / ArrayBufferView(部分运行时)\n */\nfunction normalizeRawData(data: RawData): string {\n if (Buffer.isBuffer(data)) {\n return data.toString('utf8');\n }\n if (Array.isArray(data)) {\n return Buffer.concat(data).toString('utf8');\n }\n // ArrayBuffer / ArrayBufferView\n return Buffer.from(data as unknown as Uint8Array).toString('utf8');\n}\n\n/**\n * Promise 化等待 ws 'open' 事件\n *\n * 同时监听 'open' / 'error' / 'close' 三事件,任一触发都清理 timer,\n * 避免 timer 泄漏。\n *\n * @param ws WebSocket 实例\n * @param timeout 超时毫秒,默认 2000\n * @returns 'open' → resolve;'error' → reject(err);'close' → reject;超时 → reject\n */\nexport function waitForWsOpen(ws: WebSocket, timeout = 2000): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error('WebSocket open timeout'));\n }, timeout);\n\n const cleanup = () => {\n clearTimeout(timer);\n ws.removeListener('open', onOpen);\n ws.removeListener('error', onError);\n ws.removeListener('close', onClose);\n };\n\n const onOpen = () => {\n cleanup();\n resolve();\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const onClose = () => {\n cleanup();\n reject(new Error('WebSocket closed before open'));\n };\n\n ws.once('open', onOpen);\n ws.once('error', onError);\n ws.once('close', onClose);\n });\n}\n\n/**\n * 一键连接 WS server\n *\n * 内部流程:\n * 1. baseUrl 协议转换(http → ws,https → wss)\n * 2. new WebSocket(url, protocols, { headers })\n * 3. 立即创建 MessageQueue(开始缓冲消息,避免竞态)\n * 4. waitForWsOpen 等待连接建立(三事件监听 + 超时清理)\n * 5. 返回 WsTestClient\n *\n * 连接失败(中间件拦截 / 路径未匹配 / 超时)→ reject。\n *\n * @param baseUrl createTestServer().baseUrl(http://...)\n * @param pathname WS 路径,如 '/api/chat',可含 query\n * @param options timeout / headers / protocols\n * @returns WsTestClient 实例\n */\nexport async function connectWs(\n baseUrl: string,\n pathname: string,\n options: WsTestClientOptions = {},\n): Promise<WsTestClient> {\n const { timeout = 2000, headers, protocols } = options;\n\n // 协议转换:http → ws,https → wss(业务方传入 createTestServer().baseUrl)\n const wsBaseUrl = baseUrl.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:');\n const url = `${wsBaseUrl}${pathname}`;\n\n const ws = new WebSocket(url, protocols, headers ? { headers } : undefined);\n const queue = new MessageQueue(ws);\n\n // 等待 open;失败时主动 close ws,避免 socket 处于 CONNECTING 状态泄漏\n try {\n await waitForWsOpen(ws, timeout);\n } catch (err) {\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n throw err;\n }\n\n // 关闭状态标记(幂等保护)\n let closed = false;\n\n return {\n ws,\n queue,\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n\n // 若 ws 仍 OPEN/CLOSING,主动 close\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n\n // 等待 'close' 事件(超时 1000ms 强制 resolve,避免 server 端不回 close)\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, 1000);\n ws.once('close', () => {\n clearTimeout(timer);\n resolve();\n });\n });\n },\n };\n}\n"],"mappings":"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","names":["path","body","url","path","headers","body","path","fs","path","fs","path","url","path","fs","path","path","fs","ts","fs","path","path","fs","ts","ts","ts","ts","ts","ts","path","unionInner","path","path","fs","path","fs","fg","path","fs","ts","path","fs","path","fs","SPEC_RE","fg","path","fs","path","fs","path","fs","fs","inFlight","fs","path","inFlight","path","fs","path","fs","path","Readable","path","path","params","text","headers","DEFAULTS","etag","fs","path","fs","path","Readable","path","DEFAULT_BODY_LIMIT","cors","helmet","logger","path","fs","WebSocket","msg"]}
|
|
1
|
+
{"version":3,"sources":["../src/runtime/sse.ts","../src/errors/FaapiError.ts","../src/errors/httpErrors.ts","../src/response/responseFormatter.ts","../src/runtime/createContext.ts","../src/utils/isPlainObject.ts","../src/response/pendingMeta.ts","../src/response/toResponse.ts","../src/injection/resolveInjection.ts","../src/utils/queryToObject.ts","../src/injection/registries.ts","../src/injection/agentRegistry.ts","../src/injection/agentHandle.ts","../src/injection/injectParams.ts","../src/runtime/invokeHandler.ts","../src/testServer.ts","../src/router/scanRoutes.ts","../src/router/constants.ts","../src/utils/normalizePath.ts","../src/router/parseRouteFile.ts","../src/utils/importWithCacheBust.ts","../src/middleware/loadMiddlewares.ts","../src/utils/prodPaths.ts","../src/router/sortRoutes.ts","../src/cli/generateSchemaFiles.ts","../src/utils/atomicWrite.ts","../src/ast/createProgram.ts","../src/ast/extractHandlerTypes.ts","../src/ast/resolveTypeNode.ts","../src/runtime/inputType.ts","../src/validator/schemaName.ts","../src/injection/analyzeInjection.ts","../src/cli/collectRouteSchemaSources.ts","../src/ast/generateZodSchema.ts","../src/cli/compileOnDemand.ts","../src/cli/compileSourceFiles.ts","../src/cli/aliasPlugin.ts","../src/utils/resolveAlias.ts","../src/utils/readTsconfig.ts","../src/cli/compileDevRoutes.ts","../src/cli/collectImports.ts","../src/validator/validateInput.ts","../src/server/createServer.ts","../src/router/matchRoute.ts","../src/loader/resolveExports.ts","../src/loader/validateRouteModule.ts","../src/loader/loadRouteModule.ts","../src/utils/parseJsonBody.ts","../src/utils/parseMultipart.ts","../src/runtime/resolveInput.ts","../src/response/sendNodeResponse.ts","../src/utils/getClientIp.ts","../src/middleware/cors.ts","../src/middleware/helmet.ts","../src/middleware/compression.ts","../src/middleware/etag.ts","../src/middleware/logger.ts","../src/server/handleWsUpgrade.ts","../src/server/serverUtils.ts","../src/runtime/wsHandler.ts","../src/wsTestClient.ts"],"sourcesContent":["/**\n * SSE(Server-Sent Events)支持\n *\n * 让 handler 能向客户端推送流式事件,用于 LLM token 流、进度通知等场景。\n *\n * 核心导出:\n * - `encodeSseEvent(event)`:把 SSE 事件对象编码为符合 HTML5 SSE 规范的字符串\n * - `createSseWriter()`:创建一个 SseWriter,封装 ReadableStream + Response,提供 send/close/sendError API\n * - `SseWriter`:writer 类型,ctx.sse() 返回此类型\n *\n * 设计要点:\n * - writer 内部用 ReadableStream + TextEncoder,send 时 enqueue,close 时 close controller\n * - response 预设 text/event-stream、no-cache、keep-alive 头,状态码默认 200\n * - close 后再 send 静默忽略,避免 handler 异步流程中误写已关闭的流\n * - sendError 向流写入 event: error 后关闭,用于流式输出中报错的优雅终止\n *\n * 与 ctx 的集成:\n * - ctx.sse() 调用 createSseWriter(),并把 response 缓存到 ctx 内部字段\n * - invokeHandler 在 handler 返回后检查 ctx 是否持有 SSE response,有则优先使用\n */\n\n/**\n * SSE 事件字段\n *\n * 遵循 HTML5 SSE 规范:\n * - `data`:消息数据,多行时每行加 `data: ` 前缀;对象自动 JSON.stringify\n * - `event`:事件类型,客户端可用 addEventListener(event) 监听\n * - `id`:事件 ID,客户端断线重连时通过 Last-Event-ID 头发送\n * - `retry`:重连等待时间(毫秒)\n * - `comment`:注释行(以 `:` 开头),用于 keep-alive 心跳,不传递给客户端消息\n */\nexport interface SseEvent {\n /** 消息数据。字符串原样输出;对象自动 JSON.stringify;多行时每行加 data: 前缀 */\n data?: unknown;\n /** 事件类型,客户端可用 addEventListener 监听 */\n event?: string;\n /** 事件 ID,客户端断线重连时通过 Last-Event-ID 头发送 */\n id?: string | number;\n /** 重连等待时间(毫秒) */\n retry?: number;\n /** 注释行(以 : 开头),用于 keep-alive 心跳 */\n comment?: string;\n}\n\n/**\n * 把 SSE 事件对象编码为符合 HTML5 SSE 规范的字符串\n *\n * 字段顺序固定:comment > event > id > retry > data,末尾空行分隔事件。\n *\n * @param event SSE 事件对象\n * @returns 编码后的字符串(含末尾空行)\n */\nexport function encodeSseEvent(event: SseEvent): string {\n let out = '';\n\n // comment(注释行,以 : 开头)\n if (event.comment !== undefined) {\n out += `: ${event.comment}\\n`;\n }\n\n // event\n if (event.event !== undefined) {\n out += `event: ${event.event}\\n`;\n }\n\n // id\n if (event.id !== undefined) {\n out += `id: ${event.id}\\n`;\n }\n\n // retry\n if (event.retry !== undefined) {\n out += `retry: ${event.retry}\\n`;\n }\n\n // data:对象 JSON.stringify,多行每行加前缀\n if (event.data !== undefined) {\n let dataStr: string;\n if (typeof event.data === 'string') {\n dataStr = event.data;\n } else if (event.data === null) {\n dataStr = 'null';\n } else {\n dataStr = JSON.stringify(event.data);\n }\n // 多行 data:每行加 data: 前缀\n const lines = dataStr.split('\\n');\n for (const line of lines) {\n out += `data: ${line}\\n`;\n }\n }\n\n // 空行结束事件\n out += '\\n';\n return out;\n}\n\n/**\n * SSE writer:封装流式推送 API\n *\n * 通过 `ctx.sse()` 创建,handler 调用 `send` 推送事件,`close` 关闭流。\n * 框架在 handler 返回后,自动使用 writer.response 作为 HTTP 响应。\n */\nexport interface SseWriter {\n /** 推送一个 SSE 事件 */\n send(event: SseEvent): void;\n /**\n * 直接写入原始字节/字符串,不做任何 SSE 序列化\n *\n * 用于透传上游已有的 SSE 原文(如 LLM 中转平台逐 chunk 转发 OpenAI 响应)。\n * 调用方负责保证内容符合 HTML5 SSE 规范;`send` 会再次加 `data: ` 前缀,\n * 不适用于原文透传场景。\n *\n * 接受 string 或 Uint8Array(Buffer 是 Uint8Array 子类,自然兼容)。\n * 与 `send` 一致:close/aborted 后静默忽略,不抛错。\n */\n sendRaw(chunk: string | Uint8Array): void;\n /** 推送一个 error 事件并关闭流(用于流式输出中报错的优雅终止) */\n sendError(error: unknown): void;\n /** 关闭流(多次调用安全) */\n close(): void;\n /** 流是否已关闭(handler 主动 close 或框架自动 close) */\n readonly closed: boolean;\n /** 客户端是否已断开(ReadableStream 被 cancel) */\n readonly aborted: boolean;\n /** 对应的 HTTP Response(由框架使用,用户一般不需要直接访问) */\n readonly response: Response;\n}\n\n/**\n * 创建一个 SSE writer\n *\n * 内部用 ReadableStream + TextEncoder 实现,send 时把编码后的事件 enqueue 到流,\n * close 时关闭 controller。response 预设标准 SSE 头。\n *\n * aborted 检测:监听 ReadableStream 的 cancel 钩子,客户端断开(cancel)时置为 true。\n * 此时 send 静默忽略,handler 可通过 writer.aborted 退出循环。\n */\nexport function createSseWriter(): SseWriter {\n const encoder = new TextEncoder();\n let controller: ReadableStreamDefaultController<Uint8Array> | null = null;\n let closed = false;\n let aborted = false;\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c;\n },\n cancel() {\n // 客户端断开连接(cancel ReadableStream)\n aborted = true;\n closed = true;\n controller = null;\n },\n });\n\n const response = new Response(stream, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n\n const writer: SseWriter = {\n send(event: SseEvent): void {\n if (closed || !controller) return;\n const text = encodeSseEvent(event);\n controller.enqueue(encoder.encode(text));\n },\n\n sendRaw(chunk: string | Uint8Array): void {\n if (closed || !controller) return;\n const bytes = typeof chunk === 'string' ? encoder.encode(chunk) : chunk;\n controller.enqueue(bytes);\n },\n\n sendError(error: unknown): void {\n if (closed || !controller) return;\n const message = error instanceof Error ? error.message : String(error);\n const text = encodeSseEvent({ event: 'error', data: message });\n try {\n controller.enqueue(encoder.encode(text));\n } finally {\n writer.close();\n }\n },\n\n close(): void {\n if (closed) return;\n closed = true;\n if (controller) {\n try {\n controller.close();\n } catch {\n // controller 可能已关闭,忽略\n }\n controller = null;\n }\n },\n\n get closed(): boolean {\n return closed;\n },\n\n get aborted(): boolean {\n return aborted;\n },\n\n get response(): Response {\n return response;\n },\n };\n\n return writer;\n}\n","import type { ErrorCode } from './errorCodes';\n\nexport class FaapiError extends Error {\n constructor(\n public readonly code: ErrorCode,\n message: string,\n public readonly statusCode: number,\n ) {\n super(message);\n this.name = 'FaapiError';\n }\n}\n","import { FaapiError } from './FaapiError';\n\n/**\n * 根据 issues 中的 code 推导 HTTP 状态码\n *\n * 语义参考 RFC 7807 / Rails / Laravel / Spring:\n * - 400 Bad Request:请求语法错误或结构不完整\n * - INVALID_FORMAT:JSON 格式错误、Date 非 ISO 8601\n * - MISSING_FIELD:缺少必填字段\n * - 422 Unprocessable Entity:请求语法正确但语义错误\n * - TYPE_MISMATCH:类型不匹配(string 期望 number)\n * - INVALID_VALUE:值不在允许范围(字面量/enum 不匹配)\n * - COERCE_FAILED:query 字符串转换失败\n *\n * 多个 issue 时取最高严重度(400 优先于 422)。\n */\nfunction deriveStatusCode(issues: ValidationIssue[]): number {\n // 有 400 类 issue → 400;否则 422\n const has400 = issues.some((i) => i.code === 'INVALID_FORMAT' || i.code === 'MISSING_FIELD');\n return has400 ? 400 : 422;\n}\n\nexport class ValidationError extends FaapiError {\n constructor(\n message: string,\n public readonly issues: ValidationIssue[],\n ) {\n super('VALIDATION_ERROR', message, deriveStatusCode(issues));\n this.name = 'ValidationError';\n }\n}\n\nexport class RouteNotFoundError extends FaapiError {\n constructor(path: string) {\n super('ROUTE_NOT_FOUND', `Route not found: ${path}`, 404);\n this.name = 'RouteNotFoundError';\n }\n}\n\nexport class MethodNotAllowedError extends FaapiError {\n constructor(\n method: string,\n path: string,\n public readonly allowedMethods: string[],\n ) {\n super('METHOD_NOT_ALLOWED', `Method ${method} not allowed for ${path}`, 405);\n this.name = 'MethodNotAllowedError';\n }\n}\n\nexport class InternalError extends FaapiError {\n constructor(message: string) {\n super('INTERNAL_ERROR', message, 500);\n this.name = 'InternalError';\n }\n}\n\nexport class ModuleLoadError extends FaapiError {\n constructor(filePath: string, reason: string) {\n super('MODULE_LOAD_ERROR', `Failed to load module ${filePath}: ${reason}`, 500);\n this.name = 'ModuleLoadError';\n }\n}\n\n/**\n * 请求体超过大小限制(413 Payload Too Large)\n *\n * 由 createServer 的 limitStreamSize 在流式读取阶段抛出,\n * 经 formatErrorResponse 转换为 413 响应,而非兜底 500。\n */\nexport class PayloadTooLargeError extends FaapiError {\n constructor(maxSize: number) {\n super('PAYLOAD_TOO_LARGE', `Request body exceeds size limit of ${maxSize} bytes`, 413);\n this.name = 'PayloadTooLargeError';\n }\n}\n\n/**\n * 校验问题类型\n *\n * 结构化错误信息,便于上层(全局错误中间件/前端)按 code 做不同处理,\n * 不依赖字符串解析。message 仅为人类可读的兜底描述。\n *\n * code 与 HTTP 状态码的映射(由 ValidationError 推导):\n * - INVALID_FORMAT / MISSING_FIELD → 400 Bad Request\n * - TYPE_MISMATCH / INVALID_VALUE / COERCE_FAILED → 422 Unprocessable Entity\n */\nexport interface ValidationIssue {\n /** 字段路径,如 'user.address.city' */\n path: string;\n /** 错误码,机器可读的契约 */\n code: ValidationErrorCode;\n /** 期望类型/值,如 'number' / '\"admin\" | \"user\"' */\n expected: string;\n /** 实际类型/值,如 'string' / 'undefined' */\n received: string;\n /** 人类可读的本地化消息(兜底,不保证稳定) */\n message: string;\n}\n\nexport type ValidationErrorCode =\n | 'TYPE_MISMATCH' // 类型不匹配(string 期望 number) → 422\n | 'MISSING_FIELD' // 缺少必填字段 → 400\n | 'INVALID_FORMAT' // 格式错误(JSON 解析失败、Date 非 ISO 等) → 400\n | 'INVALID_VALUE' // 值不在允许范围(字面量/enum 不匹配) → 422\n | 'COERCE_FAILED'; // 类型转换失败(query 字符串转 number/boolean 失败) → 422\n","import type { ResponseConfig } from '../config/configTypes';\nimport { FaapiError } from '../errors/FaapiError';\nimport { ValidationError, MethodNotAllowedError, PayloadTooLargeError } from '../errors/httpErrors';\n\n/**\n * 统一响应格式化中心\n *\n * 集中所有响应包装规则,确保 handler return 值的自动包裹、ctx.fail() 主动错误响应、\n * formatErrorResponse 抛错兜底 三条路径共享同一套 ok/fail 函数,避免响应格式漂移。\n *\n * 路径分工:\n * - 成功路径: handler `return data` → wrapOkResult → toResponse 多类型序列化\n * - 显式成功: handler `return ctx.ok(data)` → wrapOkResult → jsonOk 构造 Response\n * - 主动错误: handler `return ctx.fail({...})` → formatFailResponse → jsonOk\n * - 抛错兜底: handler throw → formatErrorResponse(err, config) → jsonOk\n *\n * 业务方通过 faapi.config.ts 的 `response.ok` / `response.fail` 自定义包装函数,\n * 三条路径自动一致生效。\n */\n\n/** 默认 ok 包装:(data) => ({ data }) */\nexport function defaultOk(data: unknown): Record<string, unknown> {\n return { data };\n}\n\n/**\n * 默认 fail 包装:省略的字段不放入 error 对象\n *\n * `code` 为 undefined 时不放入响应,保持 body 紧凑。\n */\nexport function defaultFail(e: {\n status?: number;\n code?: string;\n message: string;\n}): Record<string, unknown> {\n const error: Record<string, unknown> = { message: e.message };\n if (e.code !== undefined) error.code = e.code;\n return { error };\n}\n\n/** 从 ctx.config 读取 ResponseConfig(业务方在 faapi.config.ts 中配置的 response 字段) */\nexport function getResponseConfig(\n config: Record<string, unknown> | undefined,\n): ResponseConfig | undefined {\n return (config as { response?: ResponseConfig } | undefined)?.response;\n}\n\n/** 解析 ok 函数:业务方自定义 ?? 框架默认 */\nexport function resolveOkFn(config: Record<string, unknown> | undefined) {\n return getResponseConfig(config)?.ok ?? defaultOk;\n}\n\n/** 解析 fail 函数:业务方自定义 ?? 框架默认 */\nexport function resolveFailFn(config: Record<string, unknown> | undefined) {\n return getResponseConfig(config)?.fail ?? defaultFail;\n}\n\n/**\n * 构造 JSON 成功响应(不包裹,原样序列化)\n *\n * 用于 ctx.json / ctx.ok 等显式响应场景。\n */\nexport function jsonOk(body: unknown, status = 200, extraHeaders?: HeadersInit): Response {\n return jsonRaw(body, status, extraHeaders);\n}\n\n/** 内部:构造 JSON Response,不包外层 */\nfunction jsonRaw(body: unknown, status: number, extraHeaders?: HeadersInit): Response {\n const headers = new Headers({ 'Content-Type': 'application/json' });\n if (extraHeaders) {\n const extra = new Headers(extraHeaders);\n extra.forEach((value, key) => headers.set(key, value));\n }\n return new Response(JSON.stringify(body), { status, headers });\n}\n\n/**\n * 成功响应自动包装:handler return 值 → okFn(data) 包裹\n *\n * 规则:\n * - Response 对象:不包裹(ctx.ok/ctx.fail/ctx.json 等返回的 Response 原样透传)\n * - 其他值(含 null/undefined):用 config.response.ok(或默认 (data) => ({ data })) 包裹\n *\n * 注:null/undefined 也会被包裹为 { data: null } / { data: undefined }。\n *\n * 不构造 Response,只返回包装后的值——最终序列化交给 toResponse 处理(支持多类型分发 +\n * 合并 ctx.meta 的 setStatus/setHeader/setCookie)。\n */\nexport function wrapOkResult(\n result: unknown,\n config: Record<string, unknown> | undefined,\n): unknown {\n if (result instanceof Response) return result;\n return resolveOkFn(config)(result);\n}\n\n/**\n * 业务方主动错误响应:FailOptions → Response\n *\n * handler `return ctx.fail({ status, code, message })` 的实现。\n * 用 config.response.fail(或默认实现)包装为 `{ error: { ... } }` 结构。\n *\n * `status` 省略时默认 500;`code` 省略时由 fail 函数决定是否放入 body。\n */\nexport function formatFailResponse(\n options: { status?: number; code?: string; message: string },\n config: Record<string, unknown> | undefined,\n): Response {\n const failFn = resolveFailFn(config);\n const body = failFn({\n status: options.status,\n code: options.code,\n message: options.message,\n });\n return jsonOk(body, options.status ?? 500);\n}\n\n/**\n * 抛错兜底响应:把 handler 抛出的错误转换为统一格式的 Response\n *\n * 覆盖的 FaapiError 子类:\n * - ValidationError → 400/422 + issues\n * - MethodNotAllowedError → 405 + Allow header\n * - PayloadTooLargeError → 413\n * - 其他 FaapiError 子类 → 用 error.statusCode\n * - 未知 Error / 非 Error → 500 INTERNAL_ERROR\n *\n * 与 [formatFailResponse] 共享同一套 fail 函数:业务方在 faapi.config.ts 自定义的\n * `response.fail` 会同时应用到「主动错误响应」和「抛错兜底响应」,确保错误格式一致。\n *\n * @param error handler 抛出的错误\n * @param config 业务方配置(用于读取 response.fail 自定义包装函数,可选)\n */\nexport function formatErrorResponse(\n error: unknown,\n config?: Record<string, unknown> | undefined,\n): Response {\n const failFn = resolveFailFn(config);\n\n if (error instanceof ValidationError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n // ValidationError 需要附加 issues 字段。业务方 failFn 可能返回共享/复用对象\n // (性能优化写法)或冻结对象——就地改写会让 issues 跨请求残留污染或静默失败,\n // 因此浅拷贝后再扩展,不改写业务方返回的原对象\n const bodyObj =\n typeof body === 'object' && body !== null\n ? ({ ...(body as Record<string, unknown>) } as Record<string, unknown>)\n : { error: body };\n const existingError = bodyObj.error as Record<string, unknown> | undefined;\n if (existingError && typeof existingError === 'object') {\n bodyObj.error = { ...existingError, issues: error.issues };\n } else if (typeof body === 'object' && body !== null) {\n // 无 error 包装层:body 本身就是错误对象\n bodyObj.issues = error.issues;\n } else {\n bodyObj.error = { issues: error.issues };\n }\n return jsonOk(bodyObj, error.statusCode);\n }\n\n if (error instanceof MethodNotAllowedError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode, { Allow: error.allowedMethods.join(', ') });\n }\n\n if (error instanceof PayloadTooLargeError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode);\n }\n\n if (error instanceof FaapiError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode);\n }\n\n // 未知错误:500 INTERNAL_ERROR\n const message = error instanceof Error ? error.message : 'An unknown error occurred';\n const body = failFn({ status: 500, code: 'INTERNAL_ERROR', message });\n return jsonOk(body, 500);\n}\n","import type { FaapiContext, ResponseMeta, CookieOptions, FailOptions } from './contextTypes';\nimport { createSseWriter, type SseWriter } from './sse';\nimport { wrapOkResult, formatFailResponse, jsonOk } from '../response/responseFormatter';\n\n/**\n * 解析 Cookie 请求头为 Map\n */\nfunction parseCookies(cookieHeader: string): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n for (const pair of cookieHeader.split(';')) {\n const [name, ...rest] = pair.split('=');\n const trimmed = name?.trim();\n if (trimmed) {\n cookies.set(trimmed, rest.join('=').trim());\n }\n }\n return cookies;\n}\n\n/**\n * 格式化 Set-Cookie 值\n */\nfunction formatSetCookie(name: string, value: string, options?: CookieOptions): string {\n let cookie = `${name}=${value}`;\n if (options?.domain) cookie += `; Domain=${options.domain}`;\n if (options?.path) cookie += `; Path=${options.path}`;\n if (options?.maxAge !== undefined) cookie += `; Max-Age=${options.maxAge}`;\n if (options?.expires) cookie += `; Expires=${options.expires.toUTCString()}`;\n if (options?.httpOnly) cookie += `; HttpOnly`;\n if (options?.secure) cookie += `; Secure`;\n if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;\n return cookie;\n}\n\n/**\n * 从 Request 对象创建 FaapiContext\n * @param request Web Request 对象\n * @param params 动态路由参数\n * @param config 自定义业务配置(来自 faapi.config.ts)\n * @param ip 客户端 IP(由调用方从 IncomingMessage 提取,HTTP/WS 握手均通过 utils/getClientIp)\n *\n * ua 不作为参数传入:User-Agent 是标准 HTTP 请求头,createContext 内部直接从\n * request.headers 读取(与 ip 不同,ip 需要从 IncomingMessage 提取故由调用方传入)。\n */\nexport function createContext(\n request: Request,\n params: Record<string, string>,\n config: Record<string, unknown> = {},\n ip: string = '',\n registries?: FaapiContext['registries'],\n): FaapiContext {\n return createContextFromUrl(request, new URL(request.url), params, config, ip, registries);\n}\n\n/**\n * createContext 的热路径变体:URL 由调用方解析一次后传入\n *\n * createServer 每请求需要 pathname/searchParams,若各自 `new URL(request.url)`\n * 一次请求要重复解析 3~4 次。此变体接收已解析的 URL 对象,searchParams 与\n * 调用方共享(只读)。普通场景用 {@link createContext}。\n */\nexport function createContextFromUrl(\n request: Request,\n url: URL,\n params: Record<string, string>,\n config: Record<string, unknown> = {},\n ip: string = '',\n registries?: FaapiContext['registries'],\n): FaapiContext {\n const meta: ResponseMeta = { headers: {}, setCookies: [] };\n const parsedCookies = parseCookies(request.headers.get('cookie') ?? '');\n const cookiesObj: Record<string, string> = {};\n for (const [key, val] of parsedCookies) {\n cookiesObj[key] = val;\n }\n\n const ctx = {\n request,\n params,\n query: url.searchParams,\n headers: request.headers,\n method: request.method,\n path: url.pathname,\n ip,\n ua: request.headers.get('user-agent') ?? '',\n cookies: cookiesObj,\n config,\n meta,\n\n setStatus(status: number) {\n meta.status = status;\n },\n\n setHeader(key: string, value: string) {\n meta.headers[key] = value;\n },\n\n setETag(value: string) {\n meta.headers['etag'] = value;\n },\n\n redirect(url: string, status = 302): Response {\n return new Response(null, {\n status,\n headers: { Location: url },\n });\n },\n\n json(data: unknown, status?: number): Response {\n return jsonOk(data, status ?? 200);\n },\n\n html(html: string, status?: number): Response {\n const headers: Record<string, string> = { 'Content-Type': 'text/html; charset=utf-8' };\n return new Response(html, {\n status: status ?? 200,\n headers,\n });\n },\n\n getCookie(name: string): string | undefined {\n return parsedCookies.get(name);\n },\n\n setCookie(name: string, value: string, options?: CookieOptions): void {\n meta.setCookies.push(formatSetCookie(name, value, options));\n },\n\n deleteCookie(name: string): void {\n meta.setCookies.push(formatSetCookie(name, '', { maxAge: 0 }));\n },\n\n /**\n * 创建 SSE writer,用于流式推送事件\n *\n * handler 调用此方法后,通过返回的 writer 推送事件,框架自动把 writer.response\n * 作为 HTTP 响应(Content-Type: text/event-stream)。\n *\n * 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。\n */\n sse(): SseWriter {\n const writer = createSseWriter();\n const ctxWithSse = ctx as FaapiContext & {\n __sseResponse?: Response;\n __sseWriter?: SseWriter;\n };\n ctxWithSse.__sseResponse = writer.response;\n ctxWithSse.__sseWriter = writer;\n return writer;\n },\n\n /**\n * 显式包装成功响应(返回 Response,不会被自动包裹再次包装)\n *\n * 实现委托给 [responseFormatter.wrapOkResult](../response/responseFormatter.ts),\n * 与 handler `return data` 走的自动包裹路径共享同一套 ok 函数。\n *\n * 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。\n * handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。\n */\n ok(data: unknown): Response {\n const body = wrapOkResult(data, config);\n // wrapOkResult 对 Response 对象直接透传(不会包裹),ctx.ok 接收到的是 okFn(data) 的对象\n // 业务方不应在 ctx.ok 里传 Response,这里 fallback 调 jsonOk 序列化\n return jsonOk(body, 200);\n },\n\n /**\n * 返回错误响应(对象形式参数,status 和 code 均可省略)\n *\n * 实现委托给 [responseFormatter.formatFailResponse](../response/responseFormatter.ts),\n * 与 formatErrorResponse(handler 抛错兜底)共享同一套 fail 函数,确保错误格式一致。\n *\n * - status 省略时 HTTP 状态码默认 500\n * - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)\n * - status 和 code 独立无关联\n *\n * body 用 config.response.fail(或默认实现)包装。\n */\n fail(options: FailOptions): Response {\n return formatFailResponse(options, config);\n },\n } as FaapiContext & { meta: ResponseMeta; __sseResponse?: Response; __sseWriter?: SseWriter };\n\n // app 级注册表(方案 A 实例化)——框架请求链路的 tool/agent/agentHandle 读取来源\n if (registries) {\n ctx.registries = registries;\n }\n\n // 执行用户自定义的 ctx 扩展钩子(config.extendContext)\n const extend = config?.extendContext;\n if (typeof extend === 'function') {\n extend(ctx);\n }\n\n return ctx;\n}\n\n/**\n * 测试专用:从选项对象创建 FaapiContext,免去手写 `new Request(url)` 的样板代码\n *\n * 与 createContext 的关系:createTestContext 内部构造 Request 后调 createContext,\n * 语义完全一致,仅是测试场景的语法糖——不写无意义的 host、query 用对象形式、headers 直接传对象。\n *\n * 为什么不合并进 createContext:createContext 运行时也从真实 HTTP 请求构造 Request,\n * 保持 `(request: Request)` 签名使运行时与测试同构;createTestContext 是纯测试便捷封装,\n * 不引入运行时分支。\n *\n * body 不在此处理:createContext 本身不读 `request.body`,body 注入由 `invokeHandler`\n * 的第 3 个参数负责。POST/PUT/PATCH 测试时 body 单独传给 invokeHandler,避免在两处传 body 产生混淆。\n *\n * @param options 请求选项(path 必填,其余可选)\n * @returns FaapiContext\n */\nexport function createTestContext(options: CreateTestContextOptions): FaapiContext {\n const {\n method = 'GET',\n path,\n query,\n headers,\n params = {},\n config = {},\n ip = '',\n registries,\n } = options;\n\n // 用 URL 解析 + 拼 query,避免手动拼接字符串的转义问题\n const url = new URL(`http://localhost${path}`);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (Array.isArray(value)) {\n for (const item of value) {\n url.searchParams.append(key, String(item));\n }\n } else {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const request = new Request(url.toString(), {\n method,\n headers: headers as HeadersInit | undefined,\n });\n\n return createContext(request, params, config, ip, registries);\n}\n\n/**\n * createTestContext 的选项\n */\nexport interface CreateTestContextOptions {\n /** app 级注册表(测试 handler 声明 agent/agents 参数时注入用,可选) */\n registries?: FaapiContext['registries'];\n /** 请求方法,默认 'GET' */\n method?: string;\n /** 请求路径,必填,如 '/api/user'(无需写 host) */\n path: string;\n /** 查询参数,对象形式,自动拼接到 URL(值会被 String() 转换;数组生成同名多值参数) */\n query?: Record<string, string | number | boolean | Array<string | number | boolean>>;\n /** 请求头 */\n headers?: Record<string, string>;\n /** 动态路由参数,默认 {} */\n params?: Record<string, string>;\n /** 业务配置(来自 faapi.config.ts),默认 {} */\n config?: Record<string, unknown>;\n /** 客户端 IP,默认 '' */\n ip?: string;\n}\n","export function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') {\n return false;\n }\n\n // 数组不是普通对象\n if (Array.isArray(value)) {\n return false;\n }\n\n // 检查原型链:普通对象的 prototype 是 Object.prototype 或 null(Object.create(null))\n const proto = Object.getPrototypeOf(value);\n return proto === null || proto === Object.prototype;\n}\n","import type { ResponseMeta } from '../runtime/contextTypes';\n\n/**\n * 仅 headers 型 meta 的延迟落头通道\n *\n * Web Response 一旦构造不可变——此前 `ctx.ok()` / `ctx.fail()` 等返回 Response\n * 的场景,即使 meta 只有 headers(helmet 开启后每请求 ~13 个静态头),也要\n * `new Headers()` 拷贝 + `new Response()` 重建一次。改为把 headers 记入\n * WeakMap(按响应对象弱键,响应被 GC 自动清理),由 Node 发送层\n * (sendNodeResponse)最后一次性 `setHeader`——热路径零重建。\n *\n * 中间件若重建 Response(如 compression / etag 的大 body 分支),需用\n * `consumePendingMetaHeaders` 把延迟头并入新 Response 的 headers,避免丢失。\n */\n\nconst pending = new WeakMap<Response, Record<string, string>>();\n\n/**\n * 记录延迟落头(headers-only meta 场景)\n *\n * 同一响应多次记录按 key 合并(后写覆盖,与 mergeMeta 的 headers.set 语义一致)。\n */\nexport function deferMetaHeaders(response: Response, headers: Record<string, string>): void {\n const existing = pending.get(response);\n if (existing) {\n Object.assign(existing, headers);\n return;\n }\n pending.set(response, { ...headers });\n}\n\n/**\n * 取出并清除响应的延迟头(发送层 / 重建方消费)\n */\nexport function consumePendingMetaHeaders(response: Response): Record<string, string> | undefined {\n const headers = pending.get(response);\n if (headers) pending.delete(response);\n return headers;\n}\n\n/** meta 是否仅含 headers(无 status / setCookies,可走延迟通道) */\nexport function isHeadersOnlyMeta(meta: ResponseMeta): boolean {\n return meta.status === undefined && meta.setCookies.length === 0;\n}\n","import { isPlainObject } from '../utils/isPlainObject';\nimport type { ResponseMeta } from '../runtime/contextTypes';\nimport { deferMetaHeaders, isHeadersOnlyMeta } from './pendingMeta';\n\n/**\n * 将 handler 返回值统一转换为 Response\n *\n * 规则:\n * - Response 对象:原样返回\n * - ReadableStream:直接作为 body\n * - Buffer / Uint8Array:作为二进制 body\n * - 普通对象/数组:JSON.stringify,Content-Type: application/json\n * - string:text/plain\n * - number/boolean:text/plain,String(value)\n * - null/undefined:204 No Content\n * - Promise:await 后再处理\n *\n * 如果传入了 meta,会将 ctx.setStatus / ctx.setHeader 的设置合并到 Response 中\n */\nexport async function toResponse(value: unknown, meta?: ResponseMeta): Promise<Response> {\n // Promise:await 后再处理\n if (value instanceof Promise) {\n return toResponse(await value, meta);\n }\n\n // 辅助函数:将 meta headers 和 setCookies 合并到 Headers 对象\n const applyMeta = (headers: Headers): void => {\n if (!meta) return;\n for (const [key, val] of Object.entries(meta.headers)) {\n headers.set(key, val);\n }\n for (const cookie of meta.setCookies ?? []) {\n headers.append('set-cookie', cookie);\n }\n };\n\n // Response 对象:原样返回(但合并 meta headers)\n if (value instanceof Response) {\n if (\n meta &&\n (meta.status !== undefined ||\n Object.keys(meta.headers).length > 0 ||\n meta.setCookies.length > 0)\n ) {\n // 仅 headers 时延迟到发送层落头(避免每请求 Headers 拷贝 + Response 重建,\n // helmet 开启后 meta.headers 恒非空)。status/cookie 需要 Response 级变更,仍重建\n if (isHeadersOnlyMeta(meta)) {\n deferMetaHeaders(value, meta.headers);\n return value;\n }\n const headers = new Headers(value.headers);\n applyMeta(headers);\n return new Response(value.body, {\n status: meta.status ?? value.status,\n headers,\n });\n }\n return value;\n }\n\n // null/undefined:204 No Content\n if (value === null || value === undefined) {\n const status = meta?.status ?? 204;\n const headers = new Headers();\n applyMeta(headers);\n return new Response(null, { status, headers });\n }\n\n // ReadableStream:直接作为 body\n if (typeof ReadableStream !== 'undefined' && value instanceof ReadableStream) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // Buffer / Uint8Array:作为二进制 body\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n if (value instanceof Uint8Array) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // 普通对象/数组:JSON.stringify,Content-Type: application/json\n if (isPlainObject(value) || Array.isArray(value)) {\n const body = JSON.stringify(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return new Response(body, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // string:text/plain\n if (typeof value === 'string') {\n const headers = new Headers({ 'Content-Type': 'text/plain' });\n applyMeta(headers);\n return new Response(value, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // number/boolean:text/plain,String(value)\n if (typeof value === 'number' || typeof value === 'boolean') {\n const headers = new Headers({ 'Content-Type': 'text/plain' });\n applyMeta(headers);\n return new Response(String(value), {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // 其他类型 fallback:JSON.stringify\n const body = JSON.stringify(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return new Response(body, {\n status: meta?.status ?? 200,\n headers,\n });\n}\n","import ts from 'typescript';\n\n/**\n * 参数注入类型\n *\n * Phase 2.3 扩展 `agent` / `agents`:\n * - `agent` —— 默认 agent 元数据(Phase 2.4 实现 `config.defaultAgent` 后注入值)\n * - `agents` —— 所有已注册 agent 的元数据列表(`agentRegistry.listAgents()`)\n */\nexport type InjectionType =\n | 'query'\n | 'body'\n | 'form'\n | 'headers'\n | 'params'\n | 'context'\n | 'cookies'\n | 'ip'\n | 'ua'\n | 'files'\n | 'fields'\n | 'agent'\n | 'agents'\n | 'unknown';\n\n/**\n * 注入项信息\n */\nexport interface InjectionItem {\n name: string; // 参数名\n type: InjectionType; // 注入类型\n hasType: boolean; // 是否有类型标注(运行时类型已擦除,始终为 false)\n}\n\n/**\n * 参数名到注入类型的映射(单一来源,构建期和运行时共用)\n *\n * `form` 与 `body` 互斥:handler 声明其一即可。`form` 适用于\n * `application/x-www-form-urlencoded` 请求体(值均为 string,schema coerce=true);\n * `body` 适用于 JSON 请求体(coerce=false)。两者共享 `resolveInput` 的解析结果。\n *\n * Phase 2.3 添加 `agent` / `agents`:\n * - `agent` → 注入默认 agent 元数据(Phase 2.4 实现,暂返回 `undefined`)\n * - `agents` → 注入所有已注册 agent 元数据列表(`agentRegistry.listAgents()`)\n *\n * Phase 3.x 的 `@faapi/agent` 插件通过注入器机制增强为 `AgentHandle`\n * (含可调用 `run` 函数),覆盖内置的元数据注入。\n */\nexport const PARAM_TYPE_MAP: Record<string, InjectionType> = {\n query: 'query',\n body: 'body',\n form: 'form',\n headers: 'headers',\n params: 'params',\n context: 'context',\n ctx: 'context', // 别名\n cookies: 'cookies',\n ip: 'ip',\n ua: 'ua',\n files: 'files',\n fields: 'fields',\n agent: 'agent', // Phase 2.3\n agents: 'agents', // Phase 2.3\n};\n\n/**\n * 分析结果按函数引用缓存。\n *\n * handler 函数在路由模块加载后引用稳定,参数列表不会变化;用 WeakMap 弱键,\n * dev 热替换后旧模块的函数不再被引用即可随 GC 回收,不需要手动失效。\n */\ntype AnyFn = (...args: unknown[]) => unknown;\nconst injectionCache = new WeakMap<AnyFn, InjectionItem[]>();\n\n/**\n * 分析函数参数,决定需要注入什么内容\n *\n * 使用 TypeScript AST 解析 fn.toString() 的结果,\n * 正确处理解构参数、默认值、rest 参数等情况。\n *\n * 注意:运行时类型信息已被擦除,hasType 始终为 false。\n *\n * 返回的数组是缓存的共享实例,调用方不得就地修改。\n */\nexport function resolveInjection(fn: (...args: unknown[]) => unknown): InjectionItem[] {\n const cached = injectionCache.get(fn);\n if (cached) {\n return cached;\n }\n\n const fnStr = fn.toString();\n const params = extractParamsWithAst(fnStr);\n\n const items = params.map((param) => {\n const type = PARAM_TYPE_MAP[param.name] || 'unknown';\n return {\n name: param.name,\n type,\n hasType: false, // 运行时类型已擦除\n };\n });\n\n injectionCache.set(fn, items);\n return items;\n}\n\n/**\n * 使用 TypeScript AST 从函数字符串中提取参数名\n *\n * 相比正则解析,AST 能正确处理:\n * - 解构参数 `function GET({ page, size })` → page, size\n * - 默认值 `function GET(query = {})` → query\n * - rest 参数 `function GET(...args)` → args\n * - 含逗号的泛型(类型擦除后不存在该问题)\n */\nfunction extractParamsWithAst(fnStr: string): Array<{ name: string }> {\n // 包装成模块,让 TS 能解析\n const sourceFile = ts.createSourceFile(\n '__faapi_injection__.ts',\n fnStr,\n ts.ScriptTarget.Latest,\n true,\n );\n\n const paramNames: string[] = [];\n\n function visit(node: ts.Node): void {\n // 函数声明:function GET(query) {}\n if (ts.isFunctionDeclaration(node) && node.parameters.length > 0) {\n for (const param of node.parameters) {\n extractParamName(param, paramNames);\n }\n return;\n }\n\n // 箭头函数 / 函数表达式:(query) => {} 或 function(query) {}\n if ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && node.parameters.length > 0) {\n for (const param of node.parameters) {\n extractParamName(param, paramNames);\n }\n return;\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n\n return paramNames.map((name) => ({ name }));\n}\n\n/**\n * 从单个参数节点提取参数名\n *\n * 支持的参数形式:\n * - 标识符:query → query\n * - 解构对象:{ page, size } → page, size(每个属性都作为一个注入项)\n * - 解构数组:[a, b] → a, b\n * - rest 参数:...args → args\n * - 默认值:query = {} → query\n */\nfunction extractParamName(param: ts.ParameterDeclaration, names: string[]): void {\n const name = param.name;\n\n // 标识符:query\n if (ts.isIdentifier(name)) {\n names.push(name.text);\n return;\n }\n\n // 解构对象:{ page, size }\n if (ts.isObjectBindingPattern(name)) {\n for (const element of name.elements) {\n if (ts.isBindingElement(element)) {\n const elemName = element.name;\n if (ts.isIdentifier(elemName)) {\n names.push(elemName.text);\n }\n }\n }\n return;\n }\n\n // 解构数组:[a, b]\n if (ts.isArrayBindingPattern(name)) {\n for (const element of name.elements) {\n if (element && ts.isBindingElement(element)) {\n const elemName = element.name;\n if (ts.isIdentifier(elemName)) {\n names.push(elemName.text);\n }\n }\n }\n return;\n }\n}\n","/**\n * URLSearchParams → query 对象\n *\n * 重复 key 聚合为数组(对齐 Express qs / Hono getAll 语义):\n * - `?page=1` → `{ page: '1' }`\n * - `?tag=a&tag=b` → `{ tag: ['a', 'b'] }`\n *\n * 此前 last-wins 静默丢弃前面的值(`?ids=1&ids=2` 得 `'2'`),且 query schema\n * 的 coerce 管线对 array 元素同样生成 preprocess——声明 `ids: number[]` 的字段\n * 此前永远校验失败(解析端从未产出数组),属于校验管线输入端漏洞。\n */\nexport function queryToObject(params: URLSearchParams): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of params) {\n const existing = result[key];\n if (existing === undefined) {\n result[key] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n result[key] = [existing, value];\n }\n }\n return result;\n}\n","import type { ToolMetadata } from '../ast/extractToolMetadata';\nimport type { AgentMetadata, AgentCore } from '../ast/extractAgentMetadata';\nimport type { FaapiContext } from '../runtime/contextTypes';\n\n/**\n * app 级注册表(方案 A:注册表实例化)\n *\n * 每个应用实例(`createAppBase`)持有一套独立的注册表,随 app 创建、随\n * `app.close()` 销毁——多 app 同进程(测试 / 嵌入 / 多租户)互不串台。\n * 此前注册表是模块级全局单例 + hydrate 整体替换语义,后创建的 app 会覆盖\n * 先创建的 app 的清单,任一 app close 会清空全部(详见各模块 .md)。\n *\n * 原四个模块(toolRegistry / agentRegistry / skillRegistry / agentHandle)\n * 保留同名全局函数作为**默认实例的便捷访问器**(供编程式直调 / 单元测试 /\n * 无 app 上下文的场景),内部全部路由到本模块的 `defaultRegistries`。\n * 框架自身链路(hydrate / 请求注入 / `@faapi/agent` 插件 / lifecycle 钩子)\n * 一律走 app 实例,不再读写全局默认实例。\n */\n\n// ─── Tool 注册表 ─────────────────────────────────────────────────────\n\nexport interface ToolRegistry {\n /** 全量替换(tool 清单来自编译期产物,reload 时整体重新生成) */\n hydrate(tools: ToolMetadata[]): void;\n /** 按全名查找(如 `weather.getWeather`) */\n get(name: string): ToolMetadata | undefined;\n /** 所有已注册 tool(副本) */\n list(): ToolMetadata[];\n clear(): void;\n}\n\nexport function createToolRegistry(): ToolRegistry {\n let registry = new Map<string, ToolMetadata>();\n return {\n hydrate(tools) {\n const next = new Map<string, ToolMetadata>();\n for (const tool of tools) {\n next.set(tool.name, tool);\n }\n registry = next;\n },\n get(name) {\n return registry.get(name);\n },\n list() {\n return Array.from(registry.values());\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Agent 注册表 ────────────────────────────────────────────────────\n\n/** agent 包装为 tool 的描述符(reactLoop 据此识别 sub-agent 递归) */\nexport interface AgentToolDescriptor {\n kind: 'agent';\n name: string;\n agentName: string;\n description?: string;\n metadata: AgentCore;\n}\n\nexport interface AgentRegistry {\n /** 全量替换(与 hydrateToolRegistry 同构) */\n hydrate(agents: AgentMetadata[]): void;\n /** LLM 可见元数据(AgentCore) */\n getAgent(name: string): AgentCore | undefined;\n /** 完整元数据(含 filePath / hasRun,供加载 handler.js 执行 run) */\n getAgentEntry(name: string): AgentMetadata | undefined;\n /** 所有已注册 agent 的 LLM 可见元数据(副本) */\n listAgents(): AgentCore[];\n /** 包装为 tool 描述符 */\n asTool(name: string): AgentToolDescriptor | undefined;\n /**\n * 解析 agent 显式声明的 tool 集合。\n * 跨注册表依赖:经由构造时绑定的 tool 注册表查找(同属一套 AppRegistries,\n * 由 createAppBase 在同一启动阶段水合)\n */\n resolveAgentTools(name: string): ToolMetadata[];\n /** 解析 agent 可调用的子 agent 集合 */\n resolveSubAgents(name: string): AgentCore[];\n clear(): void;\n}\n\nexport function createAgentRegistry(tool: ToolRegistry): AgentRegistry {\n let registry = new Map<string, AgentMetadata>();\n\n const getAgent = (name: string): AgentCore | undefined => registry.get(name);\n\n return {\n hydrate(agents) {\n const next = new Map<string, AgentMetadata>();\n for (const agent of agents) {\n next.set(agent.name, agent);\n }\n registry = next;\n },\n getAgent,\n getAgentEntry(name) {\n return registry.get(name);\n },\n listAgents() {\n const merged = new Map<string, AgentCore>();\n for (const agent of registry.values()) merged.set(agent.name, agent);\n return Array.from(merged.values());\n },\n asTool(name) {\n const agent = getAgent(name);\n if (!agent) return undefined;\n return {\n kind: 'agent',\n name: `agent.${agent.name}`,\n agentName: agent.name,\n description: agent.description,\n metadata: agent,\n };\n },\n resolveAgentTools(name) {\n const agent = getAgent(name);\n if (!agent) return [];\n\n const result = new Map<string, ToolMetadata>();\n if (agent.tools) {\n for (const toolName of agent.tools) {\n const resolved = tool.get(toolName);\n if (resolved) result.set(resolved.name, resolved);\n // 未找到的 tool 名静默跳过(tool 可选可用,不强制存在)\n }\n }\n return Array.from(result.values());\n },\n resolveSubAgents(name) {\n const agent = getAgent(name);\n if (!agent || !agent.agents) return [];\n\n const result: AgentCore[] = [];\n for (const subName of agent.agents) {\n const sub = getAgent(subName);\n if (sub) result.push(sub);\n // 未注册的 agent 名跳过(agent 可选可用,不强制存在)\n }\n return result;\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Skill 注册表 ────────────────────────────────────────────────────\n\nexport interface SkillRegistry {\n /** 全量替换(DB change stream 场景也可用 upsert 增量) */\n hydrate(skills: AgentCore[]): void;\n /** 增量注册 / 覆盖(业务方监听 DB 单条变更) */\n upsert(skill: AgentCore): void;\n remove(name: string): void;\n get(name: string): AgentCore | undefined;\n list(): AgentCore[];\n clear(): void;\n}\n\nexport function createSkillRegistry(): SkillRegistry {\n let registry = new Map<string, AgentCore>();\n return {\n hydrate(skills) {\n const next = new Map<string, AgentCore>();\n for (const skill of skills) {\n next.set(skill.name, skill);\n }\n registry = next;\n },\n upsert(skill) {\n registry.set(skill.name, skill);\n },\n remove(name) {\n registry.delete(name);\n },\n get(name) {\n return registry.get(name);\n },\n list() {\n return Array.from(registry.values());\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Agent handle 工厂 ───────────────────────────────────────────────\n\n/** agent handle 工厂函数(由 `@faapi/agent` 插件注册) */\nexport type AgentHandleFactory = (ctx: FaapiContext) => unknown;\n\nexport interface AgentHandleStore {\n /** 注册工厂(null 清理);二次注册覆盖 */\n register(factory: AgentHandleFactory | null): void;\n /** 工厂已注册时返回 AgentHandle 实例,未注册返回 undefined */\n get(ctx: FaapiContext): unknown;\n clear(): void;\n}\n\nexport function createAgentHandleStore(): AgentHandleStore {\n let currentFactory: AgentHandleFactory | null = null;\n return {\n register(factory) {\n currentFactory = factory;\n },\n get(ctx) {\n if (currentFactory === null) return undefined;\n return currentFactory(ctx);\n },\n clear() {\n currentFactory = null;\n },\n };\n}\n\n// ─── App 级集合 ──────────────────────────────────────────────────────\n\n/** 一个 app 实例持有的全套注册表 */\nexport interface AppRegistries {\n tool: ToolRegistry;\n agent: AgentRegistry;\n skill: SkillRegistry;\n agentHandle: AgentHandleStore;\n}\n\n/** 创建一套 app 级注册表(`createAppBase` 每次调用创建独立实例) */\nexport function createAppRegistries(): AppRegistries {\n const tool = createToolRegistry();\n const agent = createAgentRegistry(tool);\n const skill = createSkillRegistry();\n const agentHandle = createAgentHandleStore();\n return { tool, agent, skill, agentHandle };\n}\n\n/**\n * 默认实例:原全局函数的退路(编程式直调 / 单元测试 / 无 app 上下文场景)。\n *\n * 框架自身链路不读写此实例——app 创建自己的 AppRegistries 并在请求链路 /\n * 插件 / lifecycle 钩子中传递。多 app 场景下默认实例无隔离语义(等同旧全局行为)。\n */\nexport const defaultRegistries: AppRegistries = createAppRegistries();\n","import type { AgentCore, AgentMetadata } from '../ast/extractAgentMetadata';\nimport { defaultRegistries } from './registries';\n\n// 类型从 registries 转发(AgentToolDescriptor 的定义源已随实例化迁入 registries)\nexport type { AgentToolDescriptor } from './registries';\n\n/**\n * agent 注册表全局访问器(默认实例便捷入口)\n *\n * 框架自身链路(`createAppBase` 水合 / `@faapi/agent` 插件 deps / `agents`\n * 参数注入)已改为读写 **app 实例级注册表**(见 [registries.md](./registries.md))——\n * agent 注册表实例在构造时绑定同套 tool 注册表实例(`resolveAgentTools` 跨表查找),\n * 多 app 同进程互不串台。\n *\n * 本模块的同名函数保留为**默认实例**的便捷访问器(语义约束不变:不 fallback 到\n * skillRegistry、Core / Entry 双查询入口),供编程式直调 / 单元测试使用。\n *\n * ## 与 skillRegistry 物理隔离\n *\n * agent 负责核心流程,skill 用于拓展——查询函数不 fallback 到 skillRegistry,\n * 实例化后依旧如此(两套注册表实例互不引用)。\n *\n * 详见 [agentRegistry.md](./agentRegistry.md)。\n */\n\n/**\n * 水合默认实例的 agent 注册表(全量替换)\n *\n * 框架路径请使用 app 实例:`ctx.registries.agent.hydrate(agents)`。\n *\n * @param agents 从 `faapi-agents.js` 水合还原的 `AgentMetadata[]`\n */\nexport function hydrateAgentRegistry(agents: AgentMetadata[]): void {\n defaultRegistries.agent.hydrate(agents);\n}\n\n/**\n * 清空默认实例的 agent 注册表\n *\n * app close 清理的是 app 自己的实例,不经过此函数。\n */\nexport function clearAgentRegistry(): void {\n defaultRegistries.agent.clear();\n}\n\n/**\n * 按名查找单个 agent 的 LLM 可见元数据(默认实例)\n *\n * 返回 [AgentCore](../ast/extractAgentMetadata.md)(不含 filePath / hasRun)。\n * 加载 handler.js 执行 `run` 函数请用 [getAgentEntry](#getAgentEntry)。\n *\n * @param name agent 名(如 `researcher`,含 `@agent` 覆盖值)\n */\nexport function getAgent(name: string): AgentCore | undefined {\n return defaultRegistries.agent.getAgent(name);\n}\n\n/**\n * 按名查找单个 agent 的完整元数据(默认实例,含 filePath / hasRun)\n */\nexport function getAgentEntry(name: string): AgentMetadata | undefined {\n return defaultRegistries.agent.getAgentEntry(name);\n}\n\n/**\n * 返回默认实例所有已注册 agent 的 LLM 可见元数据(副本,仅文件型 agent)\n */\nexport function listAgents(): AgentCore[] {\n return defaultRegistries.agent.listAgents();\n}\n\n/**\n * 把 agent 包装为 tool 描述符(默认实例)\n */\nexport function asTool(name: string) {\n return defaultRegistries.agent.asTool(name);\n}\n\n/**\n * 解析 agent 显式声明的 tool 集合(默认实例,跨查默认 tool 注册表)\n */\nexport function resolveAgentTools(name: string) {\n return defaultRegistries.agent.resolveAgentTools(name);\n}\n\n/**\n * 解析 agent 可调用的子 agent 集合(默认实例,按声明顺序)\n */\nexport function resolveSubAgents(name: string): AgentCore[] {\n return defaultRegistries.agent.resolveSubAgents(name);\n}\n","import type { FaapiContext } from '../runtime/contextTypes';\n\n/**\n * agent handle 工厂注册表(单例)\n *\n * 让 `@faapi/agent` 插件在启动时注册「请求级 agent handle 工厂」,\n * [injectParams](./injectParams.md) 在 `agent` 参数注入时调工厂拿到 `AgentHandle` 实例。\n *\n * 解耦设计:faapi 核心不依赖 `@faapi/agent`——核心只提供注册 / 查询点,\n * 工厂返回 `unknown`,具体类型由 `@faapi/agent` 的 `AgentHandle` 接口定义。\n *\n * 详见 [agentHandle.md](./agentHandle.md)。\n */\n\n/** agent handle 工厂函数(由 `@faapi/agent` 插件注册) */\nexport type AgentHandleFactory = (ctx: FaapiContext) => unknown;\n\nimport { defaultRegistries } from './registries';\n\n/**\n * agent handle 工厂全局访问器(默认实例便捷入口)\n *\n * 框架路径已改为 app 实例:`@faapi/agent` 插件在 setup 时经\n * `ctx.registries.agentHandle.register(factory)` 注册到**当前 app 的实例**,\n * 请求注入经 `ctx.registries.agentHandle.get(ctx)` 读取——多 app 同进程互不覆盖。\n * 此前模块级单值会让第二个 app 的插件注册覆盖第一个。\n *\n * 本模块的同名函数保留为默认实例便捷访问器(向后兼容)。\n *\n * 详见 [agentHandle.md](./agentHandle.md) 与 [registries.md](./registries.md)。\n */\n\n/**\n * 注册默认实例的 agent handle 工厂\n *\n * 框架路径请使用 `ctx.registries.agentHandle.register(factory)`(PluginContext 提供)。\n * 传入 `null` 等效于清理。\n */\nexport function registerAgentHandleFactory(factory: AgentHandleFactory | null): void {\n defaultRegistries.agentHandle.register(factory);\n}\n\n/**\n * 从默认实例获取 agent handle(工厂未注册时返回 undefined)\n */\nexport function getAgentHandle(ctx: FaapiContext): unknown {\n return defaultRegistries.agentHandle.get(ctx);\n}\n\n/**\n * 清空默认实例的工厂注册\n */\nexport function clearAgentHandleFactory(): void {\n defaultRegistries.agentHandle.clear();\n}\n","import type { FaapiContext } from '../runtime/contextTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { resolveInjection, type InjectionType } from './resolveInjection';\nimport { queryToObject } from '../utils/queryToObject';\nimport type { MultipartResult } from '../utils/parseMultipart';\nimport { listAgents } from './agentRegistry';\nimport { getAgentHandle } from './agentHandle';\n\n/**\n * 根据注入类型获取对应的值(内置)\n *\n * Phase 2.3 扩展 `agent` / `agents`:\n * - `agents` → `listAgents()`(所有已注册 agent 元数据列表)\n * - `agent` → `getAgentHandle(ctx)`(Phase 3.5:调 `@faapi/agent` 插件注册的工厂)\n *\n * Phase 3.5 的 `@faapi/agent` 插件通过 [agentHandle](./agentHandle.md) 工厂注册机制\n * 提供 `AgentHandle`(含可调用 `run` / `stream`)。未注册工厂时返回 `undefined`。\n */\nfunction getBuiltinInjectionValue(type: InjectionType, ctx: FaapiContext, body?: unknown): unknown {\n switch (type) {\n case 'query':\n return queryToObject(ctx.query);\n case 'params':\n return ctx.params;\n case 'headers':\n return ctx.headers;\n case 'context':\n return ctx;\n case 'cookies':\n return ctx.cookies;\n case 'ip':\n return ctx.ip;\n case 'ua':\n return ctx.ua;\n case 'body':\n return body;\n // form 与 body 共享解析结果(resolveInput 已按 Content-Type 解析 form-urlencoded)\n // 差异仅在 schema 校验(form coerce=true,由 collectRouteSchemaSources 标记)\n case 'form':\n return body;\n case 'files':\n if (body && typeof body === 'object' && 'files' in body) {\n return (body as MultipartResult).files;\n }\n return [];\n case 'fields':\n if (body && typeof body === 'object' && 'fields' in body) {\n return (body as MultipartResult).fields;\n }\n return {};\n // Phase 2.3:注入所有已注册 agent 元数据列表\n // 方案 A:优先读 app 实例注册表,无实例(编程式直调 ctx)回退默认全局实例\n case 'agents':\n return ctx.registries ? ctx.registries.agent.listAgents() : listAgents();\n // Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle\n case 'agent':\n return ctx.registries ? ctx.registries.agentHandle.get(ctx) : getAgentHandle(ctx);\n default:\n return undefined;\n }\n}\n\n/**\n * 根据注入信息,准备参数值并调用 handler(异步版本)\n *\n * 支持 async handler。内置注入优先于注入器(避免 query/body 等被覆盖)。\n * 非内置参数从注入器注册表按参数名查找,按需执行。\n */\nexport async function injectParamsAsync(\n handler: (...args: unknown[]) => unknown,\n ctx: FaapiContext,\n body?: unknown,\n injectors?: InjectorMap,\n): Promise<unknown> {\n const injections = resolveInjection(handler);\n\n if (injections.length === 0) {\n return await handler();\n }\n\n const args = await Promise.all(\n injections.map(async (injection) => {\n // 内置注入优先\n if (injection.type !== 'unknown') {\n return getBuiltinInjectionValue(injection.type, ctx, body);\n }\n // 注入器按参数名匹配\n if (injectors && injection.name in injectors) {\n // in 检查已证明存在\n return await injectors[injection.name]!(ctx);\n }\n return undefined;\n }),\n );\n\n return await handler(...args);\n}\n","import type { FaapiContext } from './contextTypes';\nimport type { ResponseMeta } from './contextTypes';\nimport type { SseWriter } from './sse';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { toResponse } from '../response/toResponse';\nimport { deferMetaHeaders, isHeadersOnlyMeta } from '../response/pendingMeta';\nimport { wrapOkResult } from '../response/responseFormatter';\nimport { injectParamsAsync } from '../injection/injectParams';\n\n/**\n * 自动包裹 handler 返回值(统一响应包装)\n *\n * 实现已移至 [response/responseFormatter.ts](../response/responseFormatter.ts) 的\n * wrapOkResult,与 ctx.ok() / formatFailResponse / formatErrorResponse 共享同一套\n * ok/fail 函数,确保响应格式在所有路径一致。\n *\n * 规则:\n * - Response 对象:不包裹(ctx.ok/ctx.fail/ctx.json 等返回的 Response 原样透传)\n * - 其他值(含 null/undefined):用 config.response.ok(或默认 (data) => ({ data })) 包裹\n *\n * ctx.ok() 返回的是 Response 对象,所以 handler 用 `return ctx.ok(data)` 或 `return data`\n * 最终响应一致(都是 ok(data) 的结果),不会双重包裹。\n *\n * 注意:null/undefined 也会被包裹为 { data: null } / { data: undefined }(JSON.stringify\n * 后者得到 \"{}\"),不再返回 204 No Content。如需 204,显式返回 Response 对象。\n */\nfunction wrapResult(result: unknown, ctx: FaapiContext): unknown {\n return wrapOkResult(result, ctx.config);\n}\n\n/**\n * 将 ctx.meta(setStatus/setHeader/setCookie 设置的响应元数据)合并到 Response\n *\n * 用于中间件返回 Response 时,确保用户通过 ctx.setStatus/setHeader 设置的\n * 状态码、响应头、cookie 能正确附加到中间件返回的 Response 上。\n */\nexport function mergeMeta(response: Response, meta: ResponseMeta): Response {\n const hasMeta =\n meta.status !== undefined || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;\n if (!hasMeta) return response;\n\n // 仅 headers 时延迟到发送层落头(零 Response 重建,见 pendingMeta.ts)\n if (isHeadersOnlyMeta(meta)) {\n deferMetaHeaders(response, meta.headers);\n return response;\n }\n\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(meta.headers)) {\n headers.set(key, value);\n }\n for (const cookie of meta.setCookies) {\n headers.append('set-cookie', cookie);\n }\n return new Response(response.body, {\n status: meta.status ?? response.status,\n headers,\n });\n}\n\n/**\n * 洋葱模型调度:将中间件链包装成 next 函数\n *\n * 每个中间件的 `await next()` 会执行下一个中间件并返回其 Response;\n * 最内层的 next 执行 handler(含参数注入)。\n *\n * 中间件返回 Response 的语义:\n * - 在 await next() 之前返回:拦截请求(不执行后续中间件和 handler)\n * - 在 await next() 之后返回:替换内层返回的响应\n * - 在 catch 块中返回:作为错误响应\n *\n * 中间件返回 void/undefined:使用 await next() 返回的内层响应。\n * 中间件不调用 next() 也不返回 Response:抛错(语义模糊,禁止使用)。\n *\n * 中间件返回 Response 时会合并 ctx.meta(setStatus/setHeader/setCookie 的设置),\n * 确保 ctx 便捷方法在中间件拦截场景下也生效。\n */\nexport async function compose(\n middlewares: FaapiMiddleware[],\n ctx: FaapiContext,\n finalHandler: () => Promise<Response>,\n): Promise<Response> {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n let index = -1;\n\n async function dispatch(i: number): Promise<Response> {\n if (i <= index) {\n throw new Error('next() called multiple times');\n }\n index = i;\n\n // 所有中间件执行完,运行 handler\n if (i >= middlewares.length) {\n return await finalHandler();\n }\n\n // i >= middlewares.length 已早退,此处索引必然存在\n const mw = middlewares[i]!;\n // next() 返回内层 Response,中间件可选择使用或替换\n let innerResponse: Response | undefined;\n const next = async (): Promise<Response> => {\n innerResponse = await dispatch(i + 1);\n return innerResponse;\n };\n\n const result = await mw(ctx, next);\n\n // 中间件返回 Response:使用它(拦截或替换),并合并 ctx.meta\n if (result instanceof Response) {\n return mergeMeta(result, meta);\n }\n // 中间件返回 void:使用 next() 返回的内层响应\n if (innerResponse !== undefined) {\n return innerResponse;\n }\n // 中间件既没返回 Response 也没调用 next():语义模糊,禁止\n throw new Error('中间件必须 await next() 或返回 Response');\n }\n\n return await dispatch(0);\n}\n\n/**\n * 调用路由 handler 并将返回值转为 Response\n *\n * 流程(洋葱模型):\n * 1. 中间件按洋葱模型执行:mw1.before → mw2.before → ... → handler → ... → mw2.after → mw1.after\n * 2. 中间件不调用 next() 即拦截请求(必须返回 Response)\n * 3. 中间件可用 try/catch 捕获内层错误\n * 4. 最内层执行注入器(按需)→ handler\n *\n * 注入器与中间件解耦:\n * - 注入器按 handler 参数名匹配,只执行需要的\n * - 注入器可读取中间件塞进 ctx 的值\n */\nexport async function invokeHandler(\n handler: (...args: unknown[]) => unknown,\n ctx: FaapiContext,\n body?: unknown,\n middlewares?: FaapiMiddleware[],\n injectors?: InjectorMap,\n): Promise<Response> {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n\n /**\n * 若 handler 调用了 ctx.sse(),优先使用 SSE Response\n * (handler 返回值被忽略,SSE 流由 writer 控制)\n *\n * 同时执行自动 close 兜底:若 handler 返回时 writer 仍未关闭,自动调用 close(),\n * 避免连接泄漏。handler 异步循环未退出、忘记 close、或抛错的场景由这里兜底。\n */\n const pickSseAndAutoClose = (): Response | null => {\n const sseWriter = (ctx as FaapiContext & { __sseWriter?: SseWriter }).__sseWriter;\n if (!sseWriter) return null;\n // 兜底:handler 未显式 close 时自动关闭\n if (!sseWriter.closed && !sseWriter.aborted) {\n sseWriter.close();\n }\n return mergeMeta(sseWriter.response, meta);\n };\n\n // 抛错时也保证 writer 被关闭(避免流泄漏)\n const autoCloseSseOnError = (): void => {\n const sseWriter = (ctx as FaapiContext & { __sseWriter?: SseWriter }).__sseWriter;\n if (sseWriter && !sseWriter.closed && !sseWriter.aborted) {\n sseWriter.close();\n }\n };\n\n // 无中间件:直接执行 handler\n if (!middlewares || middlewares.length === 0) {\n try {\n const result = await injectParamsAsync(handler, ctx, body, injectors);\n const sseResponse = pickSseAndAutoClose();\n if (sseResponse) return sseResponse;\n return toResponse(wrapResult(result, ctx), meta);\n } catch (err) {\n // handler 抛错时关闭未完成的 SSE 流,避免泄漏\n autoCloseSseOnError();\n throw err;\n }\n }\n\n // 有中间件:洋葱模型调度\n const finalHandler = async (): Promise<Response> => {\n try {\n const result = await injectParamsAsync(handler, ctx, body, injectors);\n const sseResponse = pickSseAndAutoClose();\n if (sseResponse) return sseResponse;\n return toResponse(wrapResult(result, ctx), meta);\n } catch (err) {\n // handler 抛错时关闭未完成的 SSE 流,避免泄漏\n autoCloseSseOnError();\n throw err;\n }\n };\n\n return await compose(middlewares, ctx, finalHandler);\n}\n","import type { Server } from 'node:http';\nimport path from 'node:path';\nimport os from 'node:os';\nimport fs from 'node:fs/promises';\nimport { scanRoutes } from './router/scanRoutes';\nimport { sortRoutes } from './router/sortRoutes';\nimport { generateSchemaFiles } from './cli/generateSchemaFiles';\nimport { invalidateSchemaCache } from './validator/validateInput';\nimport { createServer } from './server/createServer';\nimport type { RouteManifest, WsRouteManifest } from './router/routeTypes';\nimport type { FaapiMiddleware } from './middleware/middlewareTypes';\nimport type { InjectorMap } from './middleware/injectorTypes';\nimport type { CorsOptions } from './middleware/cors';\nimport type { HelmetOptions } from './middleware/helmet';\nimport type { LoggerOptions } from './middleware/logger';\nimport type { FaapiContext } from './runtime/contextTypes';\n\n/**\n * createTestServer 入参\n *\n * 业务方一行代码启动带 schema 校验的 E2E 测试服务器。\n * 详见 src/testServer.md。\n */\nexport interface TestServerOptions {\n /** 项目根目录(路由源码所在,必填) */\n rootDir: string;\n // 路由扫描 glob,相对 rootDir;默认 ['src/api/**/*.ts']\n patterns?: string[];\n /**\n * schema 产物输出目录(绝对路径或相对 rootDir)。\n * 不传时自动 mkdtemp 生成临时目录,close() 时清理。\n * 传值时 close() 仍会清理该目录。\n */\n dist?: string;\n /** CORS 中间件配置,默认 false(禁用,避免污染断言) */\n cors?: CorsOptions | boolean;\n /** 安全头配置,默认 false */\n helmet?: HelmetOptions | boolean;\n /** 请求日志配置,默认 false(避免污染测试输出) */\n logger?: LoggerOptions | boolean;\n /** 全局中间件(外层洋葱) */\n middlewares?: FaapiMiddleware[];\n /** 全局注入器 */\n injectors?: InjectorMap;\n /** 请求错误钩子(在错误响应生成后调用,用于副作用) */\n onError?: (error: unknown, ctx: FaapiContext) => Promise<void> | void;\n /** 业务配置(注入到 ctx.config) */\n config?: Record<string, unknown>;\n /** 请求体大小限制(字节),默认 10MB */\n bodyLimit?: number;\n}\n\n/**\n * createTestServer 返回值\n *\n * 业务方通过 baseUrl 发 fetch 请求,close() 一行完成 teardown。\n */\nexport interface TestServer {\n /** Node.js HTTP Server 实例(已 listen) */\n server: Server;\n /** 形如 http://localhost:<随机端口> */\n baseUrl: string;\n /** 排序后的路由清单 */\n routes: RouteManifest;\n /** WebSocket 路由清单 */\n wsRoutes: WsRouteManifest;\n /** schema 临时目录绝对路径(业务方调试时可查看生成的 zod.js) */\n schemaDist: string;\n /**\n * 关闭 server + 清理 schema 目录 + 清空 schema 模块缓存\n *\n * 内部顺序:\n * 1. server.closeAllConnections?.()(Node 18+,强制断开 WS / 长连接)\n * 2. server.close()\n * 3. fs.rm(schemaDist, { recursive, force })\n * 4. invalidateSchemaCache()\n *\n * 幂等:重复调用不会重复清理。\n */\n close(): Promise<void>;\n}\n\n/** 默认路由扫描 glob */\nconst DEFAULT_PATTERNS = ['src/api/**/*.ts'];\n\n/** 默认请求体大小限制:10MB */\nconst DEFAULT_BODY_LIMIT = 10 * 1024 * 1024;\n\n/**\n * 一键启动带 schema 校验的 E2E 测试服务器\n *\n * 内部流程:\n * 1. scanRoutes 扫描路由\n * 2. sortRoutes 排序\n * 3. mkdtemp 创建临时 schema 目录(或用传入的 dist)\n * 4. generateSchemaFiles 生成 zod.js\n * 5. createServer 创建 server(默认禁用 CORS/Helmet/Logger,避免污染断言)\n * 6. server.listen(0) 随机端口\n * 7. 返回 TestServer\n *\n * 详见 src/testServer.md。\n *\n * @param options rootDir 必填,其余可选\n * @returns TestServer 实例\n */\nexport async function createTestServer(options: TestServerOptions): Promise<TestServer> {\n const {\n rootDir,\n patterns = DEFAULT_PATTERNS,\n dist,\n cors = false,\n helmet = false,\n logger = false,\n middlewares,\n injectors,\n onError,\n config,\n bodyLimit = DEFAULT_BODY_LIMIT,\n } = options;\n\n // 1. 扫描 + 排序路由\n const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);\n const sorted = sortRoutes(routes);\n\n // 2. schema 临时目录(未传 dist 时自动 mkdtemp)\n const schemaDist = dist\n ? path.isAbsolute(dist)\n ? dist\n : path.resolve(rootDir, dist)\n : await fs.mkdtemp(path.join(os.tmpdir(), 'faapi-test-schema-'));\n\n // 3. 生成 zod.js\n await generateSchemaFiles(sorted, rootDir, schemaDist);\n\n // 4. 创建 server(默认禁用 CORS/Helmet/Logger,避免污染断言)\n const { server } = createServer({\n routes: sorted,\n rootDir,\n dist: schemaDist,\n cors,\n helmet,\n logger,\n middlewares,\n injectors,\n onError,\n config,\n wsRoutes,\n bodyLimit,\n });\n\n // 5. listen(0) 随机端口\n const baseUrl = await listenOnRandomPort(server);\n\n // 6. 关闭状态标记(幂等保护)\n let closed = false;\n\n const testServer: TestServer = {\n server,\n baseUrl,\n routes: sorted,\n wsRoutes,\n schemaDist,\n\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n\n // 强制断开 WS / 长连接(Node 18+)\n const s = server as Server & {\n closeAllConnections?: () => void;\n closeIdleConnections?: () => void;\n };\n s.closeAllConnections?.();\n s.closeIdleConnections?.();\n\n // 关闭 HTTP server\n await new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n\n // 清理 schema 临时目录(失败不阻塞 close 流程;force: true 已尽可能容错)\n await fs.rm(schemaDist, { recursive: true, force: true }).catch(() => {\n /* 清理失败不阻塞:closed 标记已设,目录可能在 OS 临时目录被外部清理 */\n });\n\n // 清空 schema 模块缓存,避免串扰后续测试\n invalidateSchemaCache();\n },\n };\n\n return testServer;\n}\n\n/**\n * server.listen(0) 取随机端口,返回 http://localhost:<port>\n *\n * 抽出来便于失败时抛清晰错误。\n */\nfunction listenOnRandomPort(server: Server): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n server.listen(0, () => {\n const addr = server.address();\n if (typeof addr === 'object' && addr !== null) {\n resolve(`http://localhost:${addr.port}`);\n } else {\n reject(new Error('Failed to get server address'));\n }\n });\n server.on('error', (err) => {\n reject(err);\n });\n });\n}\n","import fg from 'fast-glob';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport type { RouteManifest, WsRouteManifest } from './routeTypes';\nimport { HTTP_METHODS } from './constants';\nimport { filePathToUrlPath, extractParamNames, isCatchAllSegment } from './parseRouteFile';\nimport { loadMergedMiddlewares } from '../middleware/loadMiddlewares';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { toProdFilePath } from '../utils/prodPaths';\n\n/**\n * 匹配源码中导出的 HTTP 方法或 WS 函数\n *\n * 支持:\n * - `export function GET() {}`\n * - `export async function POST() {}`\n * - `export const GET = () => {}` / `export const GET = async () => {}`\n * - `export function WS() {}`\n *\n * 不通过运行时 import 提取方法名,避免启动时全量加载 handler 模块(Vite 风格:\n * 路由发现与 handler 加载解耦,handler.js 按需编译/导入)。\n */\nconst HTTP_OR_WS_EXPORT_RE = new RegExp(\n String.raw`export\\s+(?:async\\s+)?(?:function\\s+|const\\s+)(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|WS)\\b`,\n 'g',\n);\n\n/**\n * 从源码内容中提取所有 HTTP 方法 + WS 导出名(去重)\n */\nfunction extractExportsFromSource(source: string): Set<string> {\n const names = new Set<string>();\n let match: RegExpExecArray | null;\n HTTP_OR_WS_EXPORT_RE.lastIndex = 0;\n while ((match = HTTP_OR_WS_EXPORT_RE.exec(source)) !== null) {\n names.add(match[1]!);\n }\n return names;\n}\n\n/**\n * 从路由文件所在目录向上逐级查找 middlewares.ts(源码),\n * 返回从根到路由目录的中间件文件**绝对路径列表**(不加载模块)。\n *\n * 设计意图:scanRoutes 不再启动时全量 import middlewares.js,仅收集路径;\n * 实际的中间件加载延后到 hydrateRoutes / 请求阶段(与 prod 的 hydrateRoutes 一致)。\n * 这样 dev 启动时 zero import,启动速度接近 Vite。\n *\n * 路径选择规则(与 generateRoutes.extractMiddlewarePaths 一致):\n * - dist 传入:返回**产物** middlewares.js 绝对路径(打平 src/ 前缀,存在性检查源码 .ts)\n * - dist 不传:返回**源码** middlewares.ts/.js 绝对路径(兼容无 dist 的旧调用方,如 testServer)\n *\n * @param routeFilePath 源码相对路径(如 src/api/hello/handler.ts)\n * @param rootDir 项目根目录\n * @param dist 产物目录(dist 或 .faapi),不传则返回源码路径\n * @returns 中间件文件绝对路径列表(根在前,路由目录在后);空数组表示无中间件\n */\nfunction collectMiddlewarePaths(routeFilePath: string, rootDir: string, dist?: string): string[] {\n const routeDir = path.dirname(routeFilePath);\n const resolvedRoot = path.resolve(rootDir);\n\n const paths: string[] = [];\n let currentDir = path.resolve(rootDir, routeDir);\n while (true) {\n if (dist) {\n // 产物模式:检查源码 middlewares.ts 是否存在,返回产物 middlewares.js 绝对路径\n const mwTsPath = path.join(currentDir, 'middlewares.ts');\n const mwJsPath = path.join(currentDir, 'middlewares.js');\n const absTsPath = path.resolve(rootDir, mwTsPath);\n const absJsPath = path.resolve(rootDir, mwJsPath);\n const absMwPath = fs.existsSync(absTsPath)\n ? absTsPath\n : fs.existsSync(absJsPath)\n ? absJsPath\n : null;\n if (absMwPath) {\n // 转为产物形式绝对路径(打平 src/ 前缀,加 dist 前缀)\n const relMwPath = path.relative(rootDir, absMwPath);\n const prodAbsPath = path.resolve(rootDir, toProdFilePath(relMwPath, dist));\n paths.push(prodAbsPath);\n }\n } else {\n // 源码模式(testServer / 单元测试用):直接返回源码 middlewares.ts/.js 绝对路径\n for (const ext of ['.ts', '.js']) {\n const mwPath = path.join(currentDir, `middlewares${ext}`);\n const absMwPath = path.resolve(rootDir, mwPath);\n if (fs.existsSync(absMwPath)) {\n paths.push(absMwPath);\n break;\n }\n }\n }\n if (currentDir === resolvedRoot) break;\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) break;\n currentDir = parentDir;\n }\n\n // paths 是从路由目录向上收集的,需反转为根在前\n paths.reverse();\n return paths;\n}\n\n/**\n * 按路径列表加载并合并中间件(根在前,路由目录在后)\n *\n * 已移至 `middleware/loadMiddlewares.ts` 的 `loadMergedMiddlewares`,\n * scanRoutes 不再预加载中间件(dist 模式只收集路径,请求阶段按需加载)。\n */\n\n/**\n * 扫描 api 目录,同时生成 HTTP 路由清单和 WebSocket 路由清单\n *\n * **Vite 风格**:启动时只读源码 + 正则提取方法名,不 import handler.js / middlewares.js。\n * 中间件改为\"收集路径\",加载延后到 hydrateRoutes / 请求阶段。\n *\n * 路由文件格式:handler.ts,导出 HTTP 方法名作为 handler,也可导出 `WS` 函数声明 WebSocket 路由\n * ```ts\n * // api/user/handler.ts\n * export function GET() { return { list: [] } }\n * export function POST(body: any) { return { created: true } }\n *\n * // WebSocket 路由:导出 WS 函数\n * export function WS(ctx: WsContext) {\n * return { onMessage(ws, msg) { ws.send(`echo: ${msg}`) } };\n * }\n * ```\n * 一个 handler.ts 可同时导出 HTTP 方法(GET/POST...)和 WS 函数,\n * 分别生成 HTTP RouteRecord 和 WsRouteRecord。\n *\n * **中间件加载策略**:\n * - dev/prod(dist 传入):scanRoutes 仅收集 middlewarePaths,不加载模块;\n * 由 hydrateRoutes(prod)或 dev 启动流程加载\n * - 无 dist 模式(testServer / 单元测试):scanRoutes 直接加载中间件并塞入 route.middlewares\n * (保持向后兼容,避免破坏现有测试)\n *\n * @param rootDir 项目根目录\n * @param patterns glob patterns(源码 .ts 路径,如 src/api 下所有 .ts)\n * @param dist 产物目录(dist 或 .faapi)。传入时 scanRoutes 不 import 任何模块;\n * 不传时 import 源码 .ts(旧模式,需要 tsx,仅 testServer/单测使用)。\n */\nexport async function scanRoutes(\n rootDir: string,\n patterns: string[],\n dist?: string,\n): Promise<{ routes: RouteManifest; wsRoutes: WsRouteManifest }> {\n const files = await fg(patterns, {\n cwd: rootDir,\n onlyFiles: true,\n absolute: false,\n });\n\n const routes: RouteManifest = [];\n const wsRoutes: WsRouteManifest = [];\n\n for (const file of files) {\n const normalizedFile = file.replace(/\\\\/g, '/');\n const fileName = normalizedFile.split('/').pop()!;\n\n // 处理 handler.{ts,js} — API 路由 + WebSocket 路由\n // dev/build 模式扫源码 .ts(dist 传入),start 模式扫产物 .js(由 hydrateRoutes 处理)\n if (fileName === 'handler.ts' || fileName === 'handler.js') {\n const absPath = path.resolve(rootDir, normalizedFile);\n const urlPath = filePathToUrlPath(normalizedFile);\n const paramNames = extractParamNames(urlPath);\n const isDynamic = paramNames.length > 0;\n const isCatchAll = normalizedFile.split('/').some(isCatchAllSegment);\n\n // 中间件:dist 模式只收集路径(按需加载),无 dist 模式直接加载\n let middlewarePaths: string[] | undefined;\n let middlewareBundle: { middlewares: FaapiMiddleware[]; injectors: InjectorMap } | undefined;\n if (dist) {\n // dev/prod 模式:仅收集路径,请求阶段按需加载(Vite 风格)\n middlewarePaths = collectMiddlewarePaths(normalizedFile, rootDir, dist);\n } else {\n // 无 dist 模式(testServer/单测):直接加载源码中间件\n const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);\n middlewareBundle = await loadMergedMiddlewares(mwPaths);\n }\n\n // 提取导出名(HTTP 方法 + WS):读源码 + 正则匹配,不 import 模块\n const source = await fs.promises.readFile(absPath, 'utf8').catch(() => '');\n const exportNames = extractExportsFromSource(source);\n const methods = HTTP_METHODS.filter((m) => exportNames.has(m));\n\n for (const method of methods) {\n routes.push({\n method,\n urlPath,\n filePath: normalizedFile,\n paramNames,\n isDynamic,\n isCatchAll: isCatchAll || undefined,\n middlewarePaths,\n middlewares: middlewareBundle?.middlewares,\n injectors: middlewareBundle?.injectors,\n });\n }\n\n // WS 导出\n if (exportNames.has('WS')) {\n wsRoutes.push({\n urlPath,\n filePath: normalizedFile,\n paramNames,\n isDynamic,\n isCatchAll: isCatchAll || undefined,\n middlewarePaths,\n middlewares: middlewareBundle?.middlewares,\n injectors: middlewareBundle?.injectors,\n });\n }\n continue;\n }\n }\n\n return { routes, wsRoutes };\n}\n","export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const;\n\nexport type HttpMethod = (typeof HTTP_METHODS)[number];\n\nconst HTTP_METHOD_SET: ReadonlySet<string> = new Set(HTTP_METHODS);\n\nexport function isHttpMethod(value: string): value is HttpMethod {\n return HTTP_METHOD_SET.has(value);\n}\n","/**\n * 标准化路径:\n * - 将反斜杠替换为正斜杠\n * - 去除重复的斜杠\n * - 去除尾部斜杠\n * - 确保以 / 开头(如果非空)\n */\nexport function normalizePath(path: string): string {\n if (!path) return '';\n\n // 将反斜杠替换为正斜杠\n let result = path.replace(/\\\\/g, '/');\n\n // 去除重复的斜杠\n result = result.replace(/\\/+/g, '/');\n\n // 去除尾部斜杠\n result = result.replace(/\\/+$/, '');\n\n // 确保以 / 开头(如果非空)\n if (result && !result.startsWith('/')) {\n result = '/' + result;\n }\n\n return result;\n}\n","import { normalizePath } from '../utils/normalizePath';\n\n/**\n * 将 [name] 格式的目录名转换为 :name 格式\n * '[id]' -> ':id'\n * '[...slug]' -> ':...slug'\n * 'login' -> 'login'\n */\nexport function dynamicSegmentToParam(segment: string): string {\n const match = segment.match(/^\\[(.+)\\]$/);\n if (match) {\n return ':' + match[1];\n }\n return segment;\n}\n\n/**\n * 从 URL 路径中提取动态参数名\n * '/user/:id' -> ['id']\n * '/auth/login' -> []\n * '/user/:id/posts/:postId' -> ['id', 'postId']\n * '/shop/:...slug' -> ['slug']\n */\nexport function extractParamNames(urlPath: string): string[] {\n const params: string[] = [];\n const segments = urlPath.split('/');\n for (const segment of segments) {\n if (segment.startsWith(':...')) {\n // catch-all 参数::...slug -> slug\n params.push(segment.slice(4));\n } else if (segment.startsWith(':')) {\n params.push(segment.slice(1));\n }\n }\n return params;\n}\n\n/**\n * 判断路径段是否为 catch-all 路由\n * '[...slug]' -> true\n * '[id]' -> false\n */\nexport function isCatchAllSegment(segment: string): boolean {\n return /^\\[\\.\\.\\..+\\]$/.test(segment);\n}\n\n/**\n * 判断路径段是否为路由分组\n * '(marketing)' -> true\n * 'login' -> false\n */\nexport function isRouteGroup(segment: string): boolean {\n return /^\\(.+\\)$/.test(segment);\n}\n\n/**\n * 将路由文件路径转换为 URL 路径\n * 输入: 相对于项目根目录的文件路径,如 'src/api/auth/login/handler.ts'\n * 输出: URL 路径,如 '/api/auth/login'\n *\n * 规则:\n * - 去掉 src/ 前缀(路由源码目录写死为 src)\n * - 去掉文件名(handler.ts 等)\n * - 将 [id] 转换为 :id\n * - 忽略路由分组 (groupName) — 不影响 URL\n * - 标准化路径\n *\n * 说明:路由源码目录写死为 src/api/,src/ 前缀剥离后 api/ 前缀保留在 URL 中,\n * 因此 URL 始终带 /api 前缀。\n */\nexport function filePathToUrlPath(filePath: string): string {\n // 去掉 src/ 前缀(路由源码目录写死为 src)\n const withoutPrefix = filePath.startsWith('src/') ? filePath.slice(4) : filePath;\n\n // 去掉文件名(最后一个路径段)\n const lastSlashIndex = withoutPrefix.lastIndexOf('/');\n const dirPath = lastSlashIndex === -1 ? '' : withoutPrefix.slice(0, lastSlashIndex);\n\n if (!dirPath) {\n return '';\n }\n\n // 将每个路径段中的 [name] 转换为 :name,忽略路由分组 (name)\n const segments = dirPath\n .split('/')\n .filter((s) => !isRouteGroup(s)) // 过滤掉路由分组\n .map(dynamicSegmentToParam);\n\n // 拼接并标准化路径\n return normalizePath(segments.join('/'));\n}\n","import { pathToFileURL } from 'node:url';\n\n/**\n * 当前模块加载时间戳(watch 模式下用于绕过 ESM 缓存)\n *\n * 由 createDevApp.reloadRoutes 调用 setLoadTimestamp 设置。\n * ES 模块单例保证所有 import 此模块的地方共享同一个值,无需 globalThis。\n */\nlet loadTs: number | undefined;\n\nexport function setLoadTimestamp(ts: number): void {\n loadTs = ts;\n}\n\n/**\n * vitest 的 importActual 函数类型\n *\n * vi.importActual(path) 走 Vite SSR pipeline:\n * - 识别 vitest.config.ts 的 resolve.alias 与 tsconfig paths 别名\n * - 让 vi.mock 在加载的模块内生效\n */\ntype ImportActualFn = (path: string) => Promise<unknown>;\n\n/**\n * 检测 vitest 的 vi.importActual(globals: true 时注入 globalThis.vi)\n *\n * 业务方 vitest.config.ts 需 test.globals: true,或在测试文件内\n * 显式 `import { vi } from 'vitest'` 后挂到 globalThis.vi。\n *\n * 不在 vitest 环境下返回 undefined,调用方回退到 Node 原生 import()。\n */\nfunction getVitestImportActual(): ImportActualFn | undefined {\n const vi = (globalThis as { vi?: { importActual?: ImportActualFn } }).vi;\n if (typeof vi?.importActual !== 'function') return undefined;\n return vi.importActual.bind(vi);\n}\n\n/**\n * 动态 import 文件\n *\n * 两种加载路径:\n *\n * 1. **vitest 环境**(`globalThis.vi.importActual` 可用):走 Vite SSR pipeline,\n * 识别 tsconfig paths 别名(如 `@/lib/db`)+ 让 `vi.mock` 生效。\n * 业务方在 vitest 下用 `createTestServer` 时自动启用。\n *\n * 2. **非 vitest 环境**:Node 原生 `import()`。watch 模式下拼接 `?t=<timestamp>`\n * query 绕过 ESM 缓存;非 watch 模式等价普通 `import()`。\n *\n * @param filePath 文件绝对路径\n * @param bustViteCache dev 按需模式下走 Node 原生 import(绕过 Vite SSR pipeline)。\n * handler.js / zod.js 是已编译产物(别名已在编译时重写为相对路径),不需要 Vite alias 解析,\n * 直接走 Node 原生 import + 时间戳 query 避免 Vite SSR 缓存干扰。\n * @returns 模块导出对象\n */\nexport async function importWithCacheBust(\n filePath: string,\n bustViteCache = false,\n): Promise<Record<string, unknown>> {\n // vitest 环境优先走 Vite pipeline(识别 tsconfig paths + vi.mock)\n const importActual = getVitestImportActual();\n if (importActual) {\n if (bustViteCache) {\n // dev 按需模式:handler.js / zod.js 是已编译产物(别名已在编译时重写为相对路径),\n // 不需要 Vite alias 解析,直接走 Node 原生 import + 时间戳 query 避免 Vite SSR 缓存干扰\n let url = pathToFileURL(filePath).href;\n url += `?t=${Date.now()}`;\n return (await import(url)) as Record<string, unknown>;\n }\n return (await importActual(filePath)) as Record<string, unknown>;\n }\n\n // 否则走 Node 原生 ESM import\n let url = pathToFileURL(filePath).href;\n if (loadTs !== undefined) {\n url += `?t=${loadTs}`;\n }\n return (await import(url)) as Record<string, unknown>;\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\nimport type { InjectorMap } from './injectorTypes';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\n\n/**\n * 中间件 + 注入器加载结果\n */\nexport interface LoadedMiddlewareBundle {\n middlewares: FaapiMiddleware[];\n injectors: InjectorMap;\n}\n\n/**\n * 中间件缓存,key 为 middlewares.ts 的绝对路径\n */\nconst middlewareCache = new Map<string, LoadedMiddlewareBundle>();\n\n/**\n * in-flight 加载去重:同一文件并发首载共享同一 Promise(冷启动并发首请求\n * 不重复 import + 合并)。invalidateMiddlewareCache 时一并清空\n */\nconst inFlight = new Map<string, Promise<LoadedMiddlewareBundle>>();\n\n/**\n * 失效所有中间件缓存(watch 模式下文件变化时调用)\n */\nexport function invalidateMiddlewareCache(): void {\n middlewareCache.clear();\n inFlight.clear();\n}\n\n/**\n * 从缓存中读取中间件 bundle(未命中返回 undefined)\n */\nexport function getCachedMiddlewares(absPath: string): LoadedMiddlewareBundle | undefined {\n return middlewareCache.get(absPath);\n}\n\n/**\n * 写入中间件缓存\n */\nexport function setCachedMiddlewares(absPath: string, bundle: LoadedMiddlewareBundle): void {\n middlewareCache.set(absPath, bundle);\n}\n\n/**\n * 从绝对路径加载 middlewares.ts 并校验\n *\n * 文件可导出:\n * - `default`:中间件数组(洋葱模型,每项为 async 函数)\n * - `injectors`:注入器映射表(按参数名匹配 handler 参数)\n *\n * 两者都是可选的,但至少要有一个。\n *\n * 加载失败时的语义:\n * - 文件 import 抛错(语法错误 / 路径不存在 / 运行时抛错)→ console.error 输出原始错误,\n * 返回空 bundle,确保服务仍可启动而非崩溃。鉴权等关键中间件失效后业务可由 onError 感知。\n * 开发期 watcher 会因 reloadRoutes 触发重新加载,实现自愈。\n */\nexport async function loadMiddlewaresFile(filePath: string): Promise<LoadedMiddlewareBundle> {\n let module: Record<string, unknown>;\n try {\n module = (await importWithCacheBust(filePath)) as Record<string, unknown>;\n } catch (err) {\n // 关键可见性:必须打印原始错误,否则业务方完全无感知(鉴权中间件失效等同于裸奔)\n console.error(\n `[faapi] Failed to load middlewares from ${filePath}:`,\n err instanceof Error ? (err.stack ?? err.message) : err,\n );\n return { middlewares: [], injectors: {} };\n }\n\n // 加载中间件数组\n const middlewares = (module.default ?? module.middlewares ?? []) as unknown[];\n if (!Array.isArray(middlewares)) {\n console.warn(`[faapi] middlewares.ts 应导出数组,已忽略: ${filePath}`);\n return { middlewares: [], injectors: {} };\n }\n\n const validMiddlewares = middlewares.filter((m: unknown) => {\n if (typeof m !== 'function') {\n console.warn(`[faapi] 无效的中间件项(应为函数),已忽略: ${typeof m}`);\n return false;\n }\n return true;\n }) as FaapiMiddleware[];\n\n // 加载注入器映射表(可选命名导出)\n const injectors = (module.injectors ?? {}) as InjectorMap;\n if (typeof injectors !== 'object' || injectors === null) {\n console.warn(`[faapi] injectors 应导出对象,已忽略: ${filePath}`);\n return { middlewares: validMiddlewares, injectors: {} };\n }\n\n // 校验注入器:每个值必须是函数\n const validInjectors: InjectorMap = {};\n for (const [name, injector] of Object.entries(injectors)) {\n if (typeof injector !== 'function') {\n console.warn(`[faapi] 注入器 ${name} 应为函数,已忽略`);\n continue;\n }\n validInjectors[name] = injector;\n }\n\n return { middlewares: validMiddlewares, injectors: validInjectors };\n}\n\n/**\n * 按路径列表加载并合并中间件(根在前,路由目录在后)\n *\n * 合并语义:\n * - 子级中间件追加在父级之后(洋葱模型:后注册的中间件在内层)\n * - 子级注入器覆盖父级同名注入器\n *\n * 单文件加载带缓存(getCachedMiddlewares/setCachedMiddlewares),重复调用仅首次真正加载。\n *\n * @param middlewarePaths 中间件文件绝对路径列表(根在前,路由目录在后)\n * @returns 合并后的中间件+注入器;无中间件时返回 undefined\n */\nexport async function loadMergedMiddlewares(\n middlewarePaths: string[],\n): Promise<LoadedMiddlewareBundle | undefined> {\n if (middlewarePaths.length === 0) return undefined;\n\n const mergedMiddlewares: FaapiMiddleware[] = [];\n const mergedInjectors: InjectorMap = {};\n\n for (const absMwPath of middlewarePaths) {\n let bundle: LoadedMiddlewareBundle | undefined = getCachedMiddlewares(absMwPath);\n if (bundle === undefined) {\n // in-flight 去重:并发首载共享同一 Promise(对照 compileOnDemand 的 mutex 模式)\n let loading = inFlight.get(absMwPath);\n if (!loading) {\n loading = loadMiddlewaresFile(absMwPath).then((result) => {\n setCachedMiddlewares(absMwPath, result);\n return result;\n });\n inFlight.set(absMwPath, loading);\n }\n bundle = await loading;\n }\n mergedMiddlewares.push(...bundle.middlewares);\n for (const [name, injector] of Object.entries(bundle.injectors)) {\n mergedInjectors[name] = injector;\n }\n }\n\n if (mergedMiddlewares.length === 0 && Object.keys(mergedInjectors).length === 0) {\n return undefined;\n }\n\n return { middlewares: mergedMiddlewares, injectors: mergedInjectors };\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\n\n/** 路由源码目录(写死为 src) */\nexport const APP_DIR = 'src';\n\n/** 路由文件扫描 patterns(devCommand / buildCommand / createAppCore 共用) */\nexport const ROUTE_PATTERNS = ['src/api/**/*.ts'];\n\n/**\n * 把源码 filePath(src/api/hello/handler.ts)转为产物路径(<dist>/api/hello/handler.js)\n *\n * 产物结构打平 src/ 前缀:去掉 `src/`,加 dist 前缀,.ts → .js。\n * 已带 `<dist>/` 前缀的输入保持不变(幂等)。\n */\nexport function toProdFilePath(filePath: string, dist: string): string {\n let rel = filePath.replace(/\\\\/g, '/');\n // 去掉 src/ 前缀(打平产物结构)\n if (rel.startsWith('src/')) {\n rel = rel.slice(4);\n }\n const jsPath = rel.replace(/\\.ts$/, '.js');\n return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;\n}\n\n/**\n * 源文件后缀 → 产物后缀(.ts/.tsx/.jsx → .js,其余原样)\n */\nexport function toProdExtension(filePath: string): string {\n if (filePath.endsWith('.ts')) return filePath.slice(0, -3) + '.js';\n if (filePath.endsWith('.tsx')) return filePath.slice(0, -4) + '.js';\n if (filePath.endsWith('.jsx')) return filePath.slice(0, -4) + '.js';\n return filePath;\n}\n\n/**\n * 规范化路径为 realpath(处理 macOS /tmp → /private/tmp 等符号链接场景)\n *\n * 目录不存在时回退到原路径(不抛错),保证调用方逻辑稳定。\n */\nexport function toRealPath(p: string): string {\n try {\n return fs.realpathSync(p);\n } catch {\n return p;\n }\n}\n\n/**\n * 判断 filePath 是否位于 dir 目录下(不依赖路径前缀字符串比较,兼容符号链接规范化差异)\n *\n * 基于 `path.relative`:相对路径不以 `..` 开头且非绝对路径时视为位于 dir 下。\n * 调用前应先用 `toRealPath` 规范化两侧路径,确保前缀一致。\n */\nexport function isInsideDir(filePath: string, dir: string): boolean {\n const rel = path.relative(dir, filePath);\n return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);\n}\n\n/**\n * 源文件相对路径剥离 `src/` 前缀(打平产物结构)\n *\n * schema/tool zod.js 输出路径计算的公共步骤:\n * `src/api/hello/handler.ts` → `api/hello/handler.ts`(取目录后 join dist + zod.js)\n */\nexport function stripSrcPrefix(sourceFile: string): string {\n const rel = sourceFile.replace(/\\\\/g, '/');\n return rel.startsWith('src/') ? rel.slice(4) : rel;\n}\n","import type { RouteManifest } from './routeTypes';\n\n/**\n * 路由排序规则:\n * 1. 静态路由优先于动态路由\n * 2. 动态路由优先于 catch-all 路由\n * 3. 路径段数少的优先\n * 4. 字母序排列\n */\nexport function sortRoutes(routes: RouteManifest): RouteManifest {\n return [...routes].sort((a, b) => {\n // 1. 静态路由优先于动态路由\n if (a.isDynamic !== b.isDynamic) {\n return a.isDynamic ? 1 : -1;\n }\n\n // 2. catch-all 路由优先级最低(排在所有动态路由之后)\n if (a.isCatchAll !== b.isCatchAll) {\n return a.isCatchAll ? 1 : -1;\n }\n\n // 3. 路径段数少的优先\n const aSegments = a.urlPath.split('/').filter(Boolean).length;\n const bSegments = b.urlPath.split('/').filter(Boolean).length;\n if (aSegments !== bSegments) {\n return aSegments - bSegments;\n }\n\n // 4. 字母序排列\n return a.urlPath.localeCompare(b.urlPath);\n });\n}\n","import path from 'node:path';\nimport { atomicWriteFile } from '../utils/atomicWrite';\nimport type { RouteManifest } from '../router/routeTypes';\nimport type { RouteSchemaSource } from './collectRouteSchemaSources';\nimport type { RuntimeType } from '../ast/resolveTypeNode';\nimport { collectRouteSchemaSources } from './collectRouteSchemaSources';\nimport {\n generateZodSchemaSourceParts,\n generateHelpersFileSource,\n usesCoerceHelpers,\n HELPERS_FILENAME,\n} from '../ast/generateZodSchema';\n\n/**\n * 源文件路径 → 产物 zod.js 路径\n *\n * 每个 handler 目录下生成一个 `zod.js`(与 handler.js 同级,文件名固定为 zod.js):\n * - `src/api/hello/handler.ts` → `<dist>/api/hello/zod.js`\n *\n * 路由源码目录写死为 src,剥离 `src/` 前缀。\n *\n * @param sourceFile 源文件相对路径(相对 rootDir,如 'src/api/hello/handler.ts')\n * @param dist 输出目录(如 'dist' 或 '.faapi')\n * @param rootDir 项目根目录\n */\nexport function getSchemaOutputPath(sourceFile: string, dist: string, rootDir: string): string {\n let rel = sourceFile.replace(/\\\\/g, '/');\n // 去掉 src/ 前缀(打平产物结构)\n if (rel.startsWith('src/')) {\n rel = rel.slice(4);\n }\n // 取目录部分,basename 固定为 zod.js(每个 handler 目录一个 zod.js)\n const idx = rel.lastIndexOf('/');\n const relDir = idx >= 0 ? rel.slice(0, idx) : '';\n return path.resolve(rootDir, dist, relDir, 'zod.js');\n}\n\n/**\n * 运行时从 route.filePath 计算对应 zod.js 绝对路径\n *\n * 每个 handler 目录下的 zod.js(文件名固定为 zod.js,与 handler.js 同级):\n * - dev 模式:route.filePath 是源码路径(如 'src/api/hello/handler.ts'),\n * zod.js 在 `<rootDir>/<dist>/api/hello/zod.js`\n * - prod 模式:route.filePath 是产物路径(如 'dist/api/hello/handler.js'),\n * zod.js 在 `<rootDir>/dist/api/hello/zod.js`(与 handler.js 同级)\n *\n * 通过同时检查 src 和 dist 前缀,统一处理两种模式:\n * - dev:strip 'src/' 前缀,join dist\n * - prod:strip 'dist/' 前缀,join dist(dist='dist',结果与 rootDir 一致)\n *\n * @param filePath route.filePath(dev 为源码路径,prod 为产物路径)\n * @param dist 输出目录(如 'dist' 或 '.faapi')\n * @param rootDir 项目根目录\n */\nexport function getRuntimeSchemaPath(filePath: string, dist: string, rootDir: string): string {\n let rel = filePath.replace(/\\\\/g, '/');\n // strip src/ 前缀(dev 模式:filePath = 'src/...')\n if (rel.startsWith('src/')) {\n rel = rel.slice(4);\n }\n // strip dist 前缀(prod 模式:filePath = 'dist/...')\n else if (rel.startsWith(`${dist}/`)) {\n rel = rel.slice(dist.length + 1);\n }\n // 取目录部分,basename 固定为 zod.js\n const idx = rel.lastIndexOf('/');\n const relDir = idx >= 0 ? rel.slice(0, idx) : '';\n return path.resolve(rootDir, dist, relDir, 'zod.js');\n}\n\n/**\n * 计算 zod.js 到 faapi-helpers.js 的相对 import 路径\n *\n * faapi-helpers.js 固定在 dist 根部,zod.js 可能在子目录(如 'api/hello/zod.js')。\n * 根据子目录深度计算 `../` 数量:\n * - zod.js 在 `api/hello/`(深度 2)→ `../../faapi-helpers.js`\n * - zod.js 在 `api/`(深度 1)→ `../faapi-helpers.js`\n * - zod.js 在 dist 根(深度 0,理论不会发生)→ `./faapi-helpers.js`\n *\n * @param relDir zod.js 所在目录相对 dist 的路径(如 'api/hello' 或 '')\n * @returns ESM import 路径(如 '../../faapi-helpers.js')\n */\nexport function getHelpersImportPath(relDir: string): string {\n if (!relDir) return `./${HELPERS_FILENAME}`;\n const depth = relDir.split('/').filter(Boolean).length;\n return `${'../'.repeat(depth)}${HELPERS_FILENAME}`;\n}\n\n/**\n * 生成单个 handler 文件的 zod.js 源码\n *\n * 自包含:extractAllTypes 在 AST 阶段已通过 TypeScript checker 内联跨文件类型,\n * 每个 zod.js 无需 import 其他 zod.js。ref 仅用于同文件内的循环引用(通过 z.lazy 处理)。\n *\n * 导出格式:\n * - `<SchemaName>Schema`:zod schema 对象(用于 safeParse 校验)\n *\n * 无类型声明的方法不导出对应 Schema(validateInput 检测到 undefined 跳过校验)。\n *\n * coerce 逻辑:query/params 来源均为 string,schemaName 以 \"Query\" 或 \"Params\" 结尾时\n * 生成 coerce=true 的 schema(number/boolean 字段用 z.preprocess 包裹字符串转换)。\n * body 是 JSON 解析的天然 JS 类型,不需要 coerce。form 与 body 共享 schema 名(POSTBody),\n * 但 `RouteSchemaSource.coerce=true` 显式覆盖(form 值均为 string,需 coerce)。\n * Map/Set 字段在两种场景下都生成 z.preprocess 包裹(JSON.parse 出来的是数组/对象,需还原为 Map/Set 实例)。\n *\n * 命名类型声明文件级去重:同一文件的多个方法引用同一命名类型时,`const XSchema` 只在\n * 文件头声明一次(按首次引用顺序),避免重复 const 声明导致 zod.js import 即 SyntaxError。\n *\n * 公用函数复用:schema 引用 coerceNumber / coerceBoolean / coerceMap / coerceSet 变量时,\n * 这些变量从 dist 根部的 faapi-helpers.js import(跨文件复用,仅一份声明)。\n *\n * @param sources 同一文件的 schema 提取结果(含多个方法,如 GETQuery + POSTBody)\n * @param resolveType ref 解析函数(解析循环引用中的 ref):名称 → 运行时类型,\n * 未声明返回 undefined。惰性实现由 collectRouteSchemaSources 的 resolversByFile 提供。\n * @param helpersImportPath 到 faapi-helpers.js 的相对 import 路径(如 '../../faapi-helpers.js')。\n * 传空字符串表示不注入 coerce helpers 的 import(用于无 coerce schema 的文件或不支持外部 import 的测试场景)。\n * @returns zod.js 源码字符串\n */\nexport function generateSchemaFileSource(\n sources: RouteSchemaSource[],\n resolveType: (name: string) => RuntimeType | undefined,\n helpersImportPath: string,\n): string {\n const lines: string[] = [\"import { z } from 'zod';\"];\n\n // 文件级命名类型声明去重:同一文件的多个方法引用同一命名类型(如 GETQuery 与\n // POSTBody 都引用 Item)时,const 声明只提升到文件头生成一次——否则拼接产物含\n // 重复 const 声明,zod.js import 即 SyntaxError,该文件所有路由 500。\n const namedTypeDeclarations: string[] = [];\n const seenNamedTypes = new Set<string>();\n\n // 先生成所有 schema 入口导出,暂存到 schemaBlocks\n const schemaBlocks: string[] = [];\n for (const source of sources) {\n const { schemaName, typeInfo } = source;\n if (!typeInfo) {\n // 无类型声明,不导出对应 Schema\n continue;\n }\n\n // 推断 coerce:\n // - source.coerce 显式设置时优先使用(form 声明时由 collectRouteSchemaSources 设置为 true)\n // - 否则回退到 schemaName 后缀正则:query/params 需要 coerce(URL 来源均为 string),body 不需要\n const coerce = source.coerce ?? /(?:Query|Params)$/.test(schemaName);\n\n // 生成结构化片段:命名类型声明按名去重后提升到文件头,入口导出留在方法 block 内\n const { namedTypeDeclarations: decls, entryDeclaration } = generateZodSchemaSourceParts(\n typeInfo,\n resolveType,\n schemaName,\n coerce,\n );\n for (const { name, declaration } of decls) {\n if (seenNamedTypes.has(name)) continue;\n seenNamedTypes.add(name);\n namedTypeDeclarations.push(declaration);\n }\n\n schemaBlocks.push([`// ${schemaName}`, entryDeclaration, ''].join('\\n'));\n }\n\n if (namedTypeDeclarations.length > 0) {\n lines.push(...namedTypeDeclarations);\n lines.push('');\n }\n\n // 检测是否有 schema 引用了 coerce 公用函数,若有则注入 import 语句\n // 公用函数包含 coerceNumber / coerceBoolean(query/params 的 string 转换)和\n // coerceMap / coerceSet(Map/Set 的 JSON 还原,body 场景也会引用)\n const allSchemaCode = [...namedTypeDeclarations, ...schemaBlocks].join('\\n');\n if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {\n lines.push(\n `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`,\n );\n }\n lines.push('');\n\n lines.push(...schemaBlocks);\n\n return lines.join('\\n').replace(/\\n+$/, '\\n');\n}\n\n/**\n * 为路由清单中每个 handler 生成 zod.js\n *\n * 输出路径与 handler.js 同级(文件名固定为 zod.js):\n * - dev:`.faapi/api/hello/zod.js`\n * - prod:`dist/api/hello/zod.js`\n *\n * 公用函数复用:若项目中存在 coerce schema(query/params)或 Map/Set 字段,\n * 在 dist 根部生成 `faapi-helpers.js`(导出 coerceNumber / coerceBoolean / coerceMap / coerceSet),\n * 各 zod.js 通过相对路径 import。\n *\n * 内部流程:\n * 1. collectRouteSchemaSources 从路由清单提取 schema 源数据(AST 从源码 .ts)\n * 2. 按 filePath 分组 sources\n * 3. 为每个文件生成 zod.js 源码(暂存,用于检测是否需要 helpers)\n * 4. 检测是否需要 helpers,需要则生成 faapi-helpers.js\n * 5. 并行写入所有 zod.js\n *\n * @param routes 排序后的路由清单\n * @param rootDir 项目根目录\n * @param dist 输出目录(如 '.faapi' 或 'dist')\n */\nexport async function generateSchemaFiles(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<void> {\n if (routes.length === 0) return;\n\n const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir);\n\n // 按 filePath 分组 sources(同一 handler 的多个方法合并到一个 zod.js)\n const sourcesByFile = new Map<string, RouteSchemaSource[]>();\n for (const source of sources) {\n let list = sourcesByFile.get(source.filePath);\n if (!list) {\n list = [];\n sourcesByFile.set(source.filePath, list);\n }\n list.push(source);\n }\n\n // 为每个文件生成 zod.js 源码(先暂存,用于检测是否需要 helpers)\n const fileEntries: { outputPath: string; source: string }[] = [];\n for (const [filePath, fileSources] of sourcesByFile) {\n // filePath 是绝对路径,转为相对 rootDir 的路径用于计算输出路径\n const relFile = path.relative(rootDir, filePath).replace(/\\\\/g, '/');\n const outputPath = getSchemaOutputPath(relFile, dist, rootDir);\n // 计算 zod.js 所在目录相对 dist 的路径(用于 import helpers)\n // 与 getSchemaOutputPath 的目录计算逻辑一致:strip src/ 前缀后取目录部分\n let relForDir = relFile;\n if (relForDir.startsWith('src/')) {\n relForDir = relForDir.slice(4);\n }\n const dirIdx = relForDir.lastIndexOf('/');\n const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : '';\n const helpersImportPath = getHelpersImportPath(zodRelDir);\n\n const resolver = resolversByFile.get(filePath);\n const source = generateSchemaFileSource(\n fileSources,\n (name) => resolver?.resolve(name)?.runtimeType,\n helpersImportPath,\n );\n fileEntries.push({ outputPath, source });\n }\n\n // 检测是否需要生成 faapi-helpers.js\n const allSourceCode = fileEntries.map((e) => e.source).join('\\n');\n if (usesCoerceHelpers(allSourceCode)) {\n const helpersPath = path.resolve(rootDir, dist, HELPERS_FILENAME);\n await writeSchemaFile(helpersPath, generateHelpersFileSource());\n }\n\n // 并行写入所有 zod.js\n await Promise.all(\n fileEntries.map(({ outputPath, source }) => writeSchemaFile(outputPath, source)),\n );\n}\n\n/**\n * 写入 zod.js 文件(确保目录存在)\n */\nasync function writeSchemaFile(outputPath: string, source: string): Promise<void> {\n // 原子写:dev watch 重建与在途请求并发时,请求 import zod.js 不能读到半成品\n await atomicWriteFile(outputPath, source);\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\n\n/**\n * 原子写文件:先写临时文件再 rename 替换\n *\n * rename 在同一文件系统上是原子的(POSIX),并发读者要么看到旧文件要么看到\n * 新文件,不会读到截断的半成品。用于运行时会 import 的产物(zod.js、路由清单、\n * tool/agent 清单)——dev watch 重建与在途请求并发时,非原子写会让请求 import\n * 到半成品文件,报 SyntaxError/ENOENT 且错误信息不指向根因。\n *\n * 与 compileSourceFiles 的 esbuild 原子写(write:false + tmp + rename)同源语义。\n *\n * @param outputPath 目标绝对路径\n * @param content 文件内容\n */\nexport async function atomicWriteFile(outputPath: string, content: string): Promise<void> {\n await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });\n const tmp = `${outputPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n await fs.promises.writeFile(tmp, content, 'utf-8');\n await fs.promises.rename(tmp, outputPath);\n}\n","import ts from 'typescript';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Program 缓存,key 为入口文件路径\n *\n * watch 模式下通过 invalidateProgramCache() 全量清理。\n * 同一文件多次创建 Program 时复用缓存,避免重复解析。\n */\nconst programCache = new Map<string, ts.Program>();\n\n/**\n * tsconfig 解析结果缓存,key 为 tsconfig.json 绝对路径\n *\n * 同一 tsconfig 下多个文件共享解析结果,避免每个文件都读盘 + parse。\n * invalidateProgramCache() 同时清空此缓存。\n */\ninterface TsConfigResult {\n /** parseJsonConfigFileContent 解析得到的 module 选项 */\n module?: ts.ModuleKind;\n /** parseJsonConfigFileContent 解析得到的 moduleResolution 选项 */\n moduleResolution?: ts.ModuleResolutionKind;\n /** tsconfig include 模式匹配的所有 .ts 文件绝对路径(作为 program rootNames) */\n fileNames: string[];\n}\nconst tsConfigCache = new Map<string, TsConfigResult>();\n\n/**\n * 清理所有 Program 缓存 + tsconfig 解析缓存(watch 模式下文件变化时调用)\n *\n * 全量清理而非增量清理,理由:\n * - 简单可靠,无状态一致性问题\n * - 跨文件类型引用需要所有文件的 Program 同步更新\n * - tsconfig 可能变化,需重新读取\n * - dev 模式文件量有限,全量重建在百毫秒级\n */\nexport function invalidateProgramCache(): void {\n programCache.clear();\n tsConfigCache.clear();\n}\n\n/**\n * 从指定文件路径向上查找最近的 tsconfig.json\n *\n * 从 filePath 所在目录开始逐级向上,返回第一个找到的 tsconfig.json 绝对路径。\n * 找不到返回 null(如 os.tmpdir() 测试场景)。\n */\nfunction findTsConfig(filePath: string): string | null {\n let dir = path.dirname(filePath);\n const root = path.parse(dir).root;\n while (true) {\n const candidate = path.join(dir, 'tsconfig.json');\n if (fs.existsSync(candidate)) {\n return candidate;\n }\n if (dir === root) return null;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * 解析 tsconfig.json,提取 module / moduleResolution / fileNames\n *\n * 使用 ts.readConfigFile 读取 + ts.parseJsonConfigFileContent 解析。\n * 保留 tsconfig 原始的 include/exclude/files 配置,让 ts 自动扫描所有相关 .ts 文件\n * 作为 program 的 rootNames——这样跨文件 import 的源文件会被加载到 program 中,\n * 使 resolveTypeReference 的兜底遍历(找同名声明)能成功。\n *\n * 只提取 module / moduleResolution 两个 compilerOptions 覆盖默认值,其余字段\n * (target / strict / jsx / paths 等)用框架默认值,避免业务项目配置干扰 AST 提取。\n *\n * 解析失败(语法错误 / 缺字段)返回空 fileNames + 空选项,回退到默认 NodeNext,\n * 保证 build 不被 tsconfig 损坏阻塞。\n */\nfunction parseTsConfig(tsconfigPath: string): TsConfigResult {\n const cached = tsConfigCache.get(tsconfigPath);\n if (cached) return cached;\n\n const result: TsConfigResult = { fileNames: [] };\n\n try {\n const configFile = ts.readConfigFile(tsconfigPath, (p) => fs.readFileSync(p, 'utf-8'));\n if (configFile.error) {\n tsConfigCache.set(tsconfigPath, result);\n return result;\n }\n\n const config = configFile.config ?? {};\n const basePath = path.dirname(tsconfigPath);\n\n // 保留 tsconfig 原始 include/exclude/files,让 ts 自动扫描 .ts 文件\n // 这些文件作为 program 的 rootNames,让跨文件 import 的源文件被加载进来\n const parsed = ts.parseJsonConfigFileContent(\n config,\n ts.sys,\n basePath,\n /* existingOptions */ undefined,\n tsconfigPath,\n );\n\n result.fileNames = parsed.fileNames;\n if (parsed.options.module !== undefined) {\n result.module = parsed.options.module;\n }\n if (parsed.options.moduleResolution !== undefined) {\n result.moduleResolution = parsed.options.moduleResolution;\n }\n } catch {\n // 任何异常都回退默认,保证 build 不被 tsconfig 阻塞\n }\n\n tsConfigCache.set(tsconfigPath, result);\n return result;\n}\n\n/**\n * 为指定文件创建 TypeScript Program(带缓存)\n *\n * 从 filePath 向上查找最近的 tsconfig.json:\n * - 找到:用 ts.parseJsonConfigFileContent 解析,取 module / moduleResolution\n * 覆盖默认值,同时取 parsed.fileNames 作为 program 的 rootNames(让跨文件\n * import 的源文件被加载进 program)\n * - 找不到(如 os.tmpdir() 测试场景):回退到默认 NodeNext,rootNames 仅包含 filePath\n *\n * 加载全部相关文件保证 `resolveTypeReference` 的兜底遍历(找同名声明)能成功,\n * 修复业务项目用 `moduleResolution: Bundler` + 无扩展名相对导入时跨文件\n * `import type` 解析失败的问题。\n *\n * 同一次生成中需要分析多个文件时用 {@link createPrograms}(按 tsconfig 分组共享\n * 同一个 Program),避免每个文件单独创建 Program 重复解析全项目源码。\n *\n * @param filePath 要分析的 .ts 文件绝对路径\n */\nexport function createProgram(filePath: string): ts.Program {\n const cached = programCache.get(filePath);\n if (cached) {\n return cached;\n }\n\n const program = buildProgram([filePath], findTsConfig(filePath));\n\n programCache.set(filePath, program);\n return program;\n}\n\n/**\n * 为多个文件创建 Program(按 tsconfig 分组共享,带缓存)\n *\n * 同一次批量提取(build 时生成 routes/tools/agents 产物)中,所有向上查找到\n * 同一个 tsconfig.json 的文件共用**同一个 Program**——rootNames 为\n * tsconfig fileNames ∪ 全部入口文件,跨文件类型解析语义与单文件 `createProgram`\n * 完全一致,但 N 个文件只创建 1 个 Program(原来每个文件都全量解析一遍项目源码,\n * 是 build 时间的最大单项开销)。\n *\n * 缓存 key 为 `shared::<tsconfigPath>`(同一 tsconfig 一份 Program):同一批次重复\n * 调用命中缓存;不同批次通过 `program.getSourceFile` 校验缓存已覆盖本次全部入口文件,\n * 未覆盖(入口在 tsconfig include 之外)时重建。若把文件列表纳入 key,dev 按需模式下\n * 每个路由文件会各自持有一份全项目 Program(内存 O(路由数 × 项目大小))。\n * `invalidateProgramCache()` 同时清理共享缓存与单文件缓存。\n *\n * 向上查找不到 tsconfig.json 的文件(如 os.tmpdir() 测试场景)逐个回退到\n * {@link createProgram} 单文件行为,不参与共享。\n *\n * @param filePaths 要分析的 .ts 文件绝对路径列表(内部去重)\n * @returns filePath → Program 映射(包含全部请求文件)\n */\nexport function createPrograms(filePaths: string[]): Map<string, ts.Program> {\n const unique = [...new Set(filePaths)];\n const result = new Map<string, ts.Program>();\n\n // 按向上查找到的 tsconfig.json 分组(同组共享一个 Program)\n const groups = new Map<string, { tsconfigPath: string; files: string[] }>();\n const noTsconfigFiles: string[] = [];\n for (const filePath of unique) {\n const tsconfigPath = findTsConfig(filePath);\n if (!tsconfigPath) {\n noTsconfigFiles.push(filePath);\n continue;\n }\n const group = groups.get(tsconfigPath);\n if (group) {\n group.files.push(filePath);\n } else {\n groups.set(tsconfigPath, { tsconfigPath, files: [filePath] });\n }\n }\n\n for (const { tsconfigPath, files } of groups.values()) {\n // 缓存 key 只含 tsconfigPath:buildProgram 的 rootNames = 入口 ∪ tsconfig 全部\n // fileNames,同一 tsconfig 下不同入口批次的 Program 内容几乎一致——若把文件列表\n // 纳入 key,dev 按需模式下每个路由文件会各自持有一份\"全项目 Program\"\n // (内存 O(路由数 × 项目大小),无淘汰)。改为按 tsconfig 共享一份,通过\n // getSourceFile 校验缓存里的 Program 覆盖了本次全部入口文件(入口在 tsconfig\n // include 之外的场景重建,保证不缺文件)\n const cacheKey = `shared::${tsconfigPath}`;\n let program = programCache.get(cacheKey);\n const coversAll =\n program !== undefined && files.every((f) => program!.getSourceFile(f) !== undefined);\n if (!program || !coversAll) {\n program = buildProgram(files, tsconfigPath);\n programCache.set(cacheKey, program);\n }\n for (const filePath of files) {\n result.set(filePath, program);\n }\n }\n\n // 无 tsconfig 的文件回退单文件行为(保持与 createProgram 一致)\n for (const filePath of noTsconfigFiles) {\n result.set(filePath, createProgram(filePath));\n }\n\n return result;\n}\n\n/**\n * 构建 Program:合并框架默认 options 与 tsconfig 的 module / moduleResolution,\n * rootNames = 入口文件 ∪ tsconfig fileNames\n *\n * 由 {@link createProgram}(单文件)和 {@link createPrograms}(批量共享)共用,\n * 保证两条路径的 Program 构建语义一致。\n */\nfunction buildProgram(entryFiles: string[], tsconfigPath: string | null): ts.Program {\n // 默认选项:与项目历史行为一致(NodeNext),保证向后兼容\n const options: ts.CompilerOptions = {\n strict: true,\n target: ts.ScriptTarget.ES2022,\n module: ts.ModuleKind.NodeNext,\n moduleResolution: ts.ModuleResolutionKind.NodeNext,\n skipLibCheck: true,\n noEmit: true,\n };\n\n // rootNames 默认只包含入口文件;读 tsconfig 后扩展为全部相关文件\n const rootNames = [...entryFiles];\n\n if (tsconfigPath) {\n const tsOptions = parseTsConfig(tsconfigPath);\n if (tsOptions.module !== undefined) {\n options.module = tsOptions.module;\n }\n if (tsOptions.moduleResolution !== undefined) {\n options.moduleResolution = tsOptions.moduleResolution;\n }\n if (tsOptions.fileNames.length > 0) {\n // 确保入口文件在 rootNames 中(即使 tsconfig include 未覆盖到)\n for (const fileName of tsOptions.fileNames) {\n if (!rootNames.includes(fileName)) {\n rootNames.push(fileName);\n }\n }\n }\n }\n\n return ts.createProgram(rootNames, options);\n}\n","import ts from 'typescript';\nimport {\n resolveTypeNode,\n resolveInterfaceDeclaration,\n resolveEnumDeclaration,\n findTopLevelDecl,\n setProgramContext,\n type PropertyType,\n type RuntimeType,\n SchemaExtractionError,\n} from './resolveTypeNode';\n\nexport interface HandlerTypeInfo {\n name: string; // 类型名,如 'GETQuery'\n properties: PropertyType[];\n /** 完整的运行时类型描述(用于嵌套校验) */\n runtimeType: RuntimeType;\n}\n\n/**\n * 从源文件中提取指定名称的类型信息\n *\n * 支持的类型声明:\n * - interface 声明(含继承)\n * - type 别名(type Query = { ... })\n * - enum 声明(跨文件回退路径支持)\n *\n * 自身文件找不到目标声明时,回退到 program 的其他源文件查找同名顶层声明\n * (跳过 node_modules / TypeScript lib,首个匹配生效,与 resolveImportAlias\n * 兜底路径语义一致)。该回退服务于入口类型中第二次出现的跨文件引用:首次引用\n * 已由 checker 内联,二次引用被标记为 ref,代码生成阶段经\n * createLazyTypeResolver → extractTypeInfo 解析,若无回退会静默生成\n * z.unknown(),违背「不降级放行」约定。\n *\n * 遇到不支持的类型时抛 `SchemaExtractionError`,错误信息包含文件路径和类型名。\n *\n * @param program TypeScript Program\n * @param filePath 源文件路径\n * @param typeName 类型名,如 'GETQuery'\n */\nexport function extractTypeInfo(\n program: ts.Program,\n filePath: string,\n typeName: string,\n): HandlerTypeInfo | null {\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) return null;\n\n const checker = program.getTypeChecker();\n\n // 设置 program 上下文,供 resolveImportAlias 兜底遍历跨文件声明使用\n // (TypeChecker 运行时未暴露 getProgram(),通过模块级变量传入)\n setProgramContext(program);\n try {\n // 1. 查找 interface 声明\n let result: HandlerTypeInfo | null = null;\n\n ts.forEachChild(sourceFile, (node) => {\n if (result) return;\n\n if (ts.isInterfaceDeclaration(node) && node.name.text === typeName) {\n const visited = new Set<string>();\n visited.add(typeName); // 标记当前类型,使自引用直接返回 ref\n const runtimeType = withFileContext(filePath, typeName, () =>\n resolveInterfaceDeclaration(node, checker, visited),\n );\n result = {\n name: typeName,\n properties: runtimeType.kind === 'object' ? runtimeType.properties : [],\n runtimeType,\n };\n return;\n }\n\n // 2. 查找 type 别名\n if (ts.isTypeAliasDeclaration(node) && node.name.text === typeName) {\n const visited = new Set<string>();\n visited.add(typeName); // 标记当前类型,使自引用直接返回 ref\n const runtimeType = withFileContext(filePath, typeName, () =>\n resolveTypeNode(node.type, checker, visited),\n );\n result = {\n name: typeName,\n properties: runtimeType.kind === 'object' ? runtimeType.properties : [],\n runtimeType,\n };\n return;\n }\n });\n\n if (result) return result;\n\n // 3. 跨文件回退:自身文件无该声明时,在 program 的其他源文件中查找同名顶层声明\n for (const sf of program.getSourceFiles()) {\n if (sf === sourceFile) continue;\n // 与 resolveImportAlias 兜底路径使用相同的过滤规则\n if (sf.fileName.includes('/node_modules/') || sf.fileName.includes('typescript/lib/')) {\n continue;\n }\n const found = findTopLevelDecl(sf, typeName);\n if (!found) continue;\n\n const visited = new Set<string>();\n visited.add(typeName);\n const runtimeType = withFileContext(filePath, typeName, () => {\n if (found.kind === 'interface') {\n return resolveInterfaceDeclaration(found.node, checker, visited);\n }\n if (found.kind === 'typeAlias') {\n return resolveTypeNode(found.node.type, checker, visited);\n }\n return resolveEnumDeclaration(found.node);\n });\n return {\n name: typeName,\n properties: runtimeType.kind === 'object' ? runtimeType.properties : [],\n runtimeType,\n };\n }\n\n return null;\n } finally {\n setProgramContext(null);\n }\n}\n\n/**\n * 从源文件中提取所有 interface 和 type 别名的类型信息\n *\n * 用于:\n * - 生成 schema 模块时遍历所有命名类型\n * - 作为 typeResolver 提供给 generateZodSchema,解析循环引用中的 ref\n *\n * @returns Map<类型名, HandlerTypeInfo>\n */\nexport function extractAllTypes(\n program: ts.Program,\n filePath: string,\n): Map<string, HandlerTypeInfo> {\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) return new Map();\n\n const checker = program.getTypeChecker();\n\n // 设置 program 上下文,供 resolveImportAlias 兜底遍历跨文件声明使用\n setProgramContext(program);\n try {\n const result = new Map<string, HandlerTypeInfo>();\n\n ts.forEachChild(sourceFile, (node) => {\n if (ts.isInterfaceDeclaration(node)) {\n const visited = new Set<string>();\n visited.add(node.name.text); // 标记当前类型,使自引用直接返回 ref\n const runtimeType = withFileContext(filePath, node.name.text, () =>\n resolveInterfaceDeclaration(node, checker, visited),\n );\n result.set(node.name.text, {\n name: node.name.text,\n properties: runtimeType.kind === 'object' ? runtimeType.properties : [],\n runtimeType,\n });\n return;\n }\n\n if (ts.isTypeAliasDeclaration(node)) {\n const visited = new Set<string>();\n visited.add(node.name.text); // 标记当前类型,使自引用直接返回 ref\n const runtimeType = withFileContext(filePath, node.name.text, () =>\n resolveTypeNode(node.type, checker, visited),\n );\n result.set(node.name.text, {\n name: node.name.text,\n properties: runtimeType.kind === 'object' ? runtimeType.properties : [],\n runtimeType,\n });\n return;\n }\n });\n\n return result;\n } finally {\n setProgramContext(null);\n }\n}\n\n/**\n * 惰性类型解析器\n *\n * 按名称解析类型并缓存。与 extractAllTypes 的差异:extractAllTypes 提前解析\n * 文件中全部顶层类型——与路由无关的类型也会被解析,其中任何类型含不支持语法\n * 都会拖垮整个 build/reload;惰性解析器只在类型真正被需要时(入口类型或\n * ref 引用)才解析,无关类型零开销。\n */\nexport interface LazyTypeResolver {\n /**\n * 按名称解析类型\n *\n * @returns 类型信息;文件中无该名称的声明时返回 null\n * @throws SchemaExtractionError 类型声明含不支持语法时(与 extractTypeInfo 一致,不降级)\n */\n resolve(name: string): HandlerTypeInfo | null;\n}\n\n/**\n * 创建文件的惰性类型解析器\n *\n * 缓存保证同一类型只解析一次(幂等返回同一实例)。\n *\n * @param program TypeScript Program(解析期间会临时设置 program 上下文,供\n * resolveImportAlias 兜底遍历跨文件声明使用)\n * @param filePath 源文件绝对路径\n */\nexport function createLazyTypeResolver(program: ts.Program, filePath: string): LazyTypeResolver {\n const cache = new Map<string, HandlerTypeInfo | null>();\n return {\n resolve(name: string): HandlerTypeInfo | null {\n if (cache.has(name)) return cache.get(name)!;\n const info = extractTypeInfo(program, filePath, name);\n cache.set(name, info);\n return info;\n },\n };\n}\n\n/**\n * 包装类型解析,捕获 SchemaExtractionError 并补充文件路径和类型名上下文\n *\n * 只包装一次(最外层),避免嵌套调用时重复包装。\n */\nfunction withFileContext<T>(filePath: string, typeName: string, fn: () => T): T {\n try {\n return fn();\n } catch (err) {\n if (err instanceof SchemaExtractionError) {\n // 补充文件路径和类型名,只包装一次;保留行号位置(错误在类型文件中的精确定位)\n const fileName = filePath.split('/').pop() ?? filePath;\n const enriched = new SchemaExtractionError(\n err.typeText,\n `${err.reason}(文件: ${fileName}, 类型: ${typeName})`,\n { cause: err },\n err.location,\n );\n throw enriched;\n }\n throw err;\n }\n}\n","import ts from 'typescript';\n\n/**\n * 模块级 Program 上下文,供 `resolveImportAlias` 兜底遍历跨文件声明使用\n *\n * TypeScript 的 `TypeChecker` 类型声明了 `getProgram(): Program` 方法,但\n * 运行时实例未暴露该方法(实测 `typeof checker.getProgram === 'undefined'`)。\n * 因此采用模块级变量在调用入口设置当前 program,resolveImportAlias 兜底时读取。\n *\n * 调用入口(`extractTypeInfo` / `extractAllTypes`)在分析前调\n * `setProgramContext(program)`,分析结束后调 `setProgramContext(null)` 清空。\n *\n * 串行场景安全:faapi 的 AST 提取在单线程顺序执行,无并发。\n */\nlet currentProgram: ts.Program | null = null;\n\n/**\n * 设置当前 Program 上下文(供 resolveImportAlias 兜底遍历跨文件声明使用)\n *\n * @param program 当前 program;分析结束后传 null 清空\n */\nexport function setProgramContext(program: ts.Program | null): void {\n currentProgram = program;\n}\n\n/**\n * Schema 提取错误\n *\n * 遇到无法解析或不支持运行时校验的类型时抛出,\n * 避免静默降级为 any 导致用户不知情。\n */\n/** 错误在源文件中的位置(file:line:column,便于在几百行类型文件中定位) */\nexport interface SchemaErrorLocation {\n file: string;\n line: number;\n column: number;\n}\n\nexport class SchemaExtractionError extends Error {\n constructor(\n public readonly typeText: string,\n public readonly reason: string,\n options?: ErrorOptions,\n public readonly location?: SchemaErrorLocation,\n ) {\n super(\n `无法解析类型 \"${typeText}\": ${reason}` +\n (location ? ` (${location.file}:${location.line}:${location.column})` : ''),\n options,\n );\n this.name = 'SchemaExtractionError';\n }\n\n /**\n * 从 AST 节点构造错误(自动携带 file:line:column)\n *\n * 所有抛错点应优先使用此工厂——错误无行号时,几百行的类型文件只能靠\n * 类型名肉眼定位;解析 lib.d.ts 类型别名时还会出现错误文本与文件上下文错位\n */\n static at(node: ts.Node, typeText: string, reason: string): SchemaExtractionError {\n const sourceFile = node.getSourceFile();\n if (!sourceFile) {\n return new SchemaExtractionError(typeText, reason);\n }\n const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n return new SchemaExtractionError(typeText, reason, undefined, {\n file: sourceFile.fileName,\n line: line + 1,\n column: character + 1,\n });\n }\n}\n\n/**\n * 运行时类型描述\n *\n * 用于校验器在运行时判断值的结构是否符合声明类型。\n * 相比单纯的字符串,能描述数组元素类型、嵌套对象、联合类型等。\n */\nexport type RuntimeType =\n | { kind: 'string' }\n | { kind: 'number' }\n | { kind: 'boolean' }\n | { kind: 'bigint' }\n | { kind: 'null' }\n | { kind: 'undefined' }\n | { kind: 'any' } // 不校验\n | { kind: 'unknown' } // 不校验\n | { kind: 'literal'; value: string | number | boolean } // 字面量\n | { kind: 'array'; element: RuntimeType }\n | { kind: 'tuple'; elements: TupleElement[] }\n | { kind: 'object'; properties: PropertyType[]; catchall?: RuntimeType }\n | { kind: 'union'; members: RuntimeType[] }\n | { kind: 'date' }\n | { kind: 'record'; key: RuntimeType; value: RuntimeType }\n | { kind: 'map'; key: RuntimeType; value: RuntimeType } // Map<K,V>,JSON 序列化为 entries 数组\n | { kind: 'set'; element: RuntimeType } // Set<T>,JSON 序列化为数组\n | { kind: 'ref'; name: string }; // 命名类型引用(支持循环引用)\n\n/**\n * 元组元素类型\n *\n * - optional: 该位置元素可省略(如 `[string, number?]`)\n * - rest: 该位置是剩余元素(如 `[string, ...number[]]`),必须是数组类型,展开后逐个校验\n */\nexport interface TupleElement {\n type: RuntimeType;\n optional: boolean;\n rest: boolean;\n}\n\nexport interface PropertyType {\n name: string;\n type: RuntimeType;\n optional: boolean;\n /**\n * 字段级 JSDoc 约束标签(@max/@min/@maxLength 等)\n *\n * 来自字段 JSDoc 注释,由 generateZodSchema 转为 zod 链式调用。\n * 约束与字段类型不匹配时在提取阶段抛 SchemaExtractionError。\n */\n constraints?: TypeConstraint[];\n}\n\n/**\n * JSDoc 约束标签的运行时描述\n *\n * 由字段 JSDoc 注释提取,对应 zod schema 的链式约束方法。\n * 仅在 PropertyType.constraints 中出现,不挂在嵌套类型(array 元素、tuple 元素等)上。\n */\nexport type TypeConstraint =\n // 数值约束(仅 number 字段)\n | { kind: 'max'; value: number }\n | { kind: 'min'; value: number }\n | { kind: 'int' }\n | { kind: 'positive' }\n | { kind: 'negative' }\n | { kind: 'nonnegative' }\n | { kind: 'nonpositive' }\n // 长度约束(string 或 array 字段)\n | { kind: 'maxLength'; value: number }\n | { kind: 'minLength'; value: number }\n | { kind: 'length'; value: number }\n // 字符串格式约束(仅 string 字段)\n | { kind: 'regex'; pattern: string; flags?: string }\n | { kind: 'email' }\n | { kind: 'url' }\n | { kind: 'uuid' };\n\n/**\n * 将 TypeScript 类型节点解析为运行时类型描述\n *\n * 支持的类型:\n * - 基础类型:string / number / boolean / null / undefined / any / unknown / void\n * - bigint:不支持(HTTP/JSON 不能传输),AST 提取阶段抛 SchemaExtractionError\n * - 字面量类型:'foo' / 42 / true\n * - 数组类型:T[] / Array<T> / ReadonlyArray<T> / readonly T[]\n * - 元组类型:[string, number] / [string, number?] / [string, ...number[]] / readonly [T, U](按位置校验)\n * - 对象类型:{ name: string; age?: number }(含 readonly 字段修饰符,忽略 readonly)\n * - 联合类型:string | null\n * - 交叉类型:A & B(按对象合并处理)\n * - 引用类型:Date / 其他 interface(递归解析)\n * - 工具类型:Record<K, V> / Partial<T> / Readonly<T>(best effort)\n * - Pick<T, K> / Omit<T, K>:K 支持字面量联合、类型别名、keyof T\n *\n * readonly 是编译期约束,运行时不产生校验语义,所有 readonly 修饰符统一忽略。\n *\n * @param typeNode TypeScript 类型节点\n * @param checker 类型 checker(用于解析引用类型)\n * @param visited 防止递归循环\n * @param bindings 泛型形参绑定(形参名 → 已解析的实参类型),解析泛型声明的\n * 类型体时传入;形参名在 resolveTypeReference 入口优先命中,\n * 遮蔽同名的真实类型声明\n */\nexport function resolveTypeNode(\n typeNode: ts.TypeNode,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n bindings: Map<string, RuntimeType> = new Map(),\n): RuntimeType {\n const kind = typeNode.kind;\n\n // 基础类型\n switch (kind) {\n case ts.SyntaxKind.StringKeyword:\n return { kind: 'string' };\n case ts.SyntaxKind.NumberKeyword:\n return { kind: 'number' };\n case ts.SyntaxKind.BooleanKeyword:\n return { kind: 'boolean' };\n case ts.SyntaxKind.BigIntKeyword:\n // HTTP/JSON 不能传输 BigInt,在 AST 提取阶段就报错,避免运行时校验必然失败\n throw new SchemaExtractionError(\n typeNode.getText(),\n 'bigint 无法通过 HTTP/JSON 传输,请改用 string 或 number',\n );\n case ts.SyntaxKind.SymbolKeyword:\n // HTTP/JSON 不能传输 Symbol\n throw new SchemaExtractionError(typeNode.getText(), 'symbol 无法通过 HTTP/JSON 传输');\n case ts.SyntaxKind.NullKeyword:\n return { kind: 'null' };\n case ts.SyntaxKind.UndefinedKeyword:\n return { kind: 'undefined' };\n case ts.SyntaxKind.UnknownKeyword:\n // unknown 是唯一允许的\"不校验\"显式声明\n return { kind: 'any' };\n case ts.SyntaxKind.AnyKeyword:\n throw new SchemaExtractionError(typeNode.getText(), 'any 不支持,请使用 unknown 表示不校验');\n case ts.SyntaxKind.VoidKeyword:\n throw new SchemaExtractionError(typeNode.getText(), 'void 不支持运行时校验');\n case ts.SyntaxKind.NeverKeyword:\n throw new SchemaExtractionError(typeNode.getText(), 'never 不支持运行时校验');\n case ts.SyntaxKind.ObjectKeyword:\n throw new SchemaExtractionError(\n typeNode.getText(),\n 'object 不支持,请使用具体对象类型或 unknown',\n );\n }\n\n // 字面量类型\n if (ts.isLiteralTypeNode(typeNode)) {\n const literal = typeNode.literal;\n if (ts.isStringLiteral(literal)) {\n return { kind: 'literal', value: literal.text };\n }\n if (ts.isNumericLiteral(literal)) {\n return { kind: 'literal', value: Number(literal.text) };\n }\n if (literal.kind === ts.SyntaxKind.TrueKeyword) {\n return { kind: 'literal', value: true };\n }\n if (literal.kind === ts.SyntaxKind.FalseKeyword) {\n return { kind: 'literal', value: false };\n }\n if (literal.kind === ts.SyntaxKind.NullKeyword) {\n return { kind: 'null' };\n }\n throw new SchemaExtractionError(typeNode.getText(), '不支持的字面量类型');\n }\n\n // 数组类型:T[]\n if (ts.isArrayTypeNode(typeNode)) {\n return {\n kind: 'array',\n element: resolveTypeNode(typeNode.elementType, checker, visited, bindings),\n };\n }\n\n // 元组类型:[string, number] / [string, number?] / [string, ...number[]]\n if (ts.isTupleTypeNode(typeNode)) {\n const elements: TupleElement[] = typeNode.elements.map((e) => {\n // 剩余元素:...T\n if (ts.isRestTypeNode(e)) {\n const inner = resolveTypeNode(e.type, checker, visited, bindings);\n // ...T[] → 元素类型是 T 的数组元素\n if (inner.kind === 'array') {\n return { type: inner.element, optional: false, rest: true };\n }\n // ...T 但 T 不是数组(罕见,TS 通常要求 rest 是数组)\n return { type: inner, optional: false, rest: true };\n }\n // 命名元组成员:[name: string, age?: number]\n if (ts.isNamedTupleMember(e)) {\n return {\n type: resolveTypeNode(e.type, checker, visited, bindings),\n optional: !!e.questionToken,\n rest: false,\n };\n }\n // 可选元素:number?(无名称,有 ?)\n if (ts.isOptionalTypeNode(e)) {\n return {\n type: resolveTypeNode(e.type, checker, visited, bindings),\n optional: true,\n rest: false,\n };\n }\n // 普通元素\n return {\n type: resolveTypeNode(e, checker, visited, bindings),\n optional: false,\n rest: false,\n };\n });\n return { kind: 'tuple', elements };\n }\n\n // 联合类型:A | B\n if (ts.isUnionTypeNode(typeNode)) {\n const members = typeNode.types.map((t) => resolveTypeNode(t, checker, visited, bindings));\n return { kind: 'union', members };\n }\n\n // 交叉类型:A & B → 合并对象属性(全部成员均为 object 时)\n if (ts.isIntersectionTypeNode(typeNode)) {\n const propMap = new Map<string, PropertyType>();\n for (const t of typeNode.types) {\n const resolved = resolveTypeNode(t, checker, visited, bindings);\n if (resolved.kind !== 'object') {\n // branded 类型(string & {...})等混合交叉在运行时无对应校验物——\n // 静默丢弃非 object 成员会放宽校验,按约定显式抛错(见 AGENTS.md §6.3)\n throw new SchemaExtractionError(\n typeNode.getText(),\n `交叉类型包含非 object 成员(${resolved.kind}),运行时无法校验——branded 类型建议改用具体类型或 unknown`,\n );\n }\n for (const prop of resolved.properties) {\n const existing = propMap.get(prop.name);\n if (!existing) {\n propMap.set(prop.name, prop);\n continue;\n }\n // 同名字段:类型或可选性不一致即 TS 语义下的 never——运行时无法校验,\n // 显式抛错;一致则去重(优先保留带约束的声明,更严格)\n if (\n JSON.stringify(existing.type) !== JSON.stringify(prop.type) ||\n existing.optional !== prop.optional\n ) {\n throw SchemaExtractionError.at(\n typeNode,\n typeNode.getText(),\n `交叉类型成员的同名字段 \"${prop.name}\" 类型冲突(TS 中为 never),运行时无法校验`,\n );\n }\n if (!existing.constraints?.length && prop.constraints?.length) {\n propMap.set(prop.name, prop);\n }\n }\n }\n return { kind: 'object', properties: [...propMap.values()] };\n }\n\n // 内联对象类型:{ name: string; age?: number }\n if (ts.isTypeLiteralNode(typeNode)) {\n return resolveTypeLiteral(typeNode, checker, visited, bindings);\n }\n\n // keyof T — 用 checker 解析为字面量联合\n if (ts.isTypeOperatorNode(typeNode) && typeNode.operator === ts.SyntaxKind.KeyOfKeyword) {\n return resolveKeyOf(typeNode, checker);\n }\n\n // readonly T(如 readonly string[] / readonly [T, U])— 编译期约束,运行时不校验\n // 递归解析内部类型即可,等同于去掉 readonly 修饰符\n if (ts.isTypeOperatorNode(typeNode) && typeNode.operator === ts.SyntaxKind.ReadonlyKeyword) {\n return resolveTypeNode(typeNode.type, checker, visited, bindings);\n }\n\n // 引用类型:Date / 自定义 interface / Array<T> / Record<K,V> / Partial<T> 等\n if (ts.isTypeReferenceNode(typeNode)) {\n return resolveTypeReference(typeNode, checker, visited, bindings);\n }\n\n // interface extends 的 heritage 子句(ExpressionWithTypeArguments)\n // 构造 TypeRefLike 视图(expression → typeName),复用 resolveTypeReference\n // 解析(覆盖 Date / Array<T> / 自定义 interface 等全部分支)\n if (ts.isExpressionWithTypeArguments(typeNode)) {\n return resolveTypeReference(\n {\n getText: () => typeNode.getText(),\n typeName: typeNode.expression as ts.EntityName,\n typeArguments: typeNode.typeArguments,\n },\n checker,\n visited,\n bindings,\n );\n }\n\n // 其他无法识别的语法节点\n throw SchemaExtractionError.at(typeNode, typeNode.getText(), '不支持的类型语法');\n}\n\n/**\n * 解析内联对象类型字面量\n */\nfunction resolveTypeLiteral(\n typeNode: ts.TypeLiteralNode,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n bindings: Map<string, RuntimeType> = new Map(),\n): RuntimeType {\n const properties: PropertyType[] = [];\n // 索引签名([k: string]: T)与属性共存合法:属性保留,索引签名转为 catchall\n let catchall: RuntimeType | undefined;\n\n for (const member of typeNode.members) {\n // 方法签名/存取器:运行时 JSON 数据无方法语义,静默丢弃会弱化校验——显式抛错\n if (\n ts.isMethodSignature(member) ||\n ts.isGetAccessorDeclaration(member) ||\n ts.isSetAccessorDeclaration(member)\n ) {\n throw SchemaExtractionError.at(\n member,\n member.getText(),\n '对象类型含方法签名或存取器,运行时 JSON 数据无法校验方法——请改用具体属性类型',\n );\n }\n // 属性签名:name: string\n if (ts.isPropertySignature(member) && member.name) {\n const name = member.name.getText();\n const optional = !!member.questionToken;\n const type = member.type\n ? resolveTypeNode(member.type, checker, visited, bindings)\n : { kind: 'any' as const };\n const constraints = extractConstraintsFromJsDoc(member, name);\n validateConstraints(constraints, type, name);\n properties.push(\n constraints.length > 0 ? { name, type, optional, constraints } : { name, type, optional },\n );\n }\n // 索引签名:[key: string]: T → catchall(与属性共存时不丢弃属性)\n if (ts.isIndexSignatureDeclaration(member)) {\n catchall = member.type\n ? resolveTypeNode(member.type, checker, visited, bindings)\n : { kind: 'any' as const };\n }\n }\n\n return catchall !== undefined\n ? { kind: 'object', properties, catchall }\n : { kind: 'object', properties };\n}\n\n/**\n * 从 RuntimeType 提取字面量字符串集合\n * 用于 Pick<T, K> / Omit<T, K> 的 K 参数解析\n *\n * @returns 字面量值数组;无法解析时返回 null\n */\nfunction extractLiteralKeys(type: RuntimeType): string[] | null {\n if (type.kind === 'literal' && typeof type.value === 'string') {\n return [type.value];\n }\n if (type.kind === 'union') {\n const keys: string[] = [];\n for (const member of type.members) {\n if (member.kind === 'literal' && typeof member.value === 'string') {\n keys.push(member.value);\n } else {\n return null;\n }\n }\n return keys;\n }\n return null;\n}\n\n/**\n * 用 checker 解析 K 的最终类型,提取字面量字符串集合\n *\n * 用于 Pick<T, K> / Omit<T, K> 的 K 参数解析,覆盖 AST 无法直接拿到的场景:\n * - K 为类型别名(type Keys = 'id' | 'name')\n * - K 为 keyof T\n *\n * @returns 字面量值数组;无法解析时返回 null\n */\nfunction extractKeysFromChecker(typeNode: ts.TypeNode, checker?: ts.TypeChecker): string[] | null {\n if (!checker) return null;\n const type = checker.getTypeFromTypeNode(typeNode);\n\n if (type.isUnion()) {\n const keys: string[] = [];\n for (const member of type.types) {\n if (member.isStringLiteral()) {\n keys.push(member.value);\n } else if (member.isNumberLiteral()) {\n keys.push(String(member.value));\n } else {\n return null;\n }\n }\n return keys;\n }\n\n if (type.isStringLiteral()) {\n return [type.value];\n }\n if (type.isNumberLiteral()) {\n return [String(type.value)];\n }\n\n return null;\n}\n\n/**\n * 解析 keyof T — 用 checker 计算最终类型,返回字面量联合\n *\n * 例如 keyof { id: number; name: string } → 'id' | 'name'\n */\nfunction resolveKeyOf(typeNode: ts.TypeOperatorNode, checker?: ts.TypeChecker): RuntimeType {\n if (!checker) {\n throw new SchemaExtractionError(typeNode.getText(), 'keyof T 需要 checker 才能解析');\n }\n const type = checker.getTypeFromTypeNode(typeNode);\n\n if (type.isUnion()) {\n const members: RuntimeType[] = [];\n for (const member of type.types) {\n if (member.isStringLiteral()) {\n members.push({ kind: 'literal', value: member.value });\n } else if (member.isNumberLiteral()) {\n members.push({ kind: 'literal', value: member.value });\n } else {\n throw new SchemaExtractionError(typeNode.getText(), 'keyof T 的结果包含非字面量类型');\n }\n }\n return { kind: 'union', members };\n }\n\n if (type.isStringLiteral()) {\n return { kind: 'literal', value: type.value };\n }\n if (type.isNumberLiteral()) {\n return { kind: 'literal', value: type.value };\n }\n\n throw new SchemaExtractionError(typeNode.getText(), 'keyof T 的结果无法解析为字面量联合');\n}\n\n/**\n * resolveTypeReference 所需的最小节点结构\n *\n * TypeReferenceNode 与 interface extends 的 heritage 节点(ExpressionWithTypeArguments)\n * 均满足该结构——后者用 expression 充当 typeName,typeArguments 同名。\n */\ninterface TypeRefLike {\n getText(): string;\n typeName: ts.EntityName | ts.LeftHandSideExpression;\n typeArguments?: readonly ts.TypeNode[];\n}\n\n/**\n * 解析类型引用(Date / 自定义 interface / Array<T> / Record<K,V> 等)\n *\n * 泛型形参优先:typeName 命中 bindings 时直接返回绑定的实参类型\n * (泛型声明体内部引用形参的场景,形参遮蔽同名真实类型)。\n */\nfunction resolveTypeReference(\n typeNode: TypeRefLike,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n bindings: Map<string, RuntimeType> = new Map(),\n): RuntimeType {\n const typeName = typeNode.typeName.getText();\n\n // 泛型形参命中:直接返回已绑定的实参类型\n const bound = bindings.get(typeName);\n if (bound) {\n return bound;\n }\n\n // Date 类型\n if (typeName === 'Date') {\n return { kind: 'date' };\n }\n\n // Array<T> / ReadonlyArray<T> — 等同处理,readonly 是编译期约束\n if (\n (typeName === 'Array' || typeName === 'ReadonlyArray') &&\n typeNode.typeArguments?.length === 1\n ) {\n const [arg] = typeNode.typeArguments;\n return {\n kind: 'array',\n element: resolveTypeNode(arg!, checker, visited, bindings),\n };\n }\n\n // Record<K, V>\n if (typeName === 'Record' && typeNode.typeArguments?.length === 2) {\n const [keyArg, valueArg] = typeNode.typeArguments;\n return {\n kind: 'record',\n key: resolveTypeNode(keyArg!, checker, visited, bindings),\n value: resolveTypeNode(valueArg!, checker, visited, bindings),\n };\n }\n\n // Partial<T> / Required<T> / Readonly<T> — 解析内部类型\n if (\n (typeName === 'Partial' || typeName === 'Required' || typeName === 'Readonly') &&\n typeNode.typeArguments?.length === 1\n ) {\n const inner = resolveTypeNode(typeNode.typeArguments[0]!, checker, visited, bindings);\n if (inner.kind === 'object' && typeName === 'Partial') {\n // Partial 所有字段变可选\n return {\n kind: 'object',\n properties: inner.properties.map((p) => ({ ...p, optional: true })),\n };\n }\n if (inner.kind === 'object' && typeName === 'Required') {\n // Required 恢复所有字段为必填——静默保留 optional 会让校验弱于 TS 类型\n return {\n kind: 'object',\n properties: inner.properties.map((p) => ({ ...p, optional: false })),\n };\n }\n // Readonly:编译期约束,运行时无校验语义,原样返回(等同去掉 readonly 修饰符)\n return inner;\n }\n\n // Pick<T, K> / Omit<T, K> — 解析 T 的字段,按 K 筛选/排除\n if ((typeName === 'Pick' || typeName === 'Omit') && typeNode.typeArguments?.length === 2) {\n const innerType = resolveTypeNode(typeNode.typeArguments[0]!, checker, visited, bindings);\n if (innerType.kind !== 'object') {\n throw new SchemaExtractionError(\n typeNode.getText(),\n `${typeName} 的 T 必须是对象类型,实际为 ${innerType.kind}`,\n );\n }\n\n // K 解析顺序:AST 字面量联合 → checker 解析(覆盖类型别名 / keyof T)\n const keyTypeNode = typeNode.typeArguments[1]!;\n let keys = extractLiteralKeys(resolveTypeNode(keyTypeNode, checker, visited, bindings));\n if (keys === null) {\n keys = extractKeysFromChecker(keyTypeNode, checker);\n }\n if (keys === null) {\n throw new SchemaExtractionError(typeNode.getText(), `${typeName} 的 K 无法解析为字面量集合`);\n }\n\n const keySet = new Set(keys);\n const properties =\n typeName === 'Pick'\n ? innerType.properties.filter((p) => keySet.has(p.name))\n : innerType.properties.filter((p) => !keySet.has(p.name));\n\n return { kind: 'object', properties };\n }\n\n // Map<K, V> — JSON 序列化为 entries 数组,运行时用 z.preprocess(coerceMap, z.map(...)) 还原\n if (typeName === 'Map') {\n if (!typeNode.typeArguments || typeNode.typeArguments.length !== 2) {\n throw new SchemaExtractionError(\n typeNode.getText(),\n 'Map 必须带 2 个类型参数,如 Map<K, V>,裸 Map 不支持',\n );\n }\n const [mapKey, mapValue] = typeNode.typeArguments;\n return {\n kind: 'map',\n key: resolveTypeNode(mapKey!, checker, visited, bindings),\n value: resolveTypeNode(mapValue!, checker, visited, bindings),\n };\n }\n\n // Set<T> — JSON 序列化为数组,运行时用 z.preprocess(coerceSet, z.set(...)) 还原\n if (typeName === 'Set') {\n if (!typeNode.typeArguments || typeNode.typeArguments.length !== 1) {\n throw new SchemaExtractionError(\n typeNode.getText(),\n 'Set 必须带 1 个类型参数,如 Set<T>,裸 Set 不支持',\n );\n }\n const [setArg] = typeNode.typeArguments;\n return {\n kind: 'set',\n element: resolveTypeNode(setArg!, checker, visited, bindings),\n };\n }\n\n // WeakMap / WeakSet — 运行时无法枚举校验,仍然抛错(Map/Set 不复用其语义)\n if (typeName === 'WeakMap' || typeName === 'WeakSet') {\n throw new SchemaExtractionError(\n typeNode.getText(),\n `${typeName} 运行时无法枚举校验,请改用 Map / Set 或对象`,\n );\n }\n\n // Promise<T> — 运行时无法校验异步值\n if (typeName === 'Promise') {\n throw new SchemaExtractionError(\n typeNode.getText(),\n 'Promise 运行时无法校验,请勿在 query/body 类型中使用',\n );\n }\n\n // Function — HTTP/JSON 不能传输函数\n if (typeName === 'Function') {\n throw new SchemaExtractionError(typeNode.getText(), 'Function 无法通过 HTTP/JSON 传输');\n }\n\n // 防止递归循环:遇到已访问的类型返回 ref,支持循环引用\n if (visited.has(typeName)) {\n return { kind: 'ref', name: typeName };\n }\n visited.add(typeName);\n\n // 使用 checker 解析引用类型(interface / type 别名)\n if (checker) {\n const symbol =\n ts.isIdentifier(typeNode.typeName) || ts.isQualifiedName(typeNode.typeName)\n ? checker.getSymbolAtLocation(typeNode.typeName)\n : undefined;\n\n if (symbol) {\n const declaration = symbol.declarations?.[0];\n if (declaration) {\n // interface 声明\n if (ts.isInterfaceDeclaration(declaration)) {\n return resolveInterfaceDeclaration(\n declaration,\n checker,\n visited,\n bindings,\n typeNode.typeArguments,\n );\n }\n // type 别名声明\n if (ts.isTypeAliasDeclaration(declaration)) {\n const declBindings = bindTypeParameters(\n declaration.typeParameters,\n typeNode.typeArguments,\n bindings,\n checker,\n visited,\n typeNode,\n );\n return resolveTypeNode(declaration.type, checker, visited, declBindings);\n }\n // enum 声明 → 字面量联合\n if (ts.isEnumDeclaration(declaration)) {\n return resolveEnumDeclaration(declaration);\n }\n // import 别名(跨文件 import type):跟到真实声明\n // 业务项目用 moduleResolution: Bundler + 无扩展名导入时,\n // checker 拿到的 symbol 是 ImportSpecifier(Alias),不是真实声明\n if (ts.isImportSpecifier(declaration) || ts.isImportClause(declaration)) {\n const resolved = resolveImportAlias(\n typeNode,\n symbol,\n checker,\n visited,\n bindings,\n typeNode.typeArguments,\n );\n if (resolved) return resolved;\n }\n }\n }\n }\n\n throw new SchemaExtractionError(typeNode.getText(), `无法解析的引用类型 \"${typeName}\"`);\n}\n\n/**\n * 绑定泛型形参与实参,返回声明体解析用的 bindings\n *\n * - 实参表达式在调用方作用域解析(用 outerBindings,可引用外层形参)\n * - 默认类型在声明方作用域解析(用正在构造的 bindings,可引用前面形参,如 `<T, U = T[]>`)\n * - 实参与默认类型均缺失时抛 SchemaExtractionError(TS 本身也编译不过,防御性报错)\n * - 声明无 typeParameters 时原样返回 outerBindings\n */\nfunction bindTypeParameters(\n typeParameters: readonly ts.TypeParameterDeclaration[] | undefined,\n typeArguments: readonly ts.TypeNode[] | undefined,\n outerBindings: Map<string, RuntimeType>,\n checker: ts.TypeChecker | undefined,\n visited: Set<string>,\n errorNode: { getText(): string },\n): Map<string, RuntimeType> {\n if (!typeParameters || typeParameters.length === 0) return outerBindings;\n\n const bindings = new Map(outerBindings);\n for (let i = 0; i < typeParameters.length; i++) {\n const param = typeParameters[i];\n if (!param) continue;\n const arg = typeArguments?.[i];\n if (arg) {\n bindings.set(param.name.text, resolveTypeNode(arg, checker, visited, outerBindings));\n } else if (param.default) {\n bindings.set(param.name.text, resolveTypeNode(param.default, checker, visited, bindings));\n } else {\n throw new SchemaExtractionError(\n errorNode.getText(),\n `泛型参数 \"${param.name.text}\" 缺少类型实参(且无默认类型)`,\n );\n }\n }\n return bindings;\n}\n\n/**\n * 解析 import 别名符号,跟到真实的 interface / type alias / enum 声明\n *\n * 业务项目用 `moduleResolution: Bundler` + 无扩展名相对导入(如\n * `import type { StyleGuide } from '../../db/schema'`)时,checker 拿到的 symbol\n * 是 `ImportSpecifier`(Alias symbol),不是真实声明节点。\n *\n * 解析顺序:\n * 1. `checker.getAliasedSymbol(symbol)` — 优先路径,直接跟到真实 symbol\n * 2. 兜底:遍历 program 的所有 sourceFiles 找名为 `typeName` 的\n * `InterfaceDeclaration` / `TypeAliasDeclaration` / `EnumDeclaration`\n *\n * 兜底路径用于 `getAliasedSymbol` 在 `noEmit` 模式下未完全绑定 alias 的场景\n * (实测:即使业务项目 tsconfig 用 Bundler + program 加载了全部相关文件,\n * `getAliasedSymbol` 也可能返回 unknown / 无 declarations)。\n *\n * @returns RuntimeType 解析结果;无法解析返回 null(由调用方抛 SchemaExtractionError)\n */\nfunction resolveImportAlias(\n typeNode: TypeRefLike,\n symbol: ts.Symbol,\n checker: ts.TypeChecker,\n visited: Set<string>,\n bindings: Map<string, RuntimeType> = new Map(),\n typeArguments?: readonly ts.TypeNode[],\n): RuntimeType | null {\n const typeName = typeNode.typeName.getText();\n\n // 路径1:getAliasedSymbol 直接跟到真实 symbol\n try {\n const aliased = checker.getAliasedSymbol(symbol);\n if (aliased && aliased.declarations && aliased.declarations.length > 0) {\n const decl = aliased.declarations[0]!;\n if (ts.isInterfaceDeclaration(decl)) {\n return resolveInterfaceDeclaration(decl, checker, visited, bindings, typeArguments);\n }\n if (ts.isTypeAliasDeclaration(decl)) {\n const declBindings = bindTypeParameters(\n decl.typeParameters,\n typeArguments,\n bindings,\n checker,\n visited,\n typeNode,\n );\n return resolveTypeNode(decl.type, checker, visited, declBindings);\n }\n if (ts.isEnumDeclaration(decl)) {\n return resolveEnumDeclaration(decl);\n }\n }\n } catch {\n // getAliasedSymbol 抛错时走兜底\n }\n\n // 路径2:兜底遍历 program 的 sourceFiles 找同名声明\n // 用模块级变量 currentProgram(createProgram 时通过 setProgramContext 设置)\n // TypeChecker 类型上声明了 getProgram() 但运行时未暴露,只能通过外部传入\n const program = currentProgram;\n if (!program) return null;\n\n const allSFs = program.getSourceFiles();\n for (const sourceFile of allSFs) {\n // 跳过 lib.d.ts / node_modules / TypeScript 内置文件\n if (\n sourceFile.fileName.includes('/node_modules/') ||\n sourceFile.fileName.includes('typescript/lib/')\n ) {\n continue;\n }\n // 顶层声明查找(用独立函数返回,避免 TS 控制流分析把闭包赋值的 let 变量收窄为 never)\n const found = findTopLevelDecl(sourceFile, typeName);\n if (found) {\n if (found.kind === 'interface') {\n return resolveInterfaceDeclaration(found.node, checker, visited, bindings, typeArguments);\n }\n if (found.kind === 'typeAlias') {\n const declBindings = bindTypeParameters(\n found.node.typeParameters,\n typeArguments,\n bindings,\n checker,\n visited,\n typeNode,\n );\n return resolveTypeNode(found.node.type, checker, visited, declBindings);\n }\n if (found.kind === 'enum') {\n return resolveEnumDeclaration(found.node);\n }\n }\n }\n\n return null;\n}\n\n/**\n * 在 sourceFile 顶层查找名为 typeName 的 interface / type alias / enum 声明\n *\n * 独立为函数是因为 TS 控制流分析对\"闭包中赋值的 let 变量\"保守处理:\n * 即使 forEachChild 同步调用回调,分析器仍认为赋值可能未发生,\n * 导致外层读取时变量被收窄为初始值类型(null)。\n * 用函数返回值(带显式返回类型)绕过此限制。\n */\nexport type TopLevelDecl =\n | { kind: 'interface'; node: ts.InterfaceDeclaration }\n | { kind: 'typeAlias'; node: ts.TypeAliasDeclaration }\n | { kind: 'enum'; node: ts.EnumDeclaration };\n\nexport function findTopLevelDecl(sourceFile: ts.SourceFile, typeName: string): TopLevelDecl | null {\n let found: TopLevelDecl | null = null;\n ts.forEachChild(sourceFile, (node) => {\n if (found) return;\n if (ts.isInterfaceDeclaration(node) && node.name.text === typeName) {\n found = { kind: 'interface', node };\n } else if (ts.isTypeAliasDeclaration(node) && node.name.text === typeName) {\n found = { kind: 'typeAlias', node };\n } else if (ts.isEnumDeclaration(node) && node.name.text === typeName) {\n found = { kind: 'enum', node };\n }\n });\n return found;\n}\n\n/**\n * 解析 enum 声明为字面量联合类型\n *\n * - 字符串枚举:`enum Role { Admin = 'admin' }` → `'admin' | ...`\n * - 数值枚举:`enum Code { OK = 200 }` → `200 | ...`\n * - 隐式数值枚举:`enum Dir { Up, Down }` → `0 | 1 | ...`\n *\n * HTTP 视角:枚举值在 JSON 中是普通 string/number,按字面量联合校验即可。\n */\nexport function resolveEnumDeclaration(node: ts.EnumDeclaration): RuntimeType {\n const members: RuntimeType[] = [];\n let nextNumericValue = 0;\n\n for (const member of node.members) {\n if (member.initializer) {\n if (ts.isStringLiteral(member.initializer)) {\n members.push({ kind: 'literal', value: member.initializer.text });\n } else if (ts.isNumericLiteral(member.initializer)) {\n const num = Number(member.initializer.text);\n members.push({ kind: 'literal', value: num });\n nextNumericValue = num + 1;\n } else {\n throw new SchemaExtractionError(\n node.name.text,\n `enum 成员 \"${member.name.getText()}\" 的初始化值类型不支持,仅支持 string/number 字面量`,\n );\n }\n } else {\n // 隐式数值枚举:无初始化值,使用递增整数\n members.push({ kind: 'literal', value: nextNumericValue });\n nextNumericValue++;\n }\n }\n\n return { kind: 'union', members };\n}\n\n/**\n * 解析 interface 声明(含继承与泛型形参绑定)\n *\n * @param node interface 声明节点\n * @param checker 类型 checker\n * @param visited 防止递归循环\n * @param outerBindings 外层泛型形参绑定(调用方作用域)\n * @param typeArguments 本次引用携带的类型实参(如 `Base<string>` 的 [string]),\n * 与 node.typeParameters 按位置配对构造声明体 bindings\n */\nexport function resolveInterfaceDeclaration(\n node: ts.InterfaceDeclaration,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n outerBindings: Map<string, RuntimeType> = new Map(),\n typeArguments?: readonly ts.TypeNode[],\n): RuntimeType {\n const properties: PropertyType[] = [];\n const propMap = new Map<string, PropertyType>();\n // 索引签名([k: string]: T)与属性共存合法:属性保留,索引签名转为 catchall\n let catchall: RuntimeType | undefined;\n\n // 绑定本声明的泛型形参(声明体与 heritage 均在该作用域下解析)\n const bindings = bindTypeParameters(\n node.typeParameters,\n typeArguments,\n outerBindings,\n checker,\n visited,\n node,\n );\n\n // 处理继承的父接口\n for (const heritageClause of node.heritageClauses ?? []) {\n if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword) {\n for (const expr of heritageClause.types) {\n const parentType = resolveTypeNode(expr, checker, visited, bindings);\n if (parentType.kind === 'object') {\n for (const prop of parentType.properties) {\n propMap.set(prop.name, prop);\n }\n }\n }\n }\n }\n\n // 处理自身成员(覆盖继承的同名字段)\n for (const member of node.members) {\n // 方法签名/存取器:运行时 JSON 数据无方法语义,静默丢弃会弱化校验——显式抛错\n if (\n ts.isMethodSignature(member) ||\n ts.isGetAccessorDeclaration(member) ||\n ts.isSetAccessorDeclaration(member)\n ) {\n throw SchemaExtractionError.at(\n member,\n member.getText(),\n '接口含方法签名或存取器,运行时 JSON 数据无法校验方法——请改用具体属性类型',\n );\n }\n if (ts.isPropertySignature(member) && member.name) {\n const name = member.name.getText();\n const optional = !!member.questionToken;\n const type = member.type\n ? resolveTypeNode(member.type, checker, visited, bindings)\n : { kind: 'any' as const };\n const constraints = extractConstraintsFromJsDoc(member, name);\n validateConstraints(constraints, type, name);\n propMap.set(\n name,\n constraints.length > 0 ? { name, type, optional, constraints } : { name, type, optional },\n );\n }\n // 索引签名 → catchall(与属性/继承属性共存时不丢弃)\n if (ts.isIndexSignatureDeclaration(member)) {\n catchall = member.type\n ? resolveTypeNode(member.type, checker, visited, bindings)\n : { kind: 'any' as const };\n }\n }\n\n for (const prop of propMap.values()) {\n properties.push(prop);\n }\n\n return catchall !== undefined\n ? { kind: 'object', properties, catchall }\n : { kind: 'object', properties };\n}\n\n/**\n * 数值约束标签集合(仅 number 字段)\n */\nconst NUMBER_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'max',\n 'min',\n 'int',\n 'positive',\n 'negative',\n 'nonnegative',\n 'nonpositive',\n]);\n\n/**\n * 长度约束标签集合(string 或 array 字段)\n */\nconst LENGTH_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'maxLength',\n 'minLength',\n 'length',\n]);\n\n/**\n * 字符串格式约束标签集合(仅 string 字段)\n */\nconst STRING_FORMAT_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'regex',\n 'email',\n 'url',\n 'uuid',\n]);\n\n/**\n * 从字段的 JSDoc 注释中提取约束标签\n *\n * 解析 `/** ... *\\/` 块注释中的 `@max`/`@min`/`@maxLength` 等标签。\n * 行内注释 `// @max 100` 不识别。\n *\n * @param node 字段节点(PropertySignature)\n * @param fieldName 字段名,用于错误信息\n * @returns 约束数组(无约束时返回空数组)\n */\nfunction extractConstraintsFromJsDoc(\n node: ts.PropertySignature,\n fieldName: string,\n): TypeConstraint[] {\n // ts.getJSDocCommentsAndTags 返回 JSDoc/JsDocTag 数组\n const jsDocs = ts\n .getJSDocCommentsAndTags(node)\n .filter((entry): entry is ts.JSDoc => ts.isJSDoc(entry));\n\n if (jsDocs.length === 0) return [];\n\n const constraints: TypeConstraint[] = [];\n\n for (const jsDoc of jsDocs) {\n if (!jsDoc.tags) continue;\n for (const tag of jsDoc.tags) {\n const constraint = parseJsDocTag(tag, fieldName);\n if (constraint) constraints.push(constraint);\n }\n }\n\n return constraints;\n}\n\n/**\n * 提取 JSDoc 标签后的文本注释\n *\n * `tag.comment` 类型为 `string | NodeArray<JSDocComment> | undefined`,\n * 此处仅取纯文本形式(约束标签后跟的均为字面量值,不会包含 JSDocLink 等节点)。\n *\n * @returns 标签后的文本;无文本或非纯字符串时返回 undefined\n */\nfunction getTagCommentText(tag: ts.JSDocTag): string | undefined {\n const comment = tag.comment;\n if (typeof comment === 'string') return comment;\n return undefined;\n}\n\n/**\n * 解析单个 JSDoc 标签为约束\n *\n * @param tag JSDocTag 节点\n * @param fieldName 字段名,用于错误信息\n * @returns 约束对象;非约束标签返回 null\n */\nfunction parseJsDocTag(tag: ts.JSDocTag, fieldName: string): TypeConstraint | null {\n const tagName = tag.tagName.text;\n\n switch (tagName) {\n // 数值约束(带值)\n case 'max':\n case 'min': {\n const value = parseNumberValue(tag, fieldName, tagName);\n return { kind: tagName, value };\n }\n\n // 长度约束(带值)\n case 'maxLength':\n case 'minLength':\n case 'length': {\n const value = parseNumberValue(tag, fieldName, tagName);\n return { kind: tagName, value };\n }\n\n // 正则约束(带 /pattern/flags 值)\n case 'regex':\n case 'pattern': {\n const text = getTagCommentText(tag);\n if (!text) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签需要 /pattern/flags 形式的值`);\n }\n const regex = parseRegexLiteral(text.trim(), fieldName);\n return { kind: 'regex', pattern: regex.pattern, flags: regex.flags };\n }\n\n // 数值约束(无值)\n case 'int':\n case 'positive':\n case 'negative':\n case 'nonnegative':\n case 'nonpositive':\n return { kind: tagName };\n\n // 字符串格式约束(无值)\n case 'email':\n case 'url':\n case 'uuid':\n return { kind: tagName };\n\n default:\n return null;\n }\n}\n\n/**\n * 解析标签后的数字值\n *\n * @param tag JSDocTag 节点\n * @param fieldName 字段名,用于错误信息\n * @param tagName 标签名,用于错误信息\n */\nfunction parseNumberValue(tag: ts.JSDocTag, fieldName: string, tagName: string): number {\n const text = getTagCommentText(tag);\n if (!text) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签需要一个数字值`);\n }\n const trimmed = text.trim();\n const num = Number(trimmed);\n if (!Number.isFinite(num)) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签的值 \"${trimmed}\" 不是有效数字`);\n }\n return num;\n}\n\n/**\n * 解析 /pattern/flags 形式的正则字面量\n *\n * @param text 标签后的文本,如 \"/^[a-z]+$/i\"\n * @param fieldName 字段名,用于错误信息\n */\nfunction parseRegexLiteral(text: string, fieldName: string): { pattern: string; flags?: string } {\n // 必须以 / 开头,匹配结尾的 / 和可选 flags\n const match = /^\\/(.+)\\/([gimsuy]*)$/.exec(text);\n if (!match) {\n throw new SchemaExtractionError(fieldName, `正则值 \"${text}\" 不是 /pattern/flags 形式`);\n }\n const [, pattern, flags] = match;\n if (!pattern) {\n throw new SchemaExtractionError(fieldName, `正则值 \"${text}\" 的 pattern 部分为空`);\n }\n return flags ? { pattern, flags } : { pattern };\n}\n\n/**\n * 校验约束与字段类型是否匹配\n *\n * 不匹配时抛 SchemaExtractionError(复用现有错误类型)。\n *\n * @param constraints 约束数组\n * @param type 字段类型\n * @param fieldName 字段名,用于错误信息\n */\nfunction validateConstraints(\n constraints: TypeConstraint[],\n type: RuntimeType,\n fieldName: string,\n): void {\n if (constraints.length === 0) return;\n\n for (const constraint of constraints) {\n const kind = constraint.kind;\n\n if (NUMBER_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'number') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 number 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n\n if (LENGTH_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'string' && type.kind !== 'array') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 string 或 array 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n\n if (STRING_FORMAT_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'string') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 string 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n }\n}\n","/**\n * 根据 HTTP 方法判断主输入类型\n *\n * - GET / DELETE / HEAD:query(URL 查询参数)\n * - POST / PUT / PATCH:body(请求体)\n *\n * 注意:所有方法都可能同时有 query 和 body,\n * 这里返回的是\"主输入\"(用于校验和注入)。\n * DELETE 请求也支持 body(见 resolveInput.ts)。\n */\nexport function getInputTypeForMethod(method: string): 'query' | 'body' {\n const upper = method.toUpperCase();\n if (upper === 'GET' || upper === 'DELETE' || upper === 'HEAD') {\n return 'query';\n }\n return 'body';\n}\n\n/**\n * 判断方法是否有请求体\n *\n * POST / PUT / PATCH / DELETE 可能有 body\n * GET / HEAD 不应该有 body\n */\nexport function hasBody(method: string): boolean {\n const upper = method.toUpperCase();\n return upper === 'POST' || upper === 'PUT' || upper === 'PATCH' || upper === 'DELETE';\n}\n","/**\n * 根据 HTTP 方法和输入类型生成 schema 类型名\n * GET + query -> GETQuery\n * POST + body -> POSTBody\n * GET + params -> GETParams\n */\nexport function getSchemaName(method: string, inputType: 'query' | 'body' | 'params'): string {\n return `${method.toUpperCase()}${inputType.charAt(0).toUpperCase() + inputType.slice(1)}`;\n}\n","import ts from 'typescript';\nimport type { InjectionType } from './resolveInjection';\nimport { PARAM_TYPE_MAP } from './resolveInjection';\n\n/**\n * 参数元数据\n */\nexport interface ParamMeta {\n name: string; // 参数名\n type: InjectionType; // 注入类型\n typeName?: string; // 引用类型名(如 'Query')\n schema?: PropertyInfo[]; // 类型结构(内联类型时)\n}\n\n/**\n * 属性信息\n */\nexport interface PropertyInfo {\n name: string;\n type: string;\n optional: boolean;\n}\n\n/**\n * 注入元数据\n */\nexport interface InjectionMeta {\n params: ParamMeta[];\n}\n\n/**\n * AST 分析 handler 函数,提取参数注入元数据\n *\n * @param code handler 源码字符串\n * @param functionName 函数导出名(如 'GET')\n */\nexport function analyzeInjection(code: string, functionName: string): InjectionMeta {\n const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);\n return analyzeInjectionInSourceFile(sourceFile, functionName);\n}\n\n/**\n * 从已有 SourceFile 分析 handler 函数的参数注入元数据\n *\n * 与 analyzeInjection 语义一致,但接受 AST 节点而非源码字符串——\n * 调用方(如 collectRouteSchemaSources)的 program 中已有解析好的 SourceFile,\n * 逐方法重新 parse 同一文件字符串是纯浪费(同文件 N 个方法 = N 次全量重复解析)。\n *\n * @param sourceFile 已解析的 AST\n * @param functionName 函数导出名(如 'GET')\n */\nexport function analyzeInjectionInSourceFile(\n sourceFile: ts.SourceFile,\n functionName: string,\n): InjectionMeta {\n const params: ParamMeta[] = [];\n\n // 遍历 AST 查找目标函数\n ts.forEachChild(sourceFile, (node) => {\n if (ts.isFunctionDeclaration(node) && node.name?.text === functionName) {\n // 分析参数\n for (const param of node.parameters) {\n const paramMeta = analyzeParam(param, sourceFile);\n params.push(paramMeta);\n }\n }\n });\n\n return { params };\n}\n\n/**\n * 分析单个参数\n */\nfunction analyzeParam(param: ts.ParameterDeclaration, sourceFile: ts.SourceFile): ParamMeta {\n const name = param.name.getText(sourceFile);\n const type = PARAM_TYPE_MAP[name] || 'unknown';\n\n const result: ParamMeta = { name, type };\n\n // 分析类型\n if (param.type) {\n if (ts.isTypeReferenceNode(param.type)) {\n // 引用类型:Query, Body 等\n result.typeName = param.type.typeName.getText(sourceFile);\n } else if (ts.isTypeLiteralNode(param.type)) {\n // 内联类型:{ page: number }\n result.schema = extractSchema(param.type, sourceFile);\n }\n }\n\n return result;\n}\n\n/**\n * 从类型字面量提取 schema\n */\nfunction extractSchema(typeNode: ts.TypeLiteralNode, sourceFile: ts.SourceFile): PropertyInfo[] {\n const schema: PropertyInfo[] = [];\n\n for (const member of typeNode.members) {\n if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {\n const propName = member.name.text;\n const optional = !!member.questionToken;\n const propType = member.type?.getText(sourceFile) || 'unknown';\n\n schema.push({\n name: propName,\n type: propType,\n optional,\n });\n }\n }\n\n return schema;\n}\n","import { createPrograms } from '../ast/createProgram';\nimport {\n createLazyTypeResolver,\n type HandlerTypeInfo,\n type LazyTypeResolver,\n} from '../ast/extractHandlerTypes';\nimport { getInputTypeForMethod } from '../runtime/inputType';\nimport { getSchemaName } from '../validator/schemaName';\nimport { analyzeInjectionInSourceFile } from '../injection/analyzeInjection';\nimport type { RouteManifest } from '../router/routeTypes';\nimport path from 'node:path';\n\n/**\n * 单个路由的 schema 提取结果\n *\n * key 使用 urlPath(如 '/api/hello')而非 filePath,因为 urlPath 在 dev/prod 完全一致,\n * 无需 remapManifestKeys 桥接 .ts/.js 路径差异。\n */\nexport interface RouteSchemaSource {\n /** 路由 URL 路径(如 '/api/hello'),作为 schema key */\n urlPath: string;\n /** 源文件绝对路径(用于 generateSchemaFiles 按文件分组生成 zod.js) */\n filePath: string;\n schemaName: string;\n typeInfo: HandlerTypeInfo | null;\n /**\n * 是否对 number/boolean 字段生成 z.preprocess 字符串转换(coerce)。\n *\n * - query/params:始终 coerce=true(URL 来源均为 string)\n * - body:始终 coerce=false(JSON 解析已是天然 JS 类型)\n * - form:coerce=true(form-urlencoded 来源均为 string),由本函数在提取时\n * 检测到 handler 声明 `form` 参数时显式设置。schema 名仍为 `POSTBody`\n * (与 body 共享运行时 schema key),运行时 validateInput 无需感知 form/body 差异。\n *\n * 未设置时由 generateSchemaFileSource 回退到 schemaName 后缀正则推断(Query/Params → true)。\n */\n coerce?: boolean;\n}\n\n/**\n * 从路由清单收集 schema 提取所需的原始数据\n *\n * dev 和 prd 共享的核心提取流程:\n * 1. 按文件分组遍历路由\n * 2. 批量共享 Program(同一次提取只按文件组创建少量 Program)\n * 3. 对每个路由用 analyzeInjectionInSourceFile 定位入口参数类型,\n * extractTypeInfo 解析入口类型(入口类型必须严格解析,失败抛 SchemaExtractionError)\n * 4. 返回按文件分组的惰性类型解析器——generateSchemaFiles 生成 zod.js 时\n * 遇到 ref(同文件循环引用)按需解析并缓存\n *\n * 惰性语义:与路由无关的类型(未被任何入口类型引用)不会被解析——文件里\n * 存在一个含不支持语法的无关类型不再拖垮整个 build/reload。\n */\nexport function collectRouteSchemaSources(\n routes: RouteManifest,\n rootDir?: string,\n): {\n sources: RouteSchemaSource[];\n /** 按文件分组的惰性类型解析器(generateSchemaFiles 解析 ref 用) */\n resolversByFile: Map<string, LazyTypeResolver>;\n} {\n // 按文件分组收集方法(去重)\n // key 是文件绝对路径(createProgram 需要),但 schema key 用 urlPath\n const methodsByFile = new Map<string, { urlPath: string; methods: Set<string> }>();\n for (const route of routes) {\n const filePath = rootDir ? path.resolve(rootDir, route.filePath) : route.filePath;\n let entry = methodsByFile.get(filePath);\n if (!entry) {\n entry = { urlPath: route.urlPath, methods: new Set() };\n methodsByFile.set(filePath, entry);\n }\n entry.methods.add(route.method);\n }\n\n // 批量共享 Program:同一次提取只创建一个 Program,避免逐文件全量解析\n const programByFile = createPrograms([...methodsByFile.keys()]);\n\n // 每个文件一个惰性解析器:入口类型立即解析,ref 按需解析(缓存幂等)\n const resolversByFile = new Map<string, LazyTypeResolver>();\n for (const filePath of methodsByFile.keys()) {\n resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath)!, filePath));\n }\n\n // 每个文件提取 schema(key 用 urlPath)\n const sources: RouteSchemaSource[] = [];\n for (const [filePath, entry] of methodsByFile) {\n const program = programByFile.get(filePath)!;\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) continue;\n const resolver = resolversByFile.get(filePath)!;\n for (const method of entry.methods) {\n const inputType = getInputTypeForMethod(method);\n const schemaName = getSchemaName(method, inputType);\n // 复用 program 已解析的 SourceFile,逐方法零重复 parse\n const meta = analyzeInjectionInSourceFile(sourceFile, method);\n // POST/PUT/PATCH(inputType='body'):优先找 body 参数,找不到再找 form 参数。\n // form 与 body 共享 schema 名(POSTBody),运行时 validateInput 仍按 POSTBodySchema 查找;\n // 差异仅在校验:form 声明时通过 source.coerce=true 显式覆盖(form 值均为 string,\n // 需 z.preprocess 转换 number/boolean 字段)。\n const param =\n meta.params.find((p) => p.type === inputType) ??\n (inputType === 'body' ? meta.params.find((p) => p.type === 'form') : undefined);\n const isForm = param?.type === 'form';\n // 入口类型:必须严格解析(失败抛 SchemaExtractionError,带文件/类型上下文)\n const typeInfo = param?.typeName ? resolver.resolve(param.typeName) : null;\n sources.push({\n urlPath: entry.urlPath,\n filePath,\n schemaName,\n typeInfo,\n coerce: isForm || undefined,\n });\n }\n }\n\n return { sources, resolversByFile };\n}\n","import {\n SchemaExtractionError,\n type RuntimeType,\n type TupleElement,\n type TypeConstraint,\n} from './resolveTypeNode';\nimport type { HandlerTypeInfo } from './extractHandlerTypes';\n\n/**\n * 类型解析器:根据类型名获取 RuntimeType\n *\n * 用于解析循环引用中的 ref。调用方负责提供(通常从 AST 提取)。\n */\nexport type TypeResolver = (name: string) => RuntimeType | undefined;\n\n/**\n * 代码生成上下文\n *\n * 跟踪已收集的命名类型,用于生成 z.lazy 声明。\n */\nclass CodeGenContext {\n /** 命名类型集合:name → RuntimeType */\n readonly namedTypes = new Map<string, RuntimeType>();\n /** 类型解析器(用于解析 ref 的实际类型) */\n readonly resolveType: TypeResolver;\n /** 入口类型原始名(typeInfo.name,用于识别入口类型的自引用) */\n entryTypeName = '';\n /** 入口类型导出名(exportName,自引用时用此名生成变量名) */\n entryExportName = '';\n /**\n * 是否生成 coerce 逻辑(query/params 场景,URL 来源均为 string)\n *\n * true 时为 number/boolean 字段包 z.preprocess,把合法的字符串转成对应类型。\n * 嵌套类型(array/object/tuple/union 等)的元素递归处理。\n */\n coerce = false;\n\n constructor(resolveType: TypeResolver) {\n this.resolveType = resolveType;\n }\n}\n\n/**\n * 从 RuntimeType 收集所有命名类型(ref)\n *\n * 递归遍历 RuntimeType,遇到 ref 时解析并加入 namedTypes。\n * 已收集的类型不重复处理(防止循环引用无限递归)。\n */\nexport function collectNamedTypes(type: RuntimeType, ctx: CodeGenContext): void {\n switch (type.kind) {\n case 'string':\n case 'number':\n case 'boolean':\n case 'bigint':\n case 'null':\n case 'undefined':\n case 'any':\n case 'unknown':\n case 'literal':\n case 'date':\n return;\n\n case 'array':\n collectNamedTypes(type.element, ctx);\n return;\n\n case 'tuple':\n for (const el of type.elements) {\n collectNamedTypes(el.type, ctx);\n }\n return;\n\n case 'object':\n for (const prop of type.properties) {\n collectNamedTypes(prop.type, ctx);\n }\n return;\n\n case 'union':\n for (const member of type.members) {\n collectNamedTypes(member, ctx);\n }\n return;\n\n case 'record':\n collectNamedTypes(type.key, ctx);\n collectNamedTypes(type.value, ctx);\n return;\n\n case 'map':\n collectNamedTypes(type.key, ctx);\n collectNamedTypes(type.value, ctx);\n return;\n\n case 'set':\n collectNamedTypes(type.element, ctx);\n return;\n\n case 'ref': {\n if (ctx.namedTypes.has(type.name)) return;\n // 先占位,防止循环引用无限递归\n ctx.namedTypes.set(type.name, { kind: 'any' });\n const resolved = ctx.resolveType(type.name);\n if (resolved) {\n ctx.namedTypes.set(type.name, resolved);\n collectNamedTypes(resolved, ctx);\n return;\n }\n // ref 只在 AST 提取阶段遇到真实 TS 类型引用时产生(首现内联、二次标 ref),\n // 声明必然存在于某个源文件中;解析不到说明查找能力不足(如命名空间内声明),\n // 按框架约定显式抛错而非静默降级为 z.unknown()(AGENTS.md §6.3)\n throw new SchemaExtractionError(\n type.name,\n `无法解析命名类型引用 \"${type.name}\"(handler 文件与 program 源文件中均未找到同名顶层声明)`,\n );\n }\n }\n}\n\n/**\n * RuntimeType → zod 表达式字符串(不含声明)\n *\n * @param type 类型描述\n * @param ctx 代码生成上下文\n * @param constraints 字段级约束(仅 object 字段传入,影响最外层 zod 链)\n */\nexport function runtimeTypeToZodExpression(\n type: RuntimeType,\n ctx: CodeGenContext,\n constraints?: TypeConstraint[],\n): string {\n const expr = baseExpression(type, ctx);\n // 字段级约束链(@max/@min/@regex 等)追加到基础表达式后\n // coerce 模式下约束需作用在 preprocess 内部的 z.X() 上,故先应用约束再包裹 preprocess\n const withConstraints =\n constraints && constraints.length > 0 ? applyConstraints(expr, constraints, type.kind) : expr;\n // coerce 模式下,number/boolean 在外层包 z.preprocess\n // 嵌套类型(array/object/tuple/union 等)内部已递归处理,外层不再包裹\n if (ctx.coerce && (type.kind === 'number' || type.kind === 'boolean')) {\n return wrapCoercePreprocess(type.kind, withConstraints);\n }\n // coerce 模式下,数字/布尔字面量同样需要字符串转换:query 声明 `status: 1 | 2`\n // 时 URL 传来 \"1\"(string),裸 z.literal(1) 必然校验失败。union 成员经本函数\n // 递归生成,字面量成员逐个包裹(string 字面量天然命中,无需转换)\n if (\n ctx.coerce &&\n type.kind === 'literal' &&\n (typeof type.value === 'number' || typeof type.value === 'boolean')\n ) {\n return wrapCoercePreprocess(\n typeof type.value === 'number' ? 'number' : 'boolean',\n withConstraints,\n );\n }\n return withConstraints;\n}\n\n/**\n * 在 zod 表达式上追加约束链\n *\n * 约束与字段类型的匹配在 AST 提取阶段已校验,这里直接生成链式调用。\n *\n * coerce 模式下调用方应先 applyConstraints 再 wrapCoercePreprocess,\n * 使约束作用在 preprocess 内部的 z.X() 上,而非 preprocess 外壳。\n *\n * @param baseExpr 基础 zod 表达式(未包裹 preprocess)\n * @param constraints 约束数组\n * @param typeKind 字段类型 kind\n */\nfunction applyConstraints(\n baseExpr: string,\n constraints: TypeConstraint[],\n typeKind: RuntimeType['kind'],\n): string {\n const suffix = constraints.map((c) => constraintToZodChain(c, typeKind)).join('');\n return `${baseExpr}${suffix}`;\n}\n\n/**\n * 单个约束 → zod 链式方法字符串\n *\n * @param constraint 约束\n * @param typeKind 字段类型 kind(用于区分 string/array 的 max/min 含义)\n */\nfunction constraintToZodChain(constraint: TypeConstraint, _typeKind: RuntimeType['kind']): string {\n switch (constraint.kind) {\n case 'max':\n return `.max(${constraint.value})`;\n case 'min':\n return `.min(${constraint.value})`;\n case 'int':\n return '.int()';\n case 'positive':\n return '.positive()';\n case 'negative':\n return '.negative()';\n case 'nonnegative':\n return '.nonnegative()';\n case 'nonpositive':\n return '.nonpositive()';\n case 'maxLength':\n return `.max(${constraint.value})`;\n case 'minLength':\n return `.min(${constraint.value})`;\n case 'length':\n return `.length(${constraint.value})`;\n case 'regex': {\n const flags = constraint.flags ?? '';\n // 用 new RegExp 构造,避免 pattern 中含 / 字符导致的字面量语法错误\n return `.regex(new RegExp(${JSON.stringify(constraint.pattern)}${flags ? `, ${JSON.stringify(flags)}` : ''}))`;\n }\n case 'email':\n return '.email()';\n case 'url':\n return '.url()';\n case 'uuid':\n return '.uuid()';\n }\n}\n\n/**\n * 生成基础 zod 表达式(不含 coerce 包裹)\n */\nfunction baseExpression(type: RuntimeType, ctx: CodeGenContext): string {\n switch (type.kind) {\n case 'string':\n return 'z.string()';\n case 'number':\n return 'z.number()';\n case 'boolean':\n return 'z.boolean()';\n case 'bigint':\n // AST 提取阶段已抛错,这里不会到达\n return 'z.never()';\n case 'null':\n return 'z.null()';\n case 'undefined':\n return 'z.undefined()';\n case 'any':\n case 'unknown':\n return 'z.unknown()';\n case 'literal':\n return `z.literal(${JSON.stringify(type.value)})`;\n case 'array':\n return `z.array(${runtimeTypeToZodExpression(type.element, ctx)})`;\n case 'tuple':\n return generateTupleExpression(type.elements, ctx);\n case 'object':\n return generateObjectExpression(type, ctx);\n case 'union':\n return generateUnionExpression(type.members, ctx);\n case 'date':\n // z.preprocess 限制只接受 string/Date:string → new Date(string),其他原样传入\n // z.coerce.date() 会把 number 也转为 Date(new Date(123)),不符合需求\n // date 类型本身已用 preprocess,coerce 模式下无需再包一层\n return 'z.preprocess((v) => (typeof v === \"string\" ? new Date(v) : v), z.date())';\n case 'record':\n return `z.record(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)})`;\n case 'map':\n // Map<K,V> — JSON 序列化为 entries 数组,coerceMap 还原为 Map 实例后再交给 z.map 校验\n // coerce=false(body)场景也需要 preprocess:JSON.parse 出来是数组,不是 Map 实例\n return `z.preprocess(coerceMap, z.map(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)}))`;\n case 'set':\n // Set<T> — JSON 序列化为数组,coerceSet 还原为 Set 实例后再交给 z.set 校验\n // coerce=false(body)场景也需要 preprocess:JSON.parse 出来是数组,不是 Set 实例\n return `z.preprocess(coerceSet, z.set(${runtimeTypeToZodExpression(type.element, ctx)}))`;\n case 'ref':\n // ref 引用命名类型,直接用 NameSchema 变量\n // 入口类型的自引用需用 exportName(exportName 可能与 typeInfo.name 不同)\n if (type.name === ctx.entryTypeName) {\n return `${ctx.entryExportName}Schema`;\n }\n return `${type.name}Schema`;\n }\n}\n\n/**\n * coerceNumber 公用函数源码(ESM export 格式)\n *\n * 用于 query/params 的 string → number 转换:\n * - 合法数字字符串(\"1\"、\"3.14\")转为 number\n * - 空串/NaN 保留原值,让 z.number() 报错(避免 Number(\"\") = 0 的陷阱)\n *\n * 写入 dist 根部的 faapi-helpers.js,各 zod.js 通过相对路径 import 复用。\n */\nexport const COERCE_NUMBER_HELPER =\n 'export const coerceNumber = (v) => typeof v === \"string\" && v.trim() !== \"\" && !isNaN(Number(v)) ? Number(v) : v;';\n\n/**\n * coerceBoolean 公用函数源码(ESM export 格式)\n *\n * 用于 query/params 的 string → boolean 转换:\n * - 'true'/'1' → true\n * - 'false'/'0' → false\n * - 其他值保留原值,让 z.boolean() 报错\n *\n * 写入 dist 根部的 faapi-helpers.js,各 zod.js 通过相对路径 import 复用。\n */\nexport const COERCE_BOOLEAN_HELPER =\n 'export const coerceBoolean = (v) => {\\n' +\n ' const lower = typeof v === \"string\" ? v.toLowerCase() : v;\\n' +\n ' return lower === \"true\" || lower === \"1\" ? true : lower === \"false\" || lower === \"0\" ? false : v;\\n' +\n '};';\n\n/**\n * coerceMap 公用函数源码(ESM export 格式)\n *\n * 用于 Map<K,V> 的 JSON 还原:JSON 序列化为 entries 数组,JSON.parse 后还原为 Map 实例\n * - entries 数组 [[\"k\",v],...] → new Map(entries)\n * - Map 实例 → 原样返回\n * - 普通对象 { k: v } → new Map(Object.entries(v))\n * - 其他值保留原值,让 z.map() 报错\n *\n * 与 coerceNumber/coerceBoolean 不同,coerceMap 在 coerce=false(body 场景)下也使用:\n * JSON.parse 出来的是数组/对象,不是 Map 实例,必须还原才能交给 z.map() 校验。\n */\nexport const COERCE_MAP_HELPER =\n 'export const coerceMap = (v) => Array.isArray(v) ? new Map(v) : v instanceof Map ? v : (v && typeof v === \"object\" ? new Map(Object.entries(v)) : v);';\n\n/**\n * coerceSet 公用函数源码(ESM export 格式)\n *\n * 用于 Set<T> 的 JSON 还原:JSON 序列化为数组,JSON.parse 后还原为 Set 实例\n * - Set 实例 → 原样返回\n * - 数组 [item,...] → new Set(array)\n * - 其他值保留原值,让 z.set() 报错\n *\n * 与 coerceNumber/coerceBoolean 不同,coerceSet 在 coerce=false(body 场景)下也使用:\n * JSON.parse 出来的是数组,不是 Set 实例,必须还原才能交给 z.set() 校验。\n */\nexport const COERCE_SET_HELPER =\n 'export const coerceSet = (v) => v instanceof Set ? v : (Array.isArray(v) ? new Set(v) : v);';\n\n/**\n * faapi-helpers.js 文件名(生成在 dist 根部)\n */\nexport const HELPERS_FILENAME = 'faapi-helpers.js';\n\n/**\n * 生成 faapi-helpers.js 文件源码\n *\n * 包含 coerceNumber / coerceBoolean / coerceMap / coerceSet 四个公用函数(ESM export)。\n * 由 generateSchemaFiles 按需生成:query/params 场景的 number/boolean 字段触发 coerceNumber/coerceBoolean;\n * Map/Set 字段(query/params 与 body 均包含)触发 coerceMap/coerceSet。\n */\nexport function generateHelpersFileSource(): string {\n return [\n '// faapi-helpers.js — faapi 自动生成的公用函数(请勿手动编辑)',\n COERCE_NUMBER_HELPER,\n COERCE_BOOLEAN_HELPER,\n COERCE_MAP_HELPER,\n COERCE_SET_HELPER,\n '',\n ].join('\\n');\n}\n\n/**\n * 检测代码是否引用了 coerceNumber / coerceBoolean / coerceMap / coerceSet 变量\n *\n * 用于 generateSchemaFiles 判断是否需要生成 faapi-helpers.js。\n * 注意:Map/Set 在 coerce=false(body 场景)下也会引用 coerceMap/coerceSet,\n * 故仅凭此检测即可覆盖所有需要生成 helpers 的场景。\n */\nexport function usesCoerceHelpers(code: string): boolean {\n return (\n code.includes('coerceNumber') ||\n code.includes('coerceBoolean') ||\n code.includes('coerceMap') ||\n code.includes('coerceSet')\n );\n}\n\n/**\n * 为 number/boolean 表达式包裹 z.preprocess(用于 query coerce)\n *\n * 引用从 faapi-helpers.js import 的 coerceNumber / coerceBoolean 变量,\n * 避免每个字段都内联一长串函数。\n */\nfunction wrapCoercePreprocess(kind: 'number' | 'boolean', inner: string): string {\n if (kind === 'number') {\n return `z.preprocess(coerceNumber, ${inner})`;\n }\n return `z.preprocess(coerceBoolean, ${inner})`;\n}\n\n/**\n * 生成元组 zod 表达式\n *\n * zod 的 tuple 不直接支持可选元素,需要用 union 模拟:\n * - 全部必填:`z.tuple([e0, e1, ...])`\n * - 含可选元素:`z.union([z.tuple([e0]), z.tuple([e0, e1]), ...])`(从短到长)\n * - 含 rest:`z.tuple([e0, ...]).rest(eRest)`\n */\nfunction generateTupleExpression(elements: TupleElement[], ctx: CodeGenContext): string {\n const fixedExprs: string[] = [];\n const fixedOptional: boolean[] = [];\n let restExpression = '';\n let restStarted = false;\n\n for (const el of elements) {\n if (el.rest) {\n restExpression = runtimeTypeToZodExpression(el.type, ctx);\n restStarted = true;\n } else if (!restStarted) {\n fixedExprs.push(runtimeTypeToZodExpression(el.type, ctx));\n fixedOptional.push(el.optional);\n }\n }\n\n // 含 rest 元素:z.tuple([fixed...]).rest(restType)。\n // 可选前缀保留(zod v4 原生支持 z.tuple([X.optional()]).rest(...))——\n // 此前丢弃 fixedOptional 导致 [string?, ...number[]] 的空数组被误拒\n if (restExpression) {\n const fixedWithOptional = fixedExprs.map((expr, i) =>\n fixedOptional[i] ? `${expr}.optional()` : expr,\n );\n return `z.tuple([${fixedWithOptional.join(', ')}]).rest(${restExpression})`;\n }\n\n // 无可选元素:z.tuple([e0, e1, ...])\n const hasOptional = fixedOptional.some((o) => o);\n if (!hasOptional) {\n return `z.tuple([${fixedExprs.join(', ')}])`;\n }\n\n // 含可选元素:从后往前截断可选元素,生成 union\n // TS 语法要求可选元素在必填元素之后,所以只需从后往前去掉可选元素\n const variants: string[] = [];\n for (let len = fixedExprs.length; len >= 0; len--) {\n // len === 0 时只有全部可选才有效(空元组)\n // len > 0 时,第 len-1 个元素必须存在(必填或可选都行)\n // 截断点:去掉 fixedExprs[len..end],这些必须都是可选的\n const removed = fixedOptional.slice(len);\n if (removed.length > 0 && removed.some((o) => !o)) {\n // 被截断的元素中有必填元素,无效截断\n break;\n }\n const subset = fixedExprs.slice(0, len);\n variants.push(`z.tuple([${subset.join(', ')}])`);\n }\n\n // 从短到长排序(更自然的校验顺序)\n variants.reverse();\n if (variants.length === 1) {\n return variants[0]!;\n }\n return `z.union([${variants.join(', ')}])`;\n}\n\n/**\n * 生成对象 zod 表达式\n *\n * 可选字段用 .optional(),约束链在 .optional() 之前生成。\n * 带索引签名的对象(catchall)生成 z.object({...}).catchall(valueSchema)——\n * 索引签名与属性共存时属性保留(resolveTypeNode 不再丢弃)。\n */\nfunction generateObjectExpression(\n type: Extract<RuntimeType, { kind: 'object' }>,\n ctx: CodeGenContext,\n): string {\n const fields = type.properties.map((prop) => {\n const expr = runtimeTypeToZodExpression(prop.type, ctx, prop.constraints);\n const finalExpr = prop.optional ? `${expr}.optional()` : expr;\n return `${JSON.stringify(prop.name)}: ${finalExpr}`;\n });\n const objectExpr = `z.object({ ${fields.join(', ')} })`;\n return type.catchall !== undefined\n ? `${objectExpr}.catchall(${runtimeTypeToZodExpression(type.catchall, ctx)})`\n : objectExpr;\n}\n\n/**\n * 生成联合类型 zod 表达式\n *\n * 优化:如果联合中包含 null,用 .nullable() 简化\n * 否则用 z.union([m0, m1, ...])\n */\nfunction generateUnionExpression(members: RuntimeType[], ctx: CodeGenContext): string {\n const hasNull = members.some((m) => m.kind === 'null');\n const nonNull = members.filter((m) => m.kind !== 'null');\n\n // 只有一个非 null 成员 + null → .nullable()\n if (hasNull && nonNull.length === 1) {\n return `${runtimeTypeToZodExpression(nonNull[0]!, ctx)}.nullable()`;\n }\n\n // 多个成员 + null → z.union([...]).nullable()\n if (hasNull) {\n const unionInner = nonNull.map((m) => runtimeTypeToZodExpression(m, ctx)).join(', ');\n return `z.union([${unionInner}]).nullable()`;\n }\n\n // 无 null → z.union([...])\n const unionInner = members.map((m) => runtimeTypeToZodExpression(m, ctx)).join(', ');\n return `z.union([${unionInner}])`;\n}\n\n/**\n * 生成命名类型的 zod schema 声明\n *\n * - 无循环引用:`const NameSchema = z.object({ ... })`\n * - 有循环引用:`const NameSchema = z.lazy(() => z.object({ ... }))`\n */\nfunction generateNamedTypeDeclaration(\n name: string,\n type: RuntimeType,\n ctx: CodeGenContext,\n): string {\n const expr = runtimeTypeToZodExpression(type, ctx);\n const hasRef = containsRef(type, new Set([name]));\n if (hasRef) {\n // 循环引用:用 z.lazy 延迟求值\n return `const ${name}Schema = z.lazy(() => ${expr});`;\n }\n return `const ${name}Schema = ${expr};`;\n}\n\n/**\n * 检查 RuntimeType 是否包含对自身的 ref(循环引用)\n */\nfunction containsRef(type: RuntimeType, visited: Set<string>): boolean {\n switch (type.kind) {\n case 'ref':\n return visited.has(type.name);\n case 'array':\n return containsRef(type.element, visited);\n case 'tuple':\n return type.elements.some((el) => containsRef(el.type, visited));\n case 'object':\n return type.properties.some((prop) => containsRef(prop.type, visited));\n case 'union':\n return type.members.some((m) => containsRef(m, visited));\n case 'record':\n return containsRef(type.key, visited) || containsRef(type.value, visited);\n case 'map':\n return containsRef(type.key, visited) || containsRef(type.value, visited);\n case 'set':\n return containsRef(type.element, visited);\n default:\n return false;\n }\n}\n\n/**\n * 生成单个类型的 zod schema 代码(含命名类型声明)\n *\n * 返回的代码包含:\n * - `import { z } from 'zod'`\n * - 内部命名类型的 const 声明(带 z.lazy 循环引用保护)\n * - `export const NameSchema = ...` 入口导出\n *\n * @param typeInfo 类型信息\n * @param resolveType 类型解析器(用于解析 ref 的实际类型)\n * @param exportName 导出的 schema 变量名(不含 Schema 后缀)。默认用 typeInfo.name。\n * validateInput 按 `${schemaName}Schema` 查找,需与导出名一致。\n * @param coerce 是否生成 coerce 逻辑(query/params 场景)。默认 false。\n * true 时为 number/boolean 字段(含嵌套元素)包 z.preprocess。\n */\nexport function generateZodSchemaSource(\n typeInfo: HandlerTypeInfo,\n resolveType: TypeResolver,\n exportName?: string,\n coerce = false,\n): string {\n const { namedTypeDeclarations, entryDeclaration } = generateZodSchemaSourceParts(\n typeInfo,\n resolveType,\n exportName,\n coerce,\n );\n\n const lines: string[] = [];\n lines.push(\"import { z } from 'zod';\");\n lines.push('');\n\n for (const { declaration } of namedTypeDeclarations) {\n lines.push(declaration);\n }\n if (namedTypeDeclarations.length > 0) lines.push('');\n\n lines.push(entryDeclaration);\n\n return lines.join('\\n');\n}\n\n/**\n * 生成单个类型的 zod schema 代码片段(结构化形式)\n *\n * 与 generateZodSchemaSource 的区别:不拼 import 头,命名类型声明与入口导出分开返回,\n * 供文件级组装按名去重——同一 handler 文件的多个方法引用同一命名类型时,命名类型\n * 声明只允许出现一次,否则拼接产物含重复 const 声明,import 即 SyntaxError。\n *\n * 语义与 generateZodSchemaSource 完全一致(含 coerce / z.lazy 循环引用处理)。\n */\nexport interface ZodSchemaSourceParts {\n /** 命名类型声明(按首次引用顺序排列;name 为不含 Schema 后缀的类型名) */\n namedTypeDeclarations: Array<{ name: string; declaration: string }>;\n /** 入口 schema 导出(`export const NameSchema = ...;`) */\n entryDeclaration: string;\n}\n\nexport function generateZodSchemaSourceParts(\n typeInfo: HandlerTypeInfo,\n resolveType: TypeResolver,\n exportName?: string,\n coerce = false,\n): ZodSchemaSourceParts {\n const ctx = new CodeGenContext(resolveType);\n const name = exportName ?? typeInfo.name;\n ctx.entryTypeName = typeInfo.name;\n ctx.entryExportName = name;\n ctx.coerce = coerce;\n\n // 收集入口类型中的所有命名类型(不含入口类型本身,避免重复声明)\n collectNamedTypes(typeInfo.runtimeType, ctx);\n ctx.namedTypes.delete(typeInfo.name);\n\n const namedTypeDeclarations = [...ctx.namedTypes].map(([n, type]) => ({\n name: n,\n declaration: generateNamedTypeDeclaration(n, type, ctx),\n }));\n\n // 入口 schema 导出(含循环引用时用 z.lazy)\n const entryExpr = runtimeTypeToZodExpression(typeInfo.runtimeType, ctx);\n const hasSelfRef = containsRef(typeInfo.runtimeType, new Set([typeInfo.name]));\n const entryDeclaration = hasSelfRef\n ? `export const ${name}Schema = z.lazy(() => ${entryExpr});`\n : `export const ${name}Schema = ${entryExpr};`;\n\n return { namedTypeDeclarations, entryDeclaration };\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport { compileDevRoutes } from './compileDevRoutes';\nimport { generateSchemaFiles } from './generateSchemaFiles';\nimport { getRuntimeSchemaPath } from './generateSchemaFiles';\nimport { collectRelativeImports } from './collectImports';\nimport type { RouteManifest } from '../router/routeTypes';\n\n/**\n * 按需编译(Vite 风格):仅在请求时编译被访问的 handler.ts 及其依赖闭包 + 生成 zod.js\n *\n * 设计目标:dev 启动时零编译、零 schema 生成,handler.js / zod.js 首次被需要时才触发。\n * 与阶段 1(scanRoutes 去 import)配合,dev 冷启动近乎瞬开。\n *\n * 依赖闭包:`bundle: false` 逐文件编译不分析 import 关系,只编译 handler 单文件时,\n * handler 引用的共享模块(如 `../../lib/db`)没有产物,首次请求 import 即\n * ERR_MODULE_NOT_FOUND。因此 ensureCompiled 先收集依赖闭包\n * (collectRelativeImports,相对 + tsconfig paths 别名),跳过产物已新鲜的文件后\n * 批量编译。src 外依赖不收集(outbase=src 语义限制,与 watcher 增量编译一致)。\n *\n * 四种触发路径:\n * 1. `loadRouteModule` 先调 `ensureCompiled` 编译源码 → 再 import handler.js\n * 2. `loadWsHandler` 同上(WS handler.js)\n * 3. `createServer` 调 `ensureSchemaGenerated` 检查 zod.js 是否存在/最新 → 不存在则生成\n * 4. watcher 文件变化 → 增量编译该文件(保留现有逻辑)+ 删除 stale zod.js\n * 5. `createServer` / `handleWsUpgrade` 首次加载目录中间件前调 `ensureMiddlewaresCompiled`\n *\n * mtime 缓存(阶段 4):\n * - `ensureCompiled`:handler.js 存在且 mtime ≥ 源码 mtime → 跳过编译\n * - `ensureSchemaGenerated`:zod.js 存在且 mtime ≥ 源码 mtime → 跳过生成\n * - 首次调用后写入内存 Set,后续直接跳过(避免 fs.statSync 调用)\n * - watcher 触发时清除内存 Set,mtime 重新评估\n *\n * 并发去重(mutex):\n * - 同一文件的并发编译请求会共享同一个 in-flight Promise,避免重复触发 esbuild\n * - 同一 schemaPath 的并发生成请求同理\n * - watcher 触发 clear 时同步清空 in-flight Map\n */\n\n/**\n * 比较源文件和产物文件的 mtime\n *\n * @returns true 表示产物是最新的(可复用),false 表示需要重新生成\n */\nfunction isProductFresh(sourceAbsPath: string, productAbsPath: string): boolean {\n try {\n const srcStat = fs.statSync(sourceAbsPath);\n const prodStat = fs.statSync(productAbsPath);\n return prodStat.mtimeMs >= srcStat.mtimeMs;\n } catch {\n return false;\n }\n}\n\n// ─── DevOnDemand 状态封装 ────────────────────────────────────────────\n\n/**\n * 按需编译运行时状态\n *\n * 封装原本散落的 4 个模块级可变状态 + 2 个 mutex Map,避免全局污染 +\n * 便于测试隔离(_resetDevOnDemandState 重置回初始状态)。\n *\n * faapi 单进程单 server 设计下,单例 state 已足够。多实例测试场景通过\n * _resetDevOnDemandState 在 beforeEach / afterEach 中清空状态。\n */\ninterface DevOnDemandState {\n /** 是否启用 dev 按需编译模式(prod 始终 false) */\n enabled: boolean;\n /** dev 模式产物目录(仅 enabled 时使用) */\n distDir: string | undefined;\n /** 已编译文件缓存(源码绝对路径) */\n compiledFiles: Set<string>;\n /** 已生成 schema 缓存(schemaPath 绝对路径) */\n generatedSchemas: Set<string>;\n /** handler.js 编译 mutex:同一 sourceAbsPath 的并发请求共享同一 Promise */\n inFlightCompilations: Map<string, Promise<void>>;\n /** zod.js 生成 mutex:同一 schemaPath 的并发请求共享同一 Promise */\n inFlightSchemaGenerations: Map<string, Promise<void>>;\n}\n\nfunction createDevOnDemandState(): DevOnDemandState {\n return {\n enabled: false,\n distDir: undefined,\n compiledFiles: new Set(),\n generatedSchemas: new Set(),\n inFlightCompilations: new Map(),\n inFlightSchemaGenerations: new Map(),\n };\n}\n\nconst state: DevOnDemandState = createDevOnDemandState();\n\n/**\n * 重置 dev on demand 状态(仅测试用)\n *\n * 清空所有缓存 + mutex + 标记位,让下一次测试从干净状态开始。\n * 生产代码不应调用此函数——会丢失已编译产物的缓存。\n */\nexport function _resetDevOnDemandState(): void {\n state.enabled = false;\n state.distDir = undefined;\n state.compiledFiles.clear();\n state.generatedSchemas.clear();\n state.inFlightCompilations.clear();\n state.inFlightSchemaGenerations.clear();\n}\n\n// ─── handler.js 按需编译 ────────────────────────────────────────────\n\n/**\n * 清空所有按需编译缓存(reloadRoutes 时调用)\n *\n * 同时清空 in-flight mutex Map + 产物→源码映射缓存(源文件新建/删除后映射需重建),\n * 避免 watcher 重置后旧 Promise 永久阻塞。\n */\nexport function clearCompiledFiles(): void {\n state.compiledFiles.clear();\n state.inFlightCompilations.clear();\n sourcePathCache.clear();\n}\n\n/**\n * 确保源文件及其依赖闭包已编译为产物,未编译则触发批量编译\n *\n * 调用方(`loadRouteModule` / `loadWsHandler`)在 import 产物之前调用此函数,确保产物已生成。\n *\n * 依赖闭包:handler 引用的项目内共享模块(相对 import + tsconfig paths 别名)一并编译,\n * 跳过产物已新鲜的文件(watcher 已编译或此前请求已编译),只补缺口。\n *\n * mtime 缓存(阶段 4):\n * 1. 内存 Set 命中 → 跳过(最快路径)\n * 2. 产物存在且 mtime ≥ 源码 mtime → 跳过(复用已有产物,如 watcher 已编译)\n * 3. 产物不存在或 stale → 编译(含 stale 的依赖闭包文件) → 加入内存 Set\n *\n * 并发去重(mutex):\n * - 同一 sourceAbsPath 的并发请求共享同一 in-flight Promise\n * - 第二个请求 await 后返回 false(表示「别的请求已触发编译,本次不需要再触发」)\n *\n * @param sourceAbsPath 源码 .ts 绝对路径\n * @param rootDir 项目根目录\n * @param dist 产物目录(dev 模式为 '.faapi')\n * @returns 是否实际触发了编译(false 表示已编译过或产物已最新或源文件不存在)\n * @throws 编译失败时抛错(带原始 cause),调用方可据此给出精确错误信息\n */\nexport async function ensureCompiled(\n sourceAbsPath: string,\n rootDir: string,\n dist: string,\n): Promise<boolean> {\n // mutex: 同一文件正在被别的请求编译 → 等待并返回 false\n const inFlight = state.inFlightCompilations.get(sourceAbsPath);\n if (inFlight) {\n await inFlight.catch(() => {\n // 别的请求编译失败时不在这里抛——让本请求按正常流程自己重试\n });\n return false;\n }\n\n // 内存缓存命中:跳过\n if (state.compiledFiles.has(sourceAbsPath)) {\n return false;\n }\n\n // 源文件不存在:无法编译\n if (!fs.existsSync(sourceAbsPath)) {\n return false;\n }\n\n // mtime 缓存:产物已存在且最新 → 复用(watcher 已编译过的情况)\n const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);\n if (productPath && isProductFresh(sourceAbsPath, productPath)) {\n state.compiledFiles.add(sourceAbsPath);\n return false;\n }\n\n // 触发编译:注册 in-flight Promise 防止并发重复编译\n const compilePromise = (async () => {\n // 依赖闭包:入口本身 + src 内全部传递依赖;产物已新鲜的文件跳过(只补缺口)\n const { insideFiles } = await collectRelativeImports([sourceAbsPath], rootDir);\n const files = [sourceAbsPath];\n for (const dep of insideFiles) {\n if (state.compiledFiles.has(dep)) continue;\n const depProduct = prodSourcePathToProductPath(dep, rootDir, dist);\n if (depProduct && isProductFresh(dep, depProduct)) continue;\n files.push(dep);\n }\n\n await compileDevRoutes({\n rootDir,\n dist,\n files,\n logLevel: 'silent',\n });\n for (const file of files) {\n state.compiledFiles.add(file);\n }\n state.compiledFiles.add(sourceAbsPath);\n })();\n\n state.inFlightCompilations.set(sourceAbsPath, compilePromise);\n try {\n await compilePromise;\n return true;\n } finally {\n state.inFlightCompilations.delete(sourceAbsPath);\n }\n}\n\n/**\n * 确保目录中间件文件已编译(dev 按需模式)\n *\n * middlewarePaths 是产物绝对路径(`.faapi/api/users/middlewares.js`),dev 按需模式下\n * 产物在首次请求前不存在——若不先编译,`loadMergedMiddlewares` import 即\n * ERR_MODULE_NOT_FOUND。通过 `prodPathToSourcePath` 反推源码路径后走 `ensureCompiled`\n * (含依赖闭包)。prod 模式(按需开关关闭)直接返回——产物由 build 固化。\n *\n * @param middlewarePaths 中间件产物绝对路径列表(scanRoutes 收集)\n * @param rootDir 项目根目录\n */\nexport async function ensureMiddlewaresCompiled(\n middlewarePaths: string[],\n rootDir: string,\n): Promise<void> {\n if (!isDevOnDemandEnabled() || middlewarePaths.length === 0) return;\n const dist = getDevDist();\n if (!dist) return;\n\n for (const mwPath of middlewarePaths) {\n const sourcePath = prodPathToSourcePath(mwPath, rootDir, dist);\n try {\n await ensureCompiled(sourcePath, rootDir, dist);\n } catch (err) {\n // 中间件编译失败不阻断请求——loadMiddlewaresFile 对 import 失败已有\n // console.error + 空 bundle 降级语义(鉴权失效由 onError 感知),此处保持一致\n console.error(`[faapi] Failed to compile middleware source ${sourcePath}:`, err);\n }\n }\n}\n\n/**\n * 从源码绝对路径推算产物绝对路径\n *\n * 源码 `<rootDir>/src/api/hello/handler.ts` → 产物 `<rootDir>/<dist>/api/hello/handler.js`\n */\nfunction prodSourcePathToProductPath(\n sourceAbsPath: string,\n rootDir: string,\n dist: string,\n): string | null {\n const rel = path.relative(rootDir, sourceAbsPath).replace(/\\\\/g, '/');\n if (!rel.startsWith('src/')) return null;\n const relWithoutSrc = rel.slice(4); // 去掉 src/\n const jsRel = relWithoutSrc.replace(/\\.ts$/, '.js');\n return path.resolve(rootDir, dist, jsRel);\n}\n\n// ─── zod.js 按需生成 ────────────────────────────────────────────────\n\n/**\n * 清空所有 schema 生成缓存(reloadRoutes 时调用)\n *\n * 同时清空 in-flight mutex Map。\n */\nexport function clearGeneratedSchemas(): void {\n state.generatedSchemas.clear();\n state.inFlightSchemaGenerations.clear();\n}\n\n/**\n * 确保 zod.js 已生成,未生成则触发单文件 schema 生成\n *\n * 调用方(createServer)在 validateInput 之前调用此函数。\n *\n * route.filePath 在 dev/prod 模式下均为产物路径(如 '.faapi/api/hello/handler.js'),\n * 需通过 prodPathToSourcePath 反推源码 .ts 路径用于 AST 分析和 mtime 比较。\n *\n * mtime 缓存(阶段 4):\n * 1. 内存 Set 命中 → 跳过\n * 2. zod.js 存在且 mtime ≥ 源码 mtime → 跳过(复用已有产物)\n * 3. zod.js 不存在或 stale → 生成 → 加入内存 Set\n *\n * 并发去重(mutex):\n * - 同一 schemaPath 的并发请求共享同一 in-flight Promise\n * - 第二个请求 await 后返回 false\n *\n * @param schemaPath zod.js 绝对路径\n * @param routeFilePath route.filePath(产物路径,如 '.faapi/api/hello/handler.js')\n * @param routes 完整路由清单(用于过滤同文件的所有方法)\n * @param rootDir 项目根目录\n * @param dist 产物目录\n * @returns 是否实际触发了生成(false 表示已生成过或产物已最新或源文件不存在)\n * @throws schema 生成失败时抛错(带原始 cause),由 createServer 错误处理链接管\n */\nexport async function ensureSchemaGenerated(\n schemaPath: string,\n routeFilePath: string,\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<boolean> {\n // mutex: 同一 schemaPath 正在被别的请求生成 → 等待并返回 false\n const inFlight = state.inFlightSchemaGenerations.get(schemaPath);\n if (inFlight) {\n await inFlight.catch(() => {\n // 别的请求生成失败时不在这里抛——让本请求按正常流程自己重试\n });\n return false;\n }\n\n // 内存缓存命中:跳过\n if (state.generatedSchemas.has(schemaPath)) {\n return false;\n }\n\n // route.filePath 是产物路径,反推源码绝对路径(用于 mtime 比较和 AST 分析)\n const prodAbsPath = path.resolve(rootDir, routeFilePath);\n const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);\n if (!fs.existsSync(sourceAbsPath)) {\n return false;\n }\n\n // mtime 缓存:zod.js 已存在且最新 → 复用\n if (isProductFresh(sourceAbsPath, schemaPath)) {\n state.generatedSchemas.add(schemaPath);\n return false;\n }\n\n // 过滤同文件的所有路由(一个 handler.ts 可能有 GET/POST/WS 多个方法)\n const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);\n if (fileRoutes.length === 0) {\n return false;\n }\n\n // 把产物 filePath 转为源码 filePath(generateSchemaFiles → collectRouteSchemaSources\n // 需要源码 .ts 路径做 AST 分析)\n const sourceRelPath = path.relative(rootDir, sourceAbsPath).replace(/\\\\/g, '/');\n const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));\n\n // 触发生成:注册 in-flight Promise 防止并发重复生成\n const generatePromise = (async () => {\n // 生成失败时抛错(不吞错误),由 createServer 错误处理链接管\n await generateSchemaFiles(sourceRoutes, rootDir, dist);\n state.generatedSchemas.add(schemaPath);\n })();\n\n state.inFlightSchemaGenerations.set(schemaPath, generatePromise);\n try {\n await generatePromise;\n return true;\n } finally {\n state.inFlightSchemaGenerations.delete(schemaPath);\n }\n}\n\n/**\n * 删除指定路由清单对应的所有 zod.js 文件(reloadRoutes 时调用)\n *\n * watcher 文件变化后,旧 zod.js 可能 stale(类型引用变化等)。\n * 删除后下次请求触发 ensureSchemaGenerated 重新生成。\n *\n * 仅删除文件,不删除目录(faapi-helpers.js 保留,内容确定性不变)。\n */\nexport async function deleteSchemaFiles(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<void> {\n const deleted = new Set<string>();\n for (const route of routes) {\n const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);\n if (deleted.has(schemaPath)) continue;\n deleted.add(schemaPath);\n try {\n await fs.promises.unlink(schemaPath);\n } catch {\n // 文件不存在:忽略\n }\n }\n}\n\n// ─── 路径转换 ────────────────────────────────────────────────────────\n\n/**\n * 从产物绝对路径反推源码绝对路径(带映射缓存)\n *\n * 源码 `<rootDir>/src/api/hello/handler.ts` → 产物 `<rootDir>/<dist>/api/hello/handler.js`\n *\n * dev 按需模式下**每请求**都会调此函数(loadRouteModule / ensureSchemaGenerated),\n * 缓存映射后稳态请求零 fs 访问。映射仅取决于源文件的存在性——源文件新建/删除\n * 通过 `clearCompiledFiles()`(reloadRoutes 时调用)清缓存。\n */\nconst sourcePathCache = new Map<string, string>();\n\nexport function prodPathToSourcePath(prodAbsPath: string, rootDir: string, dist: string): string {\n const cached = sourcePathCache.get(prodAbsPath);\n if (cached) return cached;\n\n const rel = path.relative(rootDir, prodAbsPath).replace(/\\\\/g, '/');\n // 去掉 <dist>/ 前缀\n let relWithoutDist = rel;\n if (relWithoutDist.startsWith(`${dist}/`)) {\n relWithoutDist = relWithoutDist.slice(dist.length + 1);\n }\n // 加 src/ 前缀\n const srcRel = `src/${relWithoutDist}`;\n // .js → .ts(.ts 不存在时回退 .js)\n const tsRel = srcRel.replace(/\\.js$/, '.ts');\n const tsAbs = path.resolve(rootDir, tsRel);\n let result: string;\n if (fs.existsSync(tsAbs)) {\n result = tsAbs;\n } else {\n // .ts 不存在,保持 .js(极少见,可能是用户直接放 .js 源码)\n result = path.resolve(rootDir, srcRel);\n }\n sourcePathCache.set(prodAbsPath, result);\n return result;\n}\n\n// ─── Dev 按需模式标记 ────────────────────────────────────────────────\n\n/**\n * Dev 按需编译模式标记\n *\n * 由 devCommand 在按需模式启动时设置为 true,loadRouteModule / loadWsHandler /\n * createServer 通过此标记判断是否启用按需编译/schema 生成回退。\n *\n * prod 模式(node dist/main)始终为 false——产物在 build 阶段已固化,import 失败即报错。\n */\nexport function setDevOnDemandEnabled(enabled: boolean): void {\n state.enabled = enabled;\n}\n\nexport function isDevOnDemandEnabled(): boolean {\n return state.enabled;\n}\n\n/**\n * Dev 模式产物目录(仅 devOnDemandEnabled 时使用)\n *\n * 由 devCommand 设置,loadRouteModule / loadWsHandler / createServer 通过 getDevDist() 读取。\n */\nexport function setDevDist(dist: string): void {\n state.distDir = dist;\n}\n\nexport function getDevDist(): string | undefined {\n return state.distDir;\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport fg from 'fast-glob';\nimport { buildAliasPlugins } from './aliasPlugin';\nimport { APP_DIR } from '../utils/prodPaths';\n\n/**\n * 编译结果\n */\nexport interface CompileResult {\n /** 已编译的文件列表(绝对路径) */\n compiledFiles: string[];\n}\n\nexport interface CompileSourceFilesOptions {\n /** 项目根目录 */\n rootDir: string;\n /** 输出目录(dev 为 `.faapi`,build 为 `dist`) */\n dist: string;\n /**\n * 增量编译:传入要编译的文件列表(绝对路径)。\n * 不传则全量编译 src 下所有 .ts(排除测试文件和声明文件)。\n */\n files?: string[];\n /** 是否输出 esbuild 日志(默认静默) */\n logLevel?: 'silent' | 'info';\n /**\n * prod 语义:`define` 把 `process.env.NODE_ENV` 编译期替换为 `\"production\"` +\n * `minifySyntax` 删除死分支(两者在 `bundle: false` 下均生效,单文件级别优化)。\n */\n production?: boolean;\n /**\n * dev 语义:原子写(write: false + 临时文件 + rename),避免 watch 模式下\n * esbuild 非原子写期间运行时 import 读到半成品产物。\n */\n atomicWrite?: boolean;\n}\n\n/**\n * dev/prod 共用的逐文件 TypeScript 编译实现\n *\n * 每个 `.ts` 独立编译为 `.js`,不分析 import 关系(`bundle: false`——\n * 保证 config 与 routes 共享同一运行时对象,`instanceof` 跨边界生效)。\n * 产物**打平 src/ 前缀**:`src/api/hello/handler.ts` → `<dist>/api/hello/handler.js`。\n *\n * 别名在编译时重写为相对路径,运行时无需 loader。\n *\n * 通过 `compileDevRoutes` / `compileBuildRoutes` 调用,见 ./compileSourceFiles.md\n * 的选项矩阵。\n */\nexport async function compileSourceFiles(\n options: CompileSourceFilesOptions,\n): Promise<CompileResult> {\n const { rootDir, dist, files, logLevel = 'silent', production, atomicWrite } = options;\n\n // 收集要编译的文件:files 优先,否则全量扫描 src 下所有 .ts\n const entryPoints =\n files ??\n (await fg([`${APP_DIR}/**/*.ts`], {\n cwd: rootDir,\n onlyFiles: true,\n absolute: true,\n ignore: ['**/*.test.ts', '**/*.e2e.test.ts', '**/*.d.ts'],\n }));\n\n if (entryPoints.length === 0) {\n return { compiledFiles: [] };\n }\n\n // 确保输出目录存在\n const absDist = path.resolve(rootDir, dist);\n await fs.promises.mkdir(absDist, { recursive: true });\n\n // 构造别名重写插件(无 tsconfig/paths 时为空,相对路径重写仍生效)\n const plugins = buildAliasPlugins(rootDir);\n\n // 逐文件编译,outbase 设为 src 以打平产物结构:\n // `src/api/hello/handler.ts` → `<dist>/api/hello/handler.js`(去掉 src/ 前缀)\n const esbuild = await import('esbuild');\n const outbase = path.resolve(rootDir, APP_DIR);\n const result = await esbuild.build({\n entryPoints,\n outdir: absDist,\n outbase,\n bundle: false,\n platform: 'node',\n format: 'esm',\n sourcemap: true,\n packages: 'external',\n plugins,\n // build 语义:编译期 NODE_ENV 替换 + 死分支删除(见 AGENTS.md §5.3)\n ...(production\n ? { define: { 'process.env.NODE_ENV': '\"production\"' }, minifySyntax: true }\n : {}),\n logLevel,\n // dev 语义:esbuild 返回内存内容,由下方原子写落盘\n ...(atomicWrite ? { write: false as const } : {}),\n });\n\n // 原子写:逐个写临时文件 + rename。rename 在同一文件系统上是原子的(POSIX),\n // HTTP 请求要么看到旧文件要么看到新文件\n if (atomicWrite && result.outputFiles) {\n await Promise.all(\n result.outputFiles.map(async (file) => {\n await fs.promises.mkdir(path.dirname(file.path), { recursive: true });\n const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n await fs.promises.writeFile(tmp, file.contents);\n await fs.promises.rename(tmp, file.path);\n }),\n );\n }\n\n return { compiledFiles: entryPoints };\n}\n\n/**\n * 全量扫描 src 下的 .ts 文件(排除测试文件和声明文件)\n *\n * 与 compileSourceFiles 的全量分支使用同一 ignore 规则,供 watch 等场景独立扫描。\n */\nexport async function collectSourceFiles(rootDir: string): Promise<string[]> {\n return fg([`${APP_DIR}/**/*.ts`], {\n cwd: rootDir,\n onlyFiles: true,\n absolute: true,\n ignore: ['**/*.test.ts', '**/*.e2e.test.ts', '**/*.d.ts'],\n });\n}\n","import type { Plugin } from 'esbuild';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { resolveAlias } from '../utils/resolveAlias';\nimport { APP_DIR, isInsideDir, toProdExtension, toRealPath } from '../utils/prodPaths';\nimport { readTsconfig, type TsconfigPathsConfig } from '../utils/readTsconfig';\n\n// 兼容 re-export:toProdExtension 此前由本模块导出\nexport { toProdExtension };\n\n/**\n * 把候选源文件路径转为产物 import 路径(相对 importer,POSIX 风格,带 .js 后缀)\n */\nfunction toProdImportPath(sourceFile: string, importer: string): string {\n const importerDir = path.dirname(importer);\n let rel = path.relative(importerDir, sourceFile);\n rel = rel.split(path.sep).join('/');\n if (!rel.startsWith('.')) rel = './' + rel;\n return toProdExtension(rel);\n}\n\n/**\n * 把 src 下的源文件路径转为剥离 src/ 前缀的产物路径(相对 dist 根,POSIX 风格,带 .js 后缀)\n *\n * 用于 src 外 importer(config 文件、本地插件等,位于 rootDir)引用 src 内模块的场景:\n * - 源文件 `<rootDir>/src/lib/errors.ts` → 产物 `dist/lib/errors.js` → 相对 dist 根 `./lib/errors.js`\n *\n * 与 `toProdImportPath` 的区别:后者相对 importer 目录(适用于 importer 也在 src 内的场景,\n * outbase 打平后相对结构不变);本函数相对 src 根(适用于 importer 在 src 外的场景,\n * 需要剥离 src/ 前缀以匹配 compileDevRoutes 的打平产物结构)。\n *\n * 内部用 `toRealPath` 规范化 appDirAbs,兼容 macOS 符号链接(esbuild onLoad 传入的\n * args.path 已是 realpath,未规范化的 appDirAbs 会导致前缀比较失败)。\n */\nfunction toStrippedProdImportPath(sourceFile: string, rootDir: string): string {\n const appDirAbs = toRealPath(path.resolve(rootDir, APP_DIR));\n const sourceReal = toRealPath(sourceFile);\n let rel = path.relative(appDirAbs, sourceReal);\n rel = rel.split(path.sep).join('/');\n if (!rel.startsWith('.')) rel = './' + rel;\n return toProdExtension(rel);\n}\n\n/**\n * 把「相对 dist 根」的产物路径改写为「相对 importer 产物位置」的 import 路径\n *\n * src 外 importer 的产物位置 = importer 源文件相对 rootDir 的路径(.js 后缀),\n * 因为 src 外编译(compileConfig 步骤 1a / compileProjectModules)以 rootDir 为\n * outbase,产物在 `<dist>/` 下保留相对结构。\n *\n * - config `faapi.config.ts` → 产物 `faapi.config.js`(dist 根)→ `./lib/errors.js` 保持不变\n * - 插件 `plugins/db-skills.ts` → 产物 `plugins/db-skills.js`(dist 子目录)\n * → 引用 `./lib/errors.js` 需改写为 `../lib/errors.js`(相对 dist 根的路径在\n * 子目录模块里会解析到 `plugins/lib/errors.js`,错位)\n */\nfunction toProdImportFromImporter(importer: string, rootDir: string, relFromDist: string): string {\n // 两侧 realpath 归一化,兼容 macOS 符号链接(esbuild onLoad 传入的 importer 已是\n // realpath,rootDir 可能仍是 /var 形式——不归一化则 relative 产生绕行路径)\n const importerRel = path\n .relative(toRealPath(path.resolve(rootDir)), toRealPath(importer))\n .split(path.sep)\n .join('/');\n const importerProdDir = path.posix.dirname(toProdExtension(importerRel));\n if (importerProdDir === '.') return relFromDist;\n const target = relFromDist.replace(/^\\.\\//, '');\n let rel = path.posix.relative(importerProdDir, target);\n if (!rel.startsWith('.')) rel = './' + rel;\n return rel;\n}\n\n/**\n * aliasPlugin 扩展选项\n *\n * - `rootDir`:启用 src/ 前缀剥离。当 importer 不在 src 下(如 config 文件)\n * 且引用的源文件在 src 下时,重写为剥离前缀的产物路径(相对 dist 根),以匹配\n * `compileDevRoutes`/`compileBuildRoutes` 的打平产物结构。\n */\nexport interface AliasPluginOptions {\n /** 项目根目录(启用剥离时必填) */\n rootDir?: string;\n}\n\n/**\n * 产物后缀集合(已带这些后缀的 specifier 视为产物路径,不重写)\n */\nconst PROD_EXTS = ['.js', '.mjs', '.cjs'];\n\n/**\n * 源文件后缀集合(重写时尝试这些后缀定位实际文件)\n */\nconst SOURCE_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];\n\n/**\n * index 文件后缀集合(specifier 指向目录时尝试)\n */\nconst INDEX_EXTS = [\n '/index.ts',\n '/index.tsx',\n '/index.js',\n '/index.jsx',\n '/index.mjs',\n '/index.cjs',\n];\n\n/**\n * 解析相对 specifier 到实际源文件绝对路径\n *\n * - 已带产物后缀(.js/.mjs/.cjs)且文件存在 → 返回该路径(不重写)\n * - 已带源后缀(.ts/.tsx/.jsx)且文件存在 → 返回该路径(重写为 .js)\n * - 无后缀 → 尝试各源后缀 + index 文件\n *\n * @param importer 导入文件绝对路径\n * @param specifier 相对 specifier(./xxx 或 ../xxx)\n * @returns 解析到的源文件绝对路径,或 null(未解析到)\n */\nexport function resolveRelativeSpecifier(importer: string, specifier: string): string | null {\n const importerDir = path.dirname(importer);\n const base = path.resolve(importerDir, specifier);\n\n // 已带产物后缀:文件存在则返回(视为产物路径,不重写)\n if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {\n return fs.existsSync(base) ? base : null;\n }\n\n // 已带源后缀:文件存在则返回(将重写为产物后缀)\n if (/\\.(ts|tsx|jsx)$/.test(specifier)) {\n return fs.existsSync(base) ? base : null;\n }\n\n // 无后缀:尝试各源后缀\n for (const ext of SOURCE_EXTS) {\n const file = base + ext;\n if (fs.existsSync(file)) return file;\n }\n\n // 无后缀:尝试 index 文件\n for (const indexExt of INDEX_EXTS) {\n const file = base + indexExt;\n if (fs.existsSync(file)) return file;\n }\n\n return null;\n}\n\n/**\n * esbuild specifier 重写插件(dev/build/config 编译共用)\n *\n * 在 `onLoad` 阶段把源码 import/export 中的 specifier 重写为产物相对路径(带 `.js` 后缀):\n *\n * 1. **相对 specifier**(`./xxx`、`../xxx`):解析到实际源文件,重写为产物相对路径。\n * - 无后缀 → 解析 + 加 `.js`\n * - `.ts`/`.tsx`/`.jsx` → 改为 `.js`\n * - `.js`/`.mjs`/`.cjs` → 不处理(视为产物路径)\n * - 解析失败(文件不存在)→ 原样保留\n *\n * 2. **别名 specifier**(tsconfig.paths,如 `@/xxx`):调 `resolveAlias` 解析候选路径,\n * 命中则重写为产物相对路径。\n *\n * `bundle: false` 模式下 esbuild 不递归解析依赖(`onResolve` 不触发),specifier 会原样\n * 保留到产物 `.js`,运行时 Node.js ESM loader 无法解析(无后缀推断)。本插件通过\n * `onLoad` 介入,确保产物中所有相对 import 都带 `.js` 后缀。\n *\n * **src/ 前缀剥离**(可选,通过 `options.rootDir` 启用):\n * 当 importer 不在 src 下(如 `faapi.config.ts` 位于 rootDir)且引用的源文件在 src 下时,\n * 重写为剥离 src/ 前缀的产物路径(相对 dist 根),以匹配 `compileDevRoutes`/`compileBuildRoutes`\n * 的打平产物结构。典型场景:config 引用项目模块(如 `./src/lib/errors` → `./lib/errors.js`),\n * 使 config 与 routes 共享同一份模块产物,`instanceof` 跨边界生效。\n *\n * 覆盖的 import 形式:\n * - `import { x } from './base'` / `import { x } from '@/base'`\n * - `export { x } from './base'`(含 `from`,被同一正则匹配)\n * - `import('./base')`(动态)\n *\n * 绝对路径(`/`)、`file:` URL、`node:` 协议不处理,交 esbuild 默认。\n */\nexport function createAliasPlugin(\n config: TsconfigPathsConfig,\n options?: AliasPluginOptions,\n): Plugin {\n // 匹配 from '...' / from \"...\" 和 import('...') / import(\"...\")\n const SPEC_RE = /(\\bfrom\\s*|import\\s*\\(\\s*)(['\"])([^'\"]+)\\2/g;\n // 预计算 src 绝对路径(realpath 规范化,用于判断 importer 和 resolved 是否在 src 内)\n // 规范化是为了兼容 macOS 符号链接:esbuild onLoad 传入的 args.path 是 realpath,\n // 未规范化的 appDirAbs 会导致 isInsideDir 比较失败(/var/folders vs /private/var/folders)\n const appDirAbs = options?.rootDir ? toRealPath(path.resolve(options.rootDir, APP_DIR)) : null;\n return {\n name: 'faapi-alias',\n setup(build) {\n build.onLoad({ filter: /\\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {\n let source: string;\n try {\n source = fs.readFileSync(args.path, 'utf8');\n } catch {\n return undefined;\n }\n const importer = args.path;\n // importer 是否在 src 外(启用剥离时才计算,用 isInsideDir 兼容符号链接)\n const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;\n let modified = false;\n const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {\n // 绝对路径 / file: URL / node: 协议不处理\n if (\n specifier.startsWith('/') ||\n specifier.startsWith('file:') ||\n specifier.startsWith('node:')\n ) {\n return full;\n }\n\n // 相对 specifier(./ 或 ../):解析到实际文件并重写\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n const resolved = resolveRelativeSpecifier(importer, specifier);\n if (resolved) {\n // 已带产物后缀的 specifier 不重写(resolveRelativeSpecifier 返回原路径)\n if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {\n return full;\n }\n // src 前缀剥离:importer 在 src 外、resolved 在 src 内\n // → 重写为剥离前缀的产物路径(相对 importer 产物位置)\n if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {\n modified = true;\n return `${prefix}${quote}${toProdImportFromImporter(\n importer,\n options!.rootDir!,\n toStrippedProdImportPath(resolved, options!.rootDir!),\n )}${quote}`;\n }\n modified = true;\n return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;\n }\n return full;\n }\n\n // 别名 specifier:调 resolveAlias 解析\n const candidates = resolveAlias(specifier, config);\n for (const candidate of candidates) {\n for (const ext of SOURCE_EXTS) {\n const file = candidate + ext;\n if (fs.existsSync(file)) {\n modified = true;\n // 别名解析到的文件如果在 src 内且 importer 在 src 外,同样剥离前缀\n if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {\n return `${prefix}${quote}${toProdImportFromImporter(\n importer,\n options!.rootDir!,\n toStrippedProdImportPath(file, options!.rootDir!),\n )}${quote}`;\n }\n return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;\n }\n }\n for (const indexExt of INDEX_EXTS) {\n const file = candidate + indexExt;\n if (fs.existsSync(file)) {\n modified = true;\n if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {\n return `${prefix}${quote}${toProdImportFromImporter(\n importer,\n options!.rootDir!,\n toStrippedProdImportPath(file, options!.rootDir!),\n )}${quote}`;\n }\n return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;\n }\n }\n }\n return full;\n });\n if (!modified) return undefined;\n return { contents: newSource, loader: 'default' };\n });\n },\n };\n}\n\n/**\n * 读取 tsconfig paths 并构造 specifier 重写插件\n *\n * 始终返回含本插件的数组:相对路径重写不依赖 tsconfig(`bundle: false` 下 Node ESM 必需),\n * 别名重写依赖 tsconfig.paths(无 paths 时别名不重写)。\n *\n * @param rootDir 项目根目录。传入时启用 src/ 前缀剥离:importer 在 src 外\n * 且引用 src 内文件时,重写为剥离前缀的产物路径。主要用于 compileConfig\n * 编译 config 文件(位于 rootDir)引用项目模块(位于 src)的场景。\n */\nexport function buildAliasPlugins(rootDir: string): Plugin[] {\n const tsconfig = readTsconfig(rootDir);\n return [createAliasPlugin(tsconfig ?? { baseUrl: '.', paths: {} }, { rootDir })];\n}\n","import type { TsconfigPathsConfig } from './readTsconfig';\n\n/**\n * 按 tsconfig paths 配置把 import specifier 解析为候选绝对路径\n *\n * 纯函数,不检查文件存在性。调用方需自行尝试 .ts/.js/index 等后缀。\n * 支持精确匹配(无 *)和单个 * 通配匹配。\n */\nexport function resolveAlias(specifier: string, config: TsconfigPathsConfig): string[] {\n const candidates: string[] = [];\n\n for (const [pattern, targets] of Object.entries(config.paths)) {\n const wildcardIndex = pattern.indexOf('*');\n\n if (wildcardIndex === -1) {\n // 精确匹配\n if (specifier === pattern) {\n candidates.push(...targets);\n }\n continue;\n }\n\n // 通配匹配:按 * 分割为 prefix / suffix\n const prefix = pattern.slice(0, wildcardIndex);\n const suffix = pattern.slice(wildcardIndex + 1);\n\n if (\n specifier.startsWith(prefix) &&\n specifier.endsWith(suffix) &&\n specifier.length >= prefix.length + suffix.length\n ) {\n const captured = specifier.slice(prefix.length, specifier.length - suffix.length);\n for (const target of targets) {\n candidates.push(target.replace('*', captured));\n }\n }\n }\n\n return candidates;\n}\n","import ts from 'typescript';\nimport path from 'node:path';\nimport fs from 'node:fs';\n\n/**\n * tsconfig paths 别名配置(已规范化为绝对路径)\n *\n * - baseUrl:绝对路径(无 baseUrl 时为 tsconfig 所在目录)\n * - paths:pattern -> 绝对路径数组(保留 * 通配符,供 resolveAlias 替换)\n */\nexport interface TsconfigPathsConfig {\n baseUrl: string;\n paths: Record<string, string[]>;\n}\n\n/**\n * 读取项目 tsconfig.json,提取 baseUrl + paths,规范化为绝对路径配置\n *\n * 用 TypeScript Compiler API 解析(处理注释、extends 合并)。\n * 仅读取 rootDir 下的 tsconfig.json,不向上递归查找。\n * 无 paths 时返回 null。\n *\n * 按 tsconfig mtime 缓存:每次编译(watcher 每轮重建、每次首请求按需编译)都会\n * 调本函数,Compiler API 的 readConfigFile + extends 链合并在长 extends 链项目上\n * 开销可观。tsconfig 变化时 mtime 变化 → 缓存自动失效,无需手动清理。\n */\ninterface TsconfigCacheEntry {\n mtimeMs: number;\n config: TsconfigPathsConfig | null;\n}\nconst tsconfigCache = new Map<string, TsconfigCacheEntry>();\n\nexport function readTsconfig(rootDir: string): TsconfigPathsConfig | null {\n const tsconfigPath = path.resolve(rootDir, 'tsconfig.json');\n if (!fs.existsSync(tsconfigPath)) return null;\n\n // mtime 缓存:tsconfig 未变化时直接复用解析结果\n let mtimeMs: number;\n try {\n mtimeMs = fs.statSync(tsconfigPath).mtimeMs;\n } catch {\n return null;\n }\n const cached = tsconfigCache.get(tsconfigPath);\n if (cached && cached.mtimeMs === mtimeMs) {\n return cached.config;\n }\n\n const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n if (configFile.error || !configFile.config) return null;\n\n // parseJsonConfigFileContent 处理 extends 合并、解析 baseUrl 为绝对路径\n const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, rootDir);\n\n // baseUrl:parseJsonConfigFileContent 已解析为绝对路径;未设置时回退到 rootDir\n const baseUrl = parsed.options.baseUrl ?? rootDir;\n\n const rawPaths = parsed.options.paths;\n const config: TsconfigPathsConfig | null = rawPaths\n ? (() => {\n // paths 目标:原始字符串相对 baseUrl,解析为绝对路径(保留 *)\n const paths: Record<string, string[]> = {};\n for (const [pattern, targets] of Object.entries(rawPaths)) {\n paths[pattern] = targets.map((t) => path.resolve(baseUrl, t));\n }\n return { baseUrl, paths };\n })()\n : null;\n\n tsconfigCache.set(tsconfigPath, { mtimeMs, config });\n return config;\n}\n","import { compileSourceFiles, collectSourceFiles, type CompileResult } from './compileSourceFiles';\n\nexport { collectSourceFiles };\nexport type { CompileResult };\n\n/**\n * dev 编译选项\n */\nexport interface CompileDevOptions {\n /** 项目根目录 */\n rootDir: string;\n /** 输出目录(dev 模式为 `.faapi`) */\n dist: string;\n /**\n * 增量编译:传入要编译的文件列表(绝对路径)。\n * 不传则全量编译 src 下所有 .ts(排除测试文件和声明文件)。\n */\n files?: string[];\n /** 是否输出 esbuild 日志(dev 模式默认静默) */\n logLevel?: 'silent' | 'info';\n}\n\n/**\n * dev 模式编译 TypeScript:逐文件编译,启动快、增量编译友好\n *\n * 共享实现见 [compileSourceFiles](./compileSourceFiles.ts);dev 差异仅两点:\n * 不传 `define`(`process.env.NODE_ENV` 运行时读取环境变量,便于热替换)+\n * **原子写**(写临时文件 + rename,避免 watch 模式下运行时 import 读到半成品产物,\n * 详见 compileDevRoutes.md 的\"dev 原子写\"章节)。\n *\n * @example\n * // 全量编译\n * await compileDevRoutes({ rootDir, dist: '.faapi' });\n * // watch 增量:只编译变化的文件\n * await compileDevRoutes({ rootDir, dist: '.faapi', files: changedFiles });\n */\nexport async function compileDevRoutes(options: CompileDevOptions): Promise<CompileResult> {\n return compileSourceFiles({ ...options, atomicWrite: true });\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport { isInsideDir, toRealPath } from '../utils/prodPaths';\nimport { resolveRelativeSpecifier } from './aliasPlugin';\nimport { resolveAlias } from '../utils/resolveAlias';\nimport { readTsconfig } from '../utils/readTsconfig';\n\n/**\n * 匹配 from '...' / from \"...\" / import('...') / import(\"...\") 中的 specifier\n */\nconst SPEC_RE = /(\\bfrom\\s*|import\\s*\\(\\s*)(['\"])([^'\"]+)\\2/g;\n\n/**\n * 从源文件内容中提取所有 import specifier\n *\n * 覆盖相对 specifier(./xxx、../xxx)与 tsconfig paths 别名 specifier(如 @/xxx)。\n * 动态 import(import('...'))同样覆盖。\n */\nfunction extractImportSpecifiers(source: string): string[] {\n const specifiers: string[] = [];\n let match: RegExpExecArray | null;\n SPEC_RE.lastIndex = 0;\n while ((match = SPEC_RE.exec(source)) !== null) {\n specifiers.push(match[3]!);\n }\n return specifiers;\n}\n\n/**\n * 从目录解析 specifier 为源文件绝对路径(探测 .ts/.tsx 等源后缀与 index 文件)\n *\n * 复用 resolveRelativeSpecifier 的探测逻辑:它按 importer 文件所在目录解析,\n * 传入目录内的合成文件名即可从指定目录解析。\n */\nfunction resolveSpecifierFromDir(dir: string, specifier: string): string | null {\n return resolveRelativeSpecifier(path.join(dir, '__faapi_probe__.ts'), specifier);\n}\n\n/**\n * 递归收集入口文件的所有项目内 import 依赖(含传递依赖),按是否位于 appDir 内分组\n *\n * 供两类调用方使用:\n * - `compileConfig`:编译 config 源 + 其引用的项目模块,使 config 产物的 import\n * 在运行时能解析到实际文件(与 routes 编译的同一产物共享,instanceof 跨边界生效)\n * - `compileOnDemand.ensureCompiled`:dev 按需编译 handler 时补齐依赖闭包,\n * 否则 handler 引用的共享模块(如 `../../lib/db`)产物不存在,首次请求即\n * ERR_MODULE_NOT_FOUND\n *\n * 解析规则:\n * - 相对 specifier:`resolveRelativeSpecifier` 探测源后缀与 index 文件\n * - 别名 specifier(tsconfig paths,如 `@/lib/db`):`resolveAlias` 解析候选目标后\n * 同样探测源后缀(无 tsconfig/paths 时跳过)\n * - 已带产物后缀(.js/.mjs/.cjs)的 specifier 不递归(视为已编译产物,源码不在此处)\n * - 项目外依赖(node_modules、rootDir 外文件)不收集——按 external 语义运行时解析\n *\n * @param entryFiles 起始文件(绝对路径)\n * @param rootDir 项目根目录\n * @returns 收集到的文件,分为 src 内(打平前缀编译)和 src 外(保留结构编译)两组\n * (绝对路径,去重,不含入口文件本身)\n */\nexport async function collectRelativeImports(\n entryFiles: string[],\n rootDir: string,\n): Promise<{ insideFiles: string[]; outsideFiles: string[] }> {\n // 用 realpath 规范化 src 绝对路径,兼容 macOS 符号链接(esbuild 传入的路径已是 realpath)\n const appDirAbs = toRealPath(path.resolve(rootDir, 'src'));\n const tsconfig = readTsconfig(rootDir);\n const visited = new Set<string>();\n const insideFiles = new Set<string>();\n const outsideFiles = new Set<string>();\n\n async function collect(filePath: string): Promise<void> {\n if (visited.has(filePath)) return;\n visited.add(filePath);\n\n let source: string;\n try {\n source = await fs.promises.readFile(filePath, 'utf8');\n } catch {\n return;\n }\n\n for (const specifier of extractImportSpecifiers(source)) {\n // 已带产物后缀:运行时 import 的就是产物(或第三方 .js),源码不递归\n if (/\\.(js|mjs|cjs)$/.test(specifier)) continue;\n\n let resolved: string | null;\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n resolved = resolveRelativeSpecifier(filePath, specifier);\n } else if (tsconfig) {\n // 别名 specifier:解析候选目标(相对 rootDir)后探测源文件\n resolved = null;\n for (const candidate of resolveAlias(specifier, tsconfig)) {\n const probed = resolveSpecifierFromDir(rootDir, candidate);\n if (probed) {\n resolved = probed;\n break;\n }\n }\n } else {\n resolved = null;\n }\n\n if (!resolved) continue;\n // 项目外文件(node_modules / rootDir 外)不收集。\n // 归类判断用 realpath 归一化两侧(tmpdir 符号链接 /var → /private/var 会误判),\n // 但收集结果保留原始解析路径——esbuild 的 outbase 基于调用方传入的 rootDir 形式,\n // entry 路径必须与 outbase 字符串前缀一致,否则产物落入 _.._ 垃圾目录\n if (!isInsideDir(toRealPath(resolved), toRealPath(path.resolve(rootDir)))) continue;\n\n if (isInsideDir(toRealPath(resolved), appDirAbs)) {\n insideFiles.add(resolved);\n } else {\n outsideFiles.add(resolved);\n }\n await collect(resolved);\n }\n }\n\n for (const entry of entryFiles) {\n await collect(entry);\n }\n\n return {\n insideFiles: Array.from(insideFiles),\n outsideFiles: Array.from(outsideFiles),\n };\n}\n","import { getSchemaName } from './schemaName';\nimport { InternalError } from '../errors/httpErrors';\nimport type { ValidationIssue, ValidationErrorCode } from '../errors/httpErrors';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport { isDevOnDemandEnabled } from '../cli/compileOnDemand';\n\n/**\n * 校验结果类型\n *\n * data 为校验/解析后的原始值(通常为对象;数组 body 为数组,\n * 顶层 primitive body——如 `type POSTBody = string`——为对应原始值)。\n */\nexport interface ValidationResult {\n valid: boolean;\n issues: ValidationIssue[];\n data: unknown;\n}\n\n/**\n * zod.js 模块导出格式\n *\n * 每个 handler 目录下的 zod.js 导出:\n * - `<SchemaName>Schema`:zod schema 对象(用于 safeParse 校验)\n *\n * query/params 的 schema 已在代码生成阶段用 z.preprocess 包裹了字符串转换逻辑\n * (number/boolean 字段),无需运行时再做 coerce。\n *\n * 无类型声明的方法不导出对应 Schema(值为 undefined)。\n */\ninterface SchemaModule {\n [key: string]: unknown;\n}\n\n/**\n * schema 模块缓存:schemaPath → SchemaModule\n *\n * dev 模式下 watch 触发 reloadRoutes 时通过 invalidateSchemaCache() 清空,\n * 下次请求重新 import(importWithCacheBust 会拼接时间戳绕过 ESM 缓存)。\n */\nconst moduleCache = new Map<string, SchemaModule>();\n\n/**\n * 清空 schema 模块缓存(dev watch 模式下文件变化时调用)\n */\nexport function invalidateSchemaCache(): void {\n moduleCache.clear();\n}\n\n/**\n * 加载 schema 模块(带缓存)\n *\n * 首次调用时 import zod.js 并缓存,后续直接返回缓存。\n * dev 模式下 watch 触发 invalidateSchemaCache 后,下次调用重新 import。\n */\nasync function loadSchemaModule(schemaPath: string): Promise<SchemaModule> {\n let mod = moduleCache.get(schemaPath);\n if (!mod) {\n // dev 按需模式下走 Node 原生 import 绕过 Vite SSR 缓存\n // (zod.js 是已编译产物,不需要 Vite alias 解析)\n mod = (await importWithCacheBust(schemaPath, isDevOnDemandEnabled())) as SchemaModule;\n moduleCache.set(schemaPath, mod);\n }\n return mod;\n}\n\n/**\n * 校验输入参数\n *\n * 流程:从 schemaPath import zod.js → zod safeParse\n *\n * schema 来源由调用方确保已生成:\n * - dev 模式:createApp 启动时 + watch 时调 generateSchemaFiles 生成 zod.js\n * - prd 模式:faapi build 时调 generateSchemaFiles 生成 zod.js\n *\n * coerce 说明:\n * - query/params 的 schema 在代码生成阶段已用 z.preprocess 包裹字符串转换逻辑\n * (number: \"1\" → 1,boolean: \"true\" → true),运行时直接 safeParse 即可\n * - body 是 JSON 解析的天然 JS 类型,schema 不含 preprocess\n *\n * 三种状态:\n * - Schema 导出存在:执行 zod safeParse 校验\n * - Schema 导出 undefined(无类型声明):跳过校验\n * - zod.js 文件不存在或 import 失败:抛 InternalError\n *\n * @param schemaPath zod.js 文件绝对路径\n * @param method HTTP 方法\n * @param inputType 输入类型\n * @param input 输入值\n */\nexport async function validateInput(\n schemaPath: string,\n method: string,\n inputType: 'query' | 'body' | 'params',\n input: unknown,\n): Promise<ValidationResult> {\n const schemaName = getSchemaName(method, inputType);\n const schemaKey = `${schemaName}Schema`;\n\n let mod: SchemaModule;\n try {\n mod = await loadSchemaModule(schemaPath);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new InternalError(`Schema 模块加载失败: ${schemaPath}: ${reason}`);\n }\n\n const schema = mod[schemaKey];\n\n // 无类型声明:跳过校验,input 原样透传(数组/原始值 body 不再静默替换为 {})\n if (schema === undefined || schema === null) {\n return { valid: true, issues: [], data: input };\n }\n\n // schema 必须是带 safeParse 的 zod schema\n if (\n typeof schema !== 'object' ||\n typeof (schema as { safeParse?: unknown }).safeParse !== 'function'\n ) {\n throw new InternalError(`Schema 不是有效的 zod schema: ${schemaPath}#${schemaName}`);\n }\n\n // zod safeParse 校验:input 原样传入(schema 生成端支持数组/顶层 primitive body,\n // 预先替换数组会使命中 z.array 的合法 body 永远失败且 issue 误导为 received object)\n // query/params 的 preprocess 已在 schema 生成阶段内联\n const zodSchema = schema as { safeParse: (v: unknown) => ZodSafeParseResult };\n const result = zodSchema.safeParse(input);\n\n if (result.success) {\n // 解析后的数据原样返回(z.array schema 的 data 是数组,顶层 primitive 是原始值)\n return { valid: true, issues: [], data: result.data };\n }\n\n // 将 zod issues 转为 ValidationIssue\n const issues = mapZodIssues(result.error);\n return { valid: false, issues, data: input };\n}\n\n/**\n * zod safeParse 成功结果\n */\ninterface ZodSafeParseSuccess {\n success: true;\n data: unknown;\n}\n\n/**\n * zod safeParse 失败结果\n */\ninterface ZodSafeParseError {\n success: false;\n error: {\n issues: Array<{\n path: (string | number)[];\n code: string;\n expected?: string;\n received?: string;\n message: string;\n }>;\n };\n}\n\ntype ZodSafeParseResult = ZodSafeParseSuccess | ZodSafeParseError;\n\n/**\n * 将 zod v4 error issues 映射为框架的 ValidationIssue\n *\n * zod code → 框架 ValidationErrorCode 映射:\n * - invalid_type(received 非 undefined) / invalid_union → TYPE_MISMATCH(422)\n * - invalid_type(received === 'undefined',即缺失必填字段)→ MISSING_FIELD(400)\n * - unrecognized_keys → INVALID_FORMAT(400)\n * - invalid_value / invalid_format(v4 的 email/url/uuid/regex 等格式检查)/\n * invalid_key / invalid_element(v4 的 record/map 元素检查)/\n * too_small / too_big / invalid_intersection_types / not_multiple_of / custom\n * → INVALID_VALUE(422)\n *\n * path 数组转为 dot 路径(如 ['user', 'address', 'city'] → 'user.address.city')。\n */\nfunction mapZodIssues(error: ZodSafeParseError['error']): ValidationIssue[] {\n return error.issues.map((issue) => {\n const code = mapZodCode(issue);\n const path = issue.path.map(String).join('.') || '';\n return {\n path,\n code,\n expected: issue.expected ?? mapExpectedFromMessage(issue.message),\n received: issue.received ?? mapReceivedFromMessage(issue.message),\n message: issue.message,\n };\n });\n}\n\ninterface ZodIssueLike {\n code: string;\n expected?: string;\n received?: string;\n message: string;\n}\n\n/**\n * 映射 zod v4 issue code 到框架 ValidationErrorCode\n *\n * zod v4 已移除 invalid_string(v3 遗留),格式类检查(email/url/uuid/regex)\n * 产出 invalid_format;record/map 的键/元素检查产出 invalid_key / invalid_element。\n */\nfunction mapZodCode(issue: ZodIssueLike): ValidationErrorCode {\n switch (issue.code) {\n case 'invalid_type':\n // zod v4 对缺失必填字段产出 invalid_type,无独立 missing code;\n // received 字段缺失,\"received undefined\" 只出现在 message 中\n if (issue.received === 'undefined' || /received undefined/i.test(issue.message)) {\n return 'MISSING_FIELD';\n }\n return 'TYPE_MISMATCH';\n case 'invalid_union':\n case 'invalid_union_discriminator':\n return 'TYPE_MISMATCH';\n case 'unrecognized_keys':\n return 'INVALID_FORMAT';\n case 'invalid_value':\n case 'invalid_format':\n case 'invalid_key':\n case 'invalid_element':\n case 'too_small':\n case 'too_big':\n case 'invalid_intersection_types':\n case 'not_multiple_of':\n case 'custom':\n return 'INVALID_VALUE';\n default:\n return 'INVALID_VALUE';\n }\n}\n\n/**\n * 从 zod message 中提取期望类型(兜底)\n */\nfunction mapExpectedFromMessage(message: string): string {\n // zod v3 message 示例:\"Expected string, received number\"\n const match = message.match(/Expected\\s+(\\w+)/i);\n return match ? match[1]!.toLowerCase() : 'unknown';\n}\n\n/**\n * 从 zod message 中提取实际类型(兜底)\n */\nfunction mapReceivedFromMessage(message: string): string {\n const match = message.match(/received\\s+(\\w+)/i);\n return match ? match[1]!.toLowerCase() : 'unknown';\n}\n","import {\n createServer as createHttpServer,\n type Server,\n type IncomingMessage,\n type ServerResponse,\n} from 'node:http';\nimport { createSecureServer as createHttp2SecureServer } from 'node:http2';\nimport { readFileSync } from 'node:fs';\nimport { Readable } from 'node:stream';\nimport path from 'node:path';\nimport type { RouteManifest, RouteMatch, WsRouteManifest, RoutesRef } from '../router/routeTypes';\nimport { matchRoute, findAllowedMethods } from '../router/matchRoute';\nimport { loadRouteModule } from '../loader/loadRouteModule';\nimport { createContextFromUrl } from '../runtime/createContext';\nimport { resolveInputFromUrl, resolveBodyForQueryMethod } from '../runtime/resolveInput';\nimport { invokeHandler, compose, mergeMeta } from '../runtime/invokeHandler';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { sendNodeResponse } from '../response/sendNodeResponse';\nimport {\n RouteNotFoundError,\n MethodNotAllowedError,\n ValidationError,\n PayloadTooLargeError,\n} from '../errors/httpErrors';\nimport { validateInput } from '../validator/validateInput';\nimport { getInputTypeForMethod, hasBody } from '../runtime/inputType';\nimport { getClientIp } from '../utils/getClientIp';\nimport { cors, type CorsOptions } from '../middleware/cors';\nimport { helmet, type HelmetOptions } from '../middleware/helmet';\nimport { compression, type CompressionOptions } from '../middleware/compression';\nimport { etag, type EtagOptions } from '../middleware/etag';\nimport type { AppRegistries } from '../injection/registries';\nimport { logger as loggerMiddleware, type LoggerOptions } from '../middleware/logger';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { attachWebSocket } from './handleWsUpgrade';\nimport { nodeHttpToWebHeaders, buildErrorResponse } from './serverUtils';\nimport { getRuntimeSchemaPath } from '../cli/generateSchemaFiles';\nimport {\n ensureSchemaGenerated,\n ensureMiddlewaresCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n} from '../cli/compileOnDemand';\nimport { loadMergedMiddlewares } from '../middleware/loadMiddlewares';\n\n/**\n * 将 Node.js IncomingMessage 转为 Web Request 对象\n *\n * 协议判断:\n * 1. 优先使用 X-Forwarded-Proto 头(反向代理场景)\n * 2. 回退到 http(HTTPS 由外部代理处理)\n */\nconst DEFAULT_BODY_LIMIT = 10 * 1024 * 1024; // 10MB\n\nfunction toWebRequest(\n req: IncomingMessage,\n bodyLimit: number = DEFAULT_BODY_LIMIT,\n requestSignal?: AbortSignal,\n): {\n request: Request;\n /** 已解析的 URL(pathname/searchParams 由调用方复用,免重复 new URL) */\n url: URL;\n} {\n // 协议判断:优先 X-Forwarded-Proto(反向代理),否则 http\n const forwardedProto = req.headers['x-forwarded-proto'];\n const protocol = Array.isArray(forwardedProto)\n ? (forwardedProto[0]?.split(',')[0]?.trim() ?? 'http')\n : (forwardedProto?.split(',')[0]?.trim() ?? 'http');\n const host = req.headers.host ?? 'localhost';\n // 全请求唯一一次 URL 解析——pathname/searchParams 由 ctx/routePipeline 复用\n const url = new URL(req.url ?? '/', `${protocol}://${host}`);\n\n const headers = nodeHttpToWebHeaders(req);\n\n const method = req.method ?? 'GET';\n\n // GET/HEAD 不应该有 body\n if (method === 'GET' || method === 'HEAD') {\n return {\n request: new Request(url.toString(), { method, headers, signal: requestSignal }),\n url,\n };\n }\n\n // content-length 快速判定:声明长度超限直接抛 PayloadTooLargeError,\n // 免去流包装 + 逐 chunk 读取(chunked 无此头,仍走 limitStreamSize 流式限流)\n const contentLength = req.headers['content-length'];\n if (contentLength !== undefined) {\n const declared = Number(Array.isArray(contentLength) ? contentLength[0] : contentLength);\n if (Number.isFinite(declared) && declared > bodyLimit) {\n throw new PayloadTooLargeError(bodyLimit);\n }\n }\n\n // 将 Node.js IncomingMessage 转为 Web ReadableStream\n // 并限制请求体大小(防止 DoS)\n const stream = Readable.toWeb(req) as ReadableStream<Uint8Array>;\n const limitedStream = limitStreamSize(stream, bodyLimit);\n return {\n request: new Request(url.toString(), {\n method,\n headers,\n body: limitedStream,\n duplex: 'half',\n signal: requestSignal,\n } as RequestInit),\n url,\n };\n}\n\n/**\n * 限制 ReadableStream 的总字节数,超过限制时通过 controller.error 抛 PayloadTooLargeError\n *\n * 错误传播路径:controller.error → Request body 读取方(resolveInput)抛 →\n * handleRequest catch → formatErrorResponse(命中 PayloadTooLargeError 分支)→ 413 响应\n *\n * 健壮性处理:\n * - reader.read() 抛错(客户端断开等)→ controller.error + releaseLock,避免泄漏\n * - 超限或异常后释放 reader lock,避免悬挂引用\n * - 取消时同步 cancel 上游 reader\n */\nfunction limitStreamSize(\n stream: ReadableStream<Uint8Array>,\n maxSize: number,\n): ReadableStream<Uint8Array> {\n let totalSize = 0;\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n let errored = false;\n\n const releaseReader = (): void => {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // 锁已释放或 reader 已 closed,忽略\n }\n reader = undefined;\n }\n };\n\n const failStream = (\n controller: ReadableStreamDefaultController<Uint8Array>,\n err: unknown,\n ): void => {\n if (errored) return;\n errored = true;\n controller.error(err instanceof Error ? err : new Error(String(err)));\n releaseReader();\n };\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (!reader) reader = stream.getReader();\n try {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n releaseReader();\n return;\n }\n totalSize += value.byteLength;\n if (totalSize > maxSize) {\n // error 让流进入 errored 状态,下游 read() 会 reject\n failStream(controller, new PayloadTooLargeError(maxSize));\n return;\n }\n controller.enqueue(value);\n } catch (err) {\n // reader.read() 抛错(客户端断开、底层流异常等)\n failStream(controller, err);\n }\n },\n cancel(reason) {\n if (reader) {\n try {\n reader.cancel(reason);\n } catch {\n // 忽略上游 cancel 失败\n }\n releaseReader();\n }\n },\n });\n}\n\n/**\n * 查找路径的所有允许方法(用于 405 响应)\n *\n * 实现移至 [matchRoute](../router/matchRoute.ts)(共享路由索引:静态段 O(1) 直查,\n * 仅动态段线性扫描)。404 非热路径,但扫描器/探活探测的 404 高频场景下\n * 索引化仍有收益。\n */\n\nexport interface CreateServerOptions {\n routes: RouteManifest;\n rootDir: string;\n /** 产物输出目录(如 '.faapi' 或 'dist'),用于计算 schema 路径 */\n dist: string;\n cors?: CorsOptions | boolean;\n /** 请求错误钩子(在错误响应生成后调用,用于副作用;不修改已发出的响应) */\n onError?: (error: unknown, ctx: FaapiContext) => Promise<void> | void;\n /** 自定义业务配置(来自 faapi.config.ts,注入到 ctx.config) */\n config?: Record<string, unknown>;\n /** WebSocket 路由清单(空数组则不挂载 WS 支持) */\n wsRoutes?: WsRouteManifest;\n /** 全局中间件(来自 faapi.config.ts,对所有路由生效,最外层) */\n middlewares?: FaapiMiddleware[];\n /** 全局注入器(来自 faapi.config.ts,对所有路由 handler 参数注入生效) */\n injectors?: InjectorMap;\n /** 安全头配置 */\n helmet?: HelmetOptions | boolean;\n /** 响应压缩(gzip/deflate/br 协商),默认关闭,详见 middleware/compression.md */\n compression?: CompressionOptions | boolean;\n /** ETag/304 条件请求协商,默认关闭,详见 middleware/etag.md */\n etag?: EtagOptions | boolean;\n /** app 级注册表(tool/agent/skill/agentHandle)——经 FaapiContext 进入请求链路 */\n registries?: AppRegistries;\n /** 请求日志配置,默认启用(与 cors 一致) */\n logger?: LoggerOptions | boolean;\n /** 请求体大小限制(字节) */\n bodyLimit?: number;\n /** HTTP/2 配置,启用时需提供 SSL 证书路径 */\n http2?: Http2Options | boolean;\n /**\n * 是否信任反向代理头(X-Forwarded-For),默认 false\n *\n * true 时 `ctx.ip` 取 `x-forwarded-for` 第一个 IP(nginx/CDN 场景);\n * false(默认)时直取 socket 地址——直连部署下 XFF 可被伪造,安全默认不信任。\n */\n trustedProxy?: boolean;\n}\n\nexport interface Http2Options {\n key?: string;\n cert?: string;\n}\n\n/**\n * 创建 faapi HTTP server\n *\n * @param options 路由清单、根目录\n * @returns Node.js Server 实例\n */\nexport function createServer(options: CreateServerOptions): {\n server: Server;\n routesRef: RoutesRef;\n} {\n const {\n routes,\n rootDir,\n dist,\n cors: corsOption,\n onError,\n config,\n wsRoutes,\n middlewares: globalMiddlewares,\n injectors: globalInjectors,\n helmet: helmetOption,\n compression: compressionOption,\n etag: etagOption,\n registries,\n logger: loggerOption,\n bodyLimit = DEFAULT_BODY_LIMIT,\n http2: http2Option,\n trustedProxy = false,\n } = options;\n\n // 路由可变引用容器(watch 模式热替换时 reloadRoutes 更新 .current/.wsCurrent)\n const routesRef: RoutesRef = { current: routes, wsCurrent: wsRoutes ?? [] };\n\n // Build middleware chain from config options\n const configMiddlewares: FaapiMiddleware[] = [];\n\n // Compression — 显式启用;链最外层,包住完整链路使最终响应被压缩\n if (compressionOption) {\n const compOpts = typeof compressionOption === 'object' ? compressionOption : {};\n configMiddlewares.push(compression(compOpts));\n }\n\n // CORS\n const corsMiddleware: FaapiMiddleware | null =\n corsOption === false\n ? null\n : corsOption === true || corsOption === undefined\n ? cors()\n : cors(corsOption);\n if (corsMiddleware) configMiddlewares.push(corsMiddleware);\n\n // Helmet — enabled only when explicitly configured\n if (helmetOption) {\n const helmOpts = typeof helmetOption === 'object' ? helmetOption : {};\n configMiddlewares.push(helmet(helmOpts));\n }\n\n // Logger — 默认启用(与 cors 一致),false 禁用,LoggerOptions 自定义\n const loggerMiddlewareInst: FaapiMiddleware | null =\n loggerOption === false\n ? null\n : loggerOption === true || loggerOption === undefined\n ? loggerMiddleware()\n : loggerMiddleware(loggerOption);\n if (loggerMiddlewareInst) configMiddlewares.push(loggerMiddlewareInst);\n\n // ETag — 显式启用;位于 compression 内层:先算 ETag/304 再压缩,\n // 弱 ETag 基于未压缩表示计算(304 无 body 时压缩自动跳过)\n if (etagOption) {\n const etagOpts = typeof etagOption === 'object' ? etagOption : {};\n configMiddlewares.push(etag(etagOpts));\n }\n\n // 外层中间件链启动期组装一次(CORS → helmet → logger → 全局),\n // 每请求不再重复 spread 重组数组\n const outerMiddlewares: FaapiMiddleware[] = [...configMiddlewares];\n if (globalMiddlewares && globalMiddlewares.length > 0) {\n outerMiddlewares.push(...globalMiddlewares);\n }\n\n const server = ((): Server => {\n if (http2Option) {\n const h2Opts = typeof http2Option === 'object' ? http2Option : {};\n return createHttp2SecureServer({\n key: h2Opts.key ? readFileSync(h2Opts.key) : undefined,\n cert: h2Opts.cert ? readFileSync(h2Opts.cert) : undefined,\n allowHTTP1: true,\n }) as unknown as Server;\n }\n return createHttpServer();\n })();\n\n server.on('request', (req: IncomingMessage, res: ServerResponse) => {\n // 每次请求读取最新的路由状态(支持 watch 模式热更新)\n const currentRoutes = routesRef.current;\n\n handleRequest(\n currentRoutes,\n rootDir,\n dist,\n req,\n res,\n outerMiddlewares,\n onError,\n config,\n globalInjectors,\n bodyLimit,\n trustedProxy,\n registries,\n ).catch(() => {\n res.statusCode = 500;\n res.end();\n });\n });\n\n // 挂载 WebSocket 升级处理(无条件挂载:upgrade 处理器内部对无匹配路由返回 404,\n // 空清单成本一次函数调用。若按初始清单条件挂载,dev watch 中新增第一个 WS 路由后\n // upgrade 监听器不会补挂,WS 路由永远 404)\n attachWebSocket({\n server,\n routesRef,\n rootDir,\n config,\n globalMiddlewares,\n trustedProxy,\n registries,\n });\n\n return { server, routesRef };\n}\n\n/**\n * 准备请求上下文:Node IncomingMessage → Web Request + FaapiContext\n *\n * 提取为独立函数,让 handleRequest 主流程聚焦于路由 + 中间件调度,\n * 便于单测与未来扩展(如自定义 context 字段来源)。\n *\n * URL 全请求只解析一次(toWebRequest 内),pathname/searchParams\n * 由 ctx / routePipeline 共享,避免一次请求重复 new URL 3~4 次。\n */\nfunction prepareRequest(\n req: IncomingMessage,\n config: Record<string, unknown> | undefined,\n bodyLimit: number,\n trustedProxy: boolean,\n registries?: AppRegistries,\n requestSignal?: AbortSignal,\n): {\n request: Request;\n url: URL;\n ctx: FaapiContext;\n meta: ResponseMeta;\n method: string;\n urlPath: string;\n} {\n const { request, url } = toWebRequest(req, bodyLimit, requestSignal);\n const method = request.method.toUpperCase();\n const urlPath = url.pathname;\n const ctx = createContextFromUrl(\n request,\n url,\n {},\n config,\n getClientIp(req, trustedProxy),\n registries,\n );\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n return { request, url, ctx, meta, method, urlPath };\n}\n\n/**\n * 路由匹配——命中返回 MatchResult,未命中抛 RouteNotFoundError / MethodNotAllowedError\n *\n * 抛错语义让调用方 try/catch 即可,无需在主流程里分支处理 null。\n */\nfunction resolveRouteOrThrow(routes: RouteManifest, method: string, urlPath: string): RouteMatch {\n const match = matchRoute(routes, method, urlPath);\n if (match) return match;\n\n // 检查是否有其他方法匹配该路径 → 405\n const allowedMethods = findAllowedMethods(routes, urlPath);\n if (allowedMethods.length > 0) {\n throw new MethodNotAllowedError(method, urlPath, allowedMethods);\n }\n\n // 路由未匹配 → 404\n throw new RouteNotFoundError(urlPath);\n}\n\n/**\n * 路由执行管线:作为外层中间件链的 finalHandler\n *\n * 包含:路由匹配 → handler.js 加载 → 参数解析 → zod.js 按需生成 + 校验 →\n * 中间件按需加载 → 注入器合并 → handler 调用。\n *\n * 错误抛出由外层 handleRequest 的 try/catch 接管,经 formatErrorResponse 转换为响应。\n */\n// 路由静态派生路径缓存(绝对路径 / schema 路径对固定 route 恒定,免每请求字符串运算)。\n// WeakMap 按 route 对象弱键——reloadRoutes 整体替换清单后旧对象可 GC,无需手动失效\nconst routePathCache = new WeakMap<\n RouteManifest[number],\n { absFilePath: string; schemaPath: string }\n>();\n\nfunction getRoutePaths(\n route: RouteManifest[number],\n rootDir: string,\n dist: string,\n): { absFilePath: string; schemaPath: string } {\n let cached = routePathCache.get(route);\n if (!cached) {\n cached = {\n absFilePath: path.resolve(rootDir, route.filePath),\n schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir),\n };\n routePathCache.set(route, cached);\n }\n return cached;\n}\n\nfunction createRoutePipeline(opts: {\n routes: RouteManifest;\n method: string;\n urlPath: string;\n url: URL;\n ctx: FaapiContext;\n request: Request;\n rootDir: string;\n dist: string;\n globalInjectors: InjectorMap | undefined;\n}): () => Promise<Response> {\n const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;\n return async () => {\n // 1. 路由匹配(未命中抛 RouteNotFound / MethodNotAllowed)\n const match = resolveRouteOrThrow(routes, method, urlPath);\n ctx.params = match.params;\n const { route } = match;\n const { absFilePath, schemaPath } = getRoutePaths(route, rootDir, dist);\n\n // 2. 加载 handler.js(dev 按需编译 + import,prod 直接 import)\n const routeModule = await loadRouteModule(absFilePath, route.method, rootDir);\n\n // 3. 参数解析(query / body / form / files 等)——复用已解析的 URL\n const input = await resolveInputFromUrl(route.method, request, url);\n\n // 4. schema 校验(运行时按 route.filePath 计算 zod.js 路径 + safeParse)\n const inputType = getInputTypeForMethod(route.method);\n // schemaPath 已在 getRoutePaths 中缓存\n\n // Dev 按需模式:zod.js 不存在或 stale 时触发按需生成\n if (isDevOnDemandEnabled()) {\n const devDist = getDevDist();\n if (devDist) {\n await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist);\n }\n }\n\n const result = await validateInput(schemaPath, route.method, inputType, input);\n if (!result.valid) {\n throw new ValidationError('参数校验失败', result.issues);\n }\n // body 计算与主输入分流:\n // - POST/PUT/PATCH:主输入就是 body,用校验后的值\n // - DELETE:主输入是 query(校验 DELETEQuery),body 单独解析注入——\n // 若把校验后的 query 当 body 传入,handler 声明 body 会静默拿到 query;\n // 同时请求体流不被消费,keep-alive 连接无法复用\n // - GET/HEAD:无 body\n const body =\n inputType === 'query' && hasBody(route.method)\n ? await resolveBodyForQueryMethod(request)\n : hasBody(route.method)\n ? result.data\n : undefined;\n\n // 5. 中间件按需加载(Vite 风格):route.middlewares 为 undefined 时从 middlewarePaths 加载\n // 首次请求加载后缓存到 route 上,后续请求直接复用\n if (route.middlewares === undefined && route.injectors === undefined && route.middlewarePaths) {\n // dev 按需模式:先编译中间件源码(含依赖闭包)再 import\n await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);\n const bundle = await loadMergedMiddlewares(route.middlewarePaths);\n if (bundle) {\n route.middlewares = bundle.middlewares;\n route.injectors = bundle.injectors;\n } else {\n // 标记为已加载(空中间件),避免重复加载\n route.middlewares = [];\n route.injectors = {};\n }\n }\n\n // 6. 注入器合并:全局注入器为基线,目录注入器覆盖同名\n const mergedInjectors = globalInjectors\n ? { ...globalInjectors, ...route.injectors }\n : route.injectors;\n\n // 7. handler 调用(含目录中间件洋葱模型 + 自动响应包装)\n return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);\n };\n}\n\n/**\n * 发送成功响应\n */\nasync function sendSuccessResponse(response: Response, res: ServerResponse): Promise<void> {\n await sendNodeResponse(response, res);\n}\n\n/**\n * 发送错误响应 + 触发 onError 副作用\n *\n * 错误处理兜底链(参考 Fastify 语义):\n * 1. 框架内置 formatErrorResponse 兜底(handler 抛错时)——读 ctx.config.response.fail\n * 自定义包装函数,确保错误格式与 ctx.fail() 主动错误响应一致\n * 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n * 3. 响应发出后 → onError 触发副作用(不修改已发出的响应)\n * 注:业务方如需进一步自定义错误响应,在全局中间件中 try/catch next() 即可\n *\n * `ctx` 可为 undefined——请求准备阶段抛错(如 content-length 超限)时尚未构造 ctx,\n * 此时错误格式用默认 fail 包装,onError 不触发(与原\"裸 500\"路径一致)。\n */\nasync function sendErrorResponse(\n err: unknown,\n meta: ResponseMeta,\n res: ServerResponse,\n onError: ((error: unknown, ctx: FaapiContext) => Promise<void> | void) | undefined,\n ctx: FaapiContext | undefined,\n): Promise<void> {\n await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx?.config), meta), res);\n\n // 响应已发出,触发 onError 副作用(日志/告警/链路追踪),自身抛错被忽略\n if (onError && ctx) {\n try {\n await onError(err, ctx);\n } catch {\n // onError 自身抛错不影响已发出的响应\n }\n }\n}\n\nasync function handleRequest(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n req: IncomingMessage,\n res: ServerResponse,\n outerMiddlewares: FaapiMiddleware[],\n onError: ((error: unknown, ctx: FaapiContext) => Promise<void> | void) | undefined,\n config: Record<string, unknown> | undefined,\n globalInjectors: InjectorMap | undefined,\n bodyLimit: number,\n trustedProxy: boolean,\n registries?: AppRegistries,\n): Promise<void> {\n // meta/ctx 兜底:请求准备阶段抛错(如 content-length 超限的 413)时尚无 ctx\n let meta: ResponseMeta = { headers: {}, setCookies: [] };\n let ctx: FaapiContext | undefined;\n // 客户端断连信号:每请求一个 AbortController,signal 进入 Request(ctx.request.signal)\n // res 'close' 在响应正常完成(keep-alive)时也会触发,用 writableEnded 区分——\n // 响应未写完连接就断开(客户端提前断连)才 abort,正常完成不误触发\n const abortController = new AbortController();\n res.on('close', () => {\n if (!res.writableEnded) abortController.abort();\n });\n try {\n // 1. 准备请求上下文(toWebRequest + createContext)——URL 全请求只解析一次\n const prepared = prepareRequest(\n req,\n config,\n bodyLimit,\n trustedProxy,\n registries,\n abortController.signal,\n );\n ctx = prepared.ctx;\n meta = prepared.meta;\n const { request, url, method, urlPath } = prepared;\n\n // 2. 创建路由执行管线(路由匹配 + 校验 + 中间件加载 + handler 调用)\n const routePipeline = createRoutePipeline({\n routes,\n method,\n urlPath,\n url,\n ctx,\n request,\n rootDir,\n dist,\n globalInjectors,\n });\n\n // 3. 执行外层中间件链(CORS → helmet → logger → 全局 → routePipeline,\n // 数组已在 createServer 启动期组装,此处仅按需 compose)\n const response =\n outerMiddlewares.length > 0\n ? await compose(outerMiddlewares, ctx, routePipeline)\n : await routePipeline();\n // 4. 发送响应\n await sendSuccessResponse(response, res);\n } catch (err: unknown) {\n // 客户端已断开或响应已完成:网络中断/连接销毁不是服务端错误,\n // 不向已销毁的连接写 500(写入无效),也不触发 onError 误报\n if (res.destroyed || res.writableEnded) return;\n await sendErrorResponse(err, meta, res, onError, ctx);\n }\n}\n","import type { RouteManifest, RouteMatch, WsRouteManifest, WsRouteMatch } from './routeTypes';\n\n/**\n * 路由匹配索引——把 O(n) 线性扫描降为静态路由 O(1) 命中\n *\n * 每个请求都要做路由匹配,原实现逐条遍历清单(静态路由字符串比较、动态路由\n * split('/') 比对),大清单下是热路径开销。索引把清单分为两部分:\n * - **静态路由**:`method|urlPath` → route 的 Map,一次查找命中\n * - **动态路由**:保持清单顺序的数组(`sortRoutes` 保证静态恒在动态之前,\n * 且同一 `(method, path)` 至多一条静态路由,静态优先查与原 first-match\n * 语义完全等价)\n *\n * 索引按**清单数组身份**缓存(WeakMap):`reloadRoutes` / `hydrateRoutes`\n * 整体替换清单数组(`routesRef.current = newRoutes`),旧数组被替换后索引\n * 自动失效可被 GC,无需手动清理——无生命周期侵入。\n *\n * 详见 [matchRoute.md](./matchRoute.md)。\n */\n\n/**\n * HTTP 路由匹配索引\n */\ninterface HttpRoutesIndex {\n /** 静态路由:`method|urlPath` → route */\n static: Map<string, RouteManifest[number]>;\n /** 静态路径 → 方法集合(405 findAllowedMethods 反查用,免再扫静态段) */\n methodsByStaticPath: Map<string, Set<string>>;\n /** 动态路由(含 catch-all),保持清单顺序,pattern 已预编译 */\n dynamics: DynamicEntry[];\n}\n\n/** 动态路由条目(pattern segments 预编译,免去每请求对固定 pattern 重复 split) */\ninterface DynamicEntry {\n route: RouteManifest[number];\n /** 预编译的模式段(pattern.split('/').filter(Boolean),对固定 route 恒定) */\n segments: string[];\n}\n\n/** WS 路由匹配索引(无 HTTP 方法维度) */\ninterface WsRoutesIndex {\n /** 静态路由:urlPath → route */\n static: Map<string, WsRouteManifest[number]>;\n /** 动态路由(含 catch-all),保持清单顺序 */\n dynamics: WsRouteManifest;\n}\n\nconst httpIndexCache = new WeakMap<RouteManifest, HttpRoutesIndex>();\nconst wsIndexCache = new WeakMap<WsRouteManifest, WsRoutesIndex>();\n\n/** 构建 HTTP 路由索引(惰性,每个清单数组只构建一次) */\nfunction getHttpIndex(routes: RouteManifest): HttpRoutesIndex {\n let index = httpIndexCache.get(routes);\n if (index) return index;\n\n index = { static: new Map(), methodsByStaticPath: new Map(), dynamics: [] };\n for (const route of routes) {\n if (route.isDynamic) {\n index.dynamics.push({\n route,\n segments: route.urlPath.split('/').filter(Boolean),\n });\n } else {\n index.static.set(`${route.method}|${route.urlPath}`, route);\n let methods = index.methodsByStaticPath.get(route.urlPath);\n if (!methods) {\n methods = new Set();\n index.methodsByStaticPath.set(route.urlPath, methods);\n }\n methods.add(route.method);\n }\n }\n\n httpIndexCache.set(routes, index);\n return index;\n}\n\n/** 构建 WS 路由索引(惰性,每个清单数组只构建一次) */\nfunction getWsIndex(routes: WsRouteManifest): WsRoutesIndex {\n let index = wsIndexCache.get(routes);\n if (index) return index;\n\n index = { static: new Map(), dynamics: [] };\n for (const route of routes) {\n if (route.isDynamic) {\n index.dynamics.push(route);\n } else {\n index.static.set(route.urlPath, route);\n }\n }\n\n wsIndexCache.set(routes, index);\n return index;\n}\n\n/**\n * 根据请求路径和方法匹配路由\n *\n * HEAD 回退:无显式 HEAD 路由时复用 GET handler(Node ServerResponse 对 HEAD\n * 自动丢弃 body)。探活、CDN 健康检查、HTTP 客户端预检常用 HEAD——没有回退时\n * 只定义 GET 的路由对 HEAD 返回 405,监控大面积误报。\n *\n * @param routes 已排序的路由清单\n * @param method HTTP 方法\n * @param path 请求路径\n * @returns 匹配结果,包含路由记录和参数;无匹配返回 null\n */\nexport function matchRoute(routes: RouteManifest, method: string, path: string): RouteMatch | null {\n const index = getHttpIndex(routes);\n const upper = method.toUpperCase();\n\n const hit = matchByMethod(index, upper, path);\n if (hit) return hit;\n\n if (upper === 'HEAD') {\n return matchByMethod(index, 'GET', path);\n }\n\n return null;\n}\n\n/** 按方法匹配(静态 O(1) → 动态线性),matchRoute 内部复用(HEAD 回退 GET) */\nfunction matchByMethod(index: HttpRoutesIndex, method: string, path: string): RouteMatch | null {\n // 静态路由 O(1) 命中(sortRoutes 保证静态优先,等价于原遍历的首个命中)\n const staticHit = index.static.get(`${method}|${path}`);\n if (staticHit) {\n return { route: staticHit, params: {} };\n }\n\n // 动态路由按清单顺序匹配(pattern segments 已预编译)\n for (const entry of index.dynamics) {\n const route = entry.route;\n if (route.method !== method) {\n continue;\n }\n const params = matchSegments(entry.segments, path, route.paramNames, route.isCatchAll);\n if (params !== null) {\n return { route, params };\n }\n }\n\n return null;\n}\n\n/**\n * 匹配 WebSocket 路由(无 HTTP 方法维度)\n *\n * WS 路由只按路径匹配,协议升级时调用。\n * @param wsRoutes WebSocket 路由清单\n * @param path 请求路径\n */\nexport function matchWsRoute(wsRoutes: WsRouteManifest, path: string): WsRouteMatch | null {\n const index = getWsIndex(wsRoutes);\n\n const staticHit = index.static.get(path);\n if (staticHit) {\n return { route: staticHit, params: {} };\n }\n\n for (const route of index.dynamics) {\n const params = matchDynamicPath(route.urlPath, path, route.paramNames, route.isCatchAll);\n if (params !== null) {\n return { route, params };\n }\n }\n\n return null;\n}\n\n/**\n * 查找路径的所有允许方法(用于 405 响应)\n *\n * 静态段直接查索引(O(1)),仅动态段线性扫描——原实现对全部路由做\n * O(n) 遍历 + 静态路径比较,404/405 高频场景(扫描器、探活探测)开销显著。\n */\nexport function findAllowedMethods(routes: RouteManifest, path: string): string[] {\n const index = getHttpIndex(routes);\n const methods = new Set<string>();\n\n // 静态路由:索引直查\n const staticMethods = index.methodsByStaticPath.get(path);\n if (staticMethods) {\n for (const method of staticMethods) {\n methods.add(method);\n }\n }\n\n // 动态路由:线性扫描(segments 已预编译)\n for (const entry of index.dynamics) {\n const params = matchSegments(\n entry.segments,\n path,\n entry.route.paramNames,\n entry.route.isCatchAll,\n );\n if (params !== null) {\n methods.add(entry.route.method);\n }\n }\n\n // RFC 9110:GET 允许时 HEAD 必然允许(matchRoute 有 HEAD→GET 回退),\n // 405 的 Allow 头如实反映,避免客户端误判 HEAD 不可用\n if (methods.has('GET')) {\n methods.add('HEAD');\n }\n\n return Array.from(methods);\n}\n\n/**\n * 动态路径匹配\n * 将路由模式(如 /user/:id)与请求路径(如 /user/123)匹配\n * 支持 catch-all 路由(如 /shop/:...slug 匹配 /shop/clothes/tops)\n * 返回提取的参数对象,不匹配返回 null\n */\nexport function matchDynamicPath(\n pattern: string,\n path: string,\n paramNames: string[],\n isCatchAll?: boolean,\n): Record<string, string> | null {\n return matchSegments(pattern.split('/').filter(Boolean), path, paramNames, isCatchAll);\n}\n\n/**\n * 基于**预编译 segments** 的动态匹配(热路径内部用)\n *\n * 与 matchDynamicPath 语义一致,但模式段由索引构建期一次性 split,\n * 免去每请求对固定 pattern 重复字符串分割\n */\nfunction matchSegments(\n patternSegments: string[],\n path: string,\n paramNames: string[],\n isCatchAll?: boolean,\n): Record<string, string> | null {\n const pathSegments = path.split('/').filter(Boolean);\n\n // catch-all 路由:最后一个模式段为 :...slug\n if (isCatchAll) {\n // catch-all 前面的静态/动态段必须匹配\n // catch-all 段至少匹配一个路径段\n const nonCatchAllCount = patternSegments.length - 1;\n if (pathSegments.length <= nonCatchAllCount) {\n return null;\n }\n\n const params: Record<string, string> = {};\n\n for (let i = 0; i < nonCatchAllCount; i++) {\n // i < nonCatchAllCount ≤ 两数组长度,索引必然存在\n const patternSeg = patternSegments[i]!;\n const pathSeg = pathSegments[i]!;\n\n if (patternSeg.startsWith(':')) {\n const paramName = patternSeg.slice(1);\n params[paramName] = pathSeg;\n } else if (patternSeg !== pathSeg) {\n return null;\n }\n }\n\n // catch-all 段:剩余所有路径段用 / 连接\n const catchAllValue = pathSegments.slice(nonCatchAllCount).join('/');\n // 前置检查 pathSegments.length > nonCatchAllCount 保证 catch-all 段存在\n const catchAllParamName = patternSegments[nonCatchAllCount]!.slice(4); // 去掉 ':...'\n params[catchAllParamName] = catchAllValue;\n\n if (Object.keys(params).length !== paramNames.length) {\n return null;\n }\n\n return params;\n }\n\n // 普通动态路由:段数必须一致\n if (patternSegments.length !== pathSegments.length) {\n return null;\n }\n\n const params: Record<string, string> = {};\n\n for (let i = 0; i < patternSegments.length; i++) {\n // 段数一致性检查后索引必然存在\n const patternSeg = patternSegments[i]!;\n const pathSeg = pathSegments[i]!;\n\n if (patternSeg.startsWith(':')) {\n // 动态段,提取参数值\n const paramName = patternSeg.slice(1);\n params[paramName] = pathSeg;\n } else if (patternSeg !== pathSeg) {\n // 静态段不匹配\n return null;\n }\n }\n\n // 确保所有参数名都被提取到\n if (Object.keys(params).length !== paramNames.length) {\n return null;\n }\n\n return params;\n}\n","/**\n * 从模块中解析出指定名称的导出\n *\n * 解析顺序:\n * 1. 具名导出:export function GET() {}\n * 2. 默认导出的对象属性:export default { GET() {} }\n *\n * @param module 动态 import 得到的模块对象\n * @param exportName 导出名(如 'GET')\n */\nexport function resolveExport(module: Record<string, unknown>, exportName: string): unknown {\n // 1. 具名导出\n if (exportName in module && typeof module[exportName] !== 'undefined') {\n return module[exportName];\n }\n\n // 2. 默认导出的对象属性\n const defaultExport = module.default;\n if (defaultExport !== null && typeof defaultExport === 'object') {\n const value = (defaultExport as Record<string, unknown>)[exportName];\n if (value !== undefined) {\n return value;\n }\n }\n\n return undefined;\n}\n","/**\n * 校验导出值是否为合法的 handler 函数\n */\nexport function validateRouteModule(\n value: unknown,\n method: string,\n filePath: string,\n): asserts value is (...args: unknown[]) => unknown {\n if (typeof value !== 'function') {\n throw new Error(\n `Route module \"${filePath}\" does not export a valid handler for method \"${method}\". ` +\n `Expected a function, got ${typeof value}.`,\n );\n }\n}\n","import type { HttpMethod } from '../router/constants';\nimport { resolveExport } from './resolveExports';\nimport { validateRouteModule } from './validateRouteModule';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport {\n ensureCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n prodPathToSourcePath,\n} from '../cli/compileOnDemand';\n\nexport interface RouteModule {\n handler: (...args: unknown[]) => unknown;\n method: HttpMethod;\n}\n\n/**\n * 动态 import 路由文件并提取 handler\n *\n * Dev 按需编译模式(Vite 风格):先确保编译再 import,避免 import 不存在的文件。\n * Prod 模式:产物在 build 阶段已固化,直接 import,失败即报错。\n *\n * 错误传递:\n * - 编译失败 → 抛出编译错误(\"Failed to compile route module\")\n * - import 失败 → 抛出加载错误(\"Failed to load route module\")\n *\n * @param filePath 路由文件的绝对路径\n * @param method HTTP 方法名(也是导出名)\n * @param rootDir 项目根目录(按需编译模式用,可选)\n */\nexport async function loadRouteModule(\n filePath: string,\n method: HttpMethod,\n rootDir?: string,\n): Promise<RouteModule> {\n // Dev 按需模式:先确保编译再 import\n // 避免\"import 失败 → 编译 → 重试 import\"模式——首次 import 不存在的文件会污染\n // Vite SSR 内部状态,导致编译创建文件后重试 import 仍失败(CI Linux 上复现)\n //\n // 不在此处 existsSync(sourcePath):ensureCompiled 内部对缺失源文件直接返回 false,\n // 外层检查是冗余的每请求同步 IO(编译完成一次后内存 Set 命中即短路)\n if (isDevOnDemandEnabled() && rootDir) {\n const dist = getDevDist();\n if (dist) {\n const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);\n if (sourcePath) {\n try {\n await ensureCompiled(sourcePath, rootDir, dist);\n } catch (compileErr) {\n const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);\n throw new Error(`Failed to compile route module \"${sourcePath}\": ${reason}`, {\n cause: compileErr,\n });\n }\n }\n }\n }\n\n // import 模块(dev 按需模式下走 Node 原生 import 绕过 Vite SSR 缓存)\n let module: Record<string, unknown>;\n try {\n module = await importWithCacheBust(filePath, isDevOnDemandEnabled());\n } catch (err: unknown) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to load route module \"${filePath}\": ${reason}`, { cause: err });\n }\n\n const handler = resolveExport(module, method);\n validateRouteModule(handler, method, filePath);\n\n return { handler, method };\n}\n","export type JsonParseResult = { success: true; data: unknown } | { success: false; error: string };\n\nexport function parseJsonBody(text: string): JsonParseResult {\n try {\n const data = JSON.parse(text);\n return { success: true, data };\n } catch {\n return { success: false, error: 'Invalid JSON body' };\n }\n}\n","/**\n * 解析 multipart/form-data 请求\n * 使用 Web 标准 Request.formData() API\n */\n\nexport interface UploadedFile {\n name: string; // 字段名\n filename: string; // 原始文件名\n type: string; // MIME 类型\n size: number; // 文件大小(字节)\n arrayBuffer: () => Promise<ArrayBuffer>; // 获取文件内容\n}\n\nexport interface MultipartResult {\n /** 表单字段(同名字段用数组收集,单值保持 string) */\n fields: Record<string, string | string[]>;\n files: UploadedFile[];\n}\n\n/**\n * 解析 multipart/form-data\n *\n * 同名字段的处理:\n * - 第一次出现:string\n * - 第二次出现:string[](包含所有同名字段的值)\n * - 文件始终收集到 files 数组(通过 name 字段标识归属)\n */\nexport async function parseMultipart(request: Request): Promise<MultipartResult> {\n const formData = await request.formData();\n const fields: Record<string, string | string[]> = {};\n const files: UploadedFile[] = [];\n\n for (const [key, value] of formData.entries()) {\n if (value instanceof File) {\n files.push({\n name: key,\n filename: value.name,\n type: value.type,\n size: value.size,\n arrayBuffer: () => value.arrayBuffer(),\n });\n } else {\n // 同名字段收集为数组\n if (key in fields) {\n const existing = fields[key];\n if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n // in 检查已证明存在\n fields[key] = [existing!, value];\n }\n } else {\n fields[key] = value;\n }\n }\n }\n\n return { fields, files };\n}\n","import { queryToObject } from '../utils/queryToObject';\nimport { parseJsonBody } from '../utils/parseJsonBody';\nimport { parseMultipart } from '../utils/parseMultipart';\nimport { getInputTypeForMethod } from './inputType';\nimport { ValidationError } from '../errors/httpErrors';\n\n/**\n * 根据 HTTP 方法解析主输入\n *\n * - GET / DELETE / HEAD:主输入是 query(URL 查询参数)\n * - POST / PUT / PATCH:主输入是 body(请求体)\n *\n * 注意:DELETE 也可能有 body,但主输入(用于校验)是 query。\n * body 由 createServer 调 resolveBodyForQueryMethod 单独解析后注入\n * (同时消费请求体流,保证 keep-alive 连接可复用)。\n *\n * HTTP 传输语义:\n * - 空请求体(或纯空白)视为无 body,返回 null(handler 可不声明 body 参数)\n * - 请求体非空但 JSON 格式非法,抛 ValidationError(code=INVALID_FORMAT),\n * 不再静默返回 null 导致后续报\"字段缺失\"\n *\n * @param method HTTP 方法\n * @param request Request 对象\n * @returns 解析后的输入值(query 对象或 body 对象)\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveInput(method: string, request: Request): Promise<unknown> {\n return resolveInputFromUrl(method, request, new URL(request.url));\n}\n\n/**\n * resolveInput 的热路径变体:URL 由调用方解析一次后传入\n *\n * createServer 每请求已持有解析好的 URL(pathname/searchParams 复用),\n * 通过此变体避免 query 分支的重复 `new URL(request.url)`。\n */\nexport async function resolveInputFromUrl(\n method: string,\n request: Request,\n url: URL,\n): Promise<unknown> {\n const inputType = getInputTypeForMethod(method);\n\n // 主输入是 body 的方法:解析请求体\n if (inputType === 'body') {\n const contentType = request.headers.get('content-type') ?? '';\n\n // multipart/form-data:字段 + 文件\n if (contentType.includes('multipart/form-data')) {\n return parseMultipart(request);\n }\n\n // application/x-www-form-urlencoded:表单字段\n if (contentType.includes('application/x-www-form-urlencoded')) {\n const text = await request.text();\n if (isBlankText(text)) return null;\n const params = new URLSearchParams(text);\n const obj: Record<string, string> = {};\n for (const [key, value] of params) {\n obj[key] = value;\n }\n return obj;\n }\n\n // 默认按 JSON 解析(application/json 及其它未明确类型)\n const text = await request.text();\n\n // 空请求体:视为无 body(handler 可不声明 body 参数)\n if (isBlankText(text)) {\n return null;\n }\n\n // 非空请求体:必须能解析为 JSON,否则是格式错误\n const result = parseJsonBody(text);\n if (!result.success) {\n throw new ValidationError('请求体不是合法的 JSON', [\n {\n path: 'body',\n code: 'INVALID_FORMAT',\n expected: 'JSON',\n received: 'text',\n message: '请求体不是合法的 JSON',\n },\n ]);\n }\n return result.data;\n }\n\n // 主输入是 query 的方法(GET / DELETE / HEAD):从 URL 提取 query\n return queryToObject(url.searchParams);\n}\n\n/** 空白文本判断(length 短路 + 正则扫描,避免 trim 的全量字符串拷贝) */\nfunction isBlankText(text: string): boolean {\n return text.length === 0 || !/\\S/.test(text);\n}\n\n/**\n * 解析\"主输入是 query 但允许携带 body\"的方法(DELETE)的请求体\n *\n * DELETE 的主输入(用于校验)是 query,但请求体流必须被消费(keep-alive 连接\n * 上有未读 body 时 Node 只能断开连接,无法复用),且 handler 声明 `body` 参数\n * 时应注入真正的请求体而非 query。\n *\n * 与主输入 body 的方法不同:DELETE body 无对应 schema(schema 提取只产出\n * DELETEQuery),不做校验,原样注入。空请求体返回 undefined(handler 未声明\n * body 参数时注入 undefined,行为一致)。\n *\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveBodyForQueryMethod(request: Request): Promise<unknown> {\n const text = await request.text();\n if (isBlankText(text)) return undefined;\n const result = parseJsonBody(text);\n if (!result.success) {\n throw new ValidationError('请求体不是合法的 JSON', [\n {\n path: 'body',\n code: 'INVALID_FORMAT',\n expected: 'JSON',\n received: 'text',\n message: '请求体不是合法的 JSON',\n },\n ]);\n }\n return result.data;\n}\n","import type { ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { consumePendingMetaHeaders } from './pendingMeta';\n\n/**\n * 将 Web Response 写入 Node.js ServerResponse\n *\n * 使用 stream pipe 处理背压,避免高并发下内存膨胀。\n *\n * 断连语义:客户端中断(res 'error' / 提前 'close')时销毁源流并按正常完成收尾——\n * 网络中断不是服务端错误,不进入 500 兜底、不触发 onError;destroy 使底层\n * web ReadableStream 触发 cancel(),上游生产者(如 SseWriter)感知 aborted\n * 停止推送,避免数据堆积在无消费者的流 buffer 中(内存泄漏)。\n * 源流自身错误(handler 流式写失败)仍 reject,走正常错误响应路径。\n */\nexport async function sendNodeResponse(response: Response, res: ServerResponse): Promise<void> {\n // 设置状态码\n res.statusCode = response.status;\n\n // 设置 headers\n for (const [key, value] of response.headers) {\n if (key.toLowerCase() === 'set-cookie') {\n // Set-Cookie 使用 appendHeader 支持多个值\n res.appendHeader(key, value);\n } else {\n res.setHeader(key, value);\n }\n }\n // 延迟落头(headers-only meta 的零重建通道,见 pendingMeta.ts)——\n // 后写覆盖,与 Headers.set 语义一致\n const deferred = consumePendingMetaHeaders(response);\n if (deferred) {\n for (const [key, value] of Object.entries(deferred)) {\n res.setHeader(key, value);\n }\n }\n\n // 写入 body(使用 pipe 处理背压)\n if (response.body) {\n // TS 5.7 lib.dom 的 ReadableStream 与 node:stream/web 的 ReadableStream 是不同类型,cast 绕过\n const nodeStream = Readable.fromWeb(response.body as never);\n await new Promise<void>((resolve, reject) => {\n // 源流错误:真实错误 → reject(handleRequest catch 走错误响应)\n nodeStream.on('error', reject);\n\n // 客户端断开:销毁源流(触发底层 cancel)+ resolve(不当作服务端错误)\n const abort = () => {\n nodeStream.destroy();\n resolve();\n };\n res.on('error', abort);\n res.on('close', () => {\n // 正常完成也会触发 'close'(在 'finish' 之后,此时 writableEnded=true),不视为断连\n if (!res.writableEnded) abort();\n });\n\n res.on('finish', resolve);\n nodeStream.pipe(res);\n });\n return;\n }\n\n res.end();\n}\n","import type { IncomingMessage } from 'node:http';\n\n/**\n * 从 IncomingMessage 提取客户端 IP\n *\n * 优先级:\n * 1. `trustedProxy=true` 时,`x-forwarded-for` 第一个 IP(反向代理场景,如 nginx/CDN)\n * 2. `req.socket.remoteAddress`(直连场景,默认路径)\n *\n * `x-forwarded-for` 格式:`client, proxy1, proxy2`,取第一个即客户端真实 IP。\n *\n * **默认不信任该 header**(`trustedProxy=false`):客户端直连时 XFF 可被任意伪造,\n * 污染 `ctx.ip`(影响限流/日志/风控)。部署在受信任的反向代理之后时,通过\n * `faapi.config.ts` 的 `trustedProxy: true` 显式开启。\n *\n * @param trustedProxy 是否信任反向代理头(默认 false——安全默认,直连不可伪造)\n * @returns 客户端 IP,无法获取时返回空字符串\n */\nexport function getClientIp(req: IncomingMessage, trustedProxy = false): string {\n // 1. x-forwarded-for(仅显式信任反向代理时)\n if (trustedProxy) {\n const xff = req.headers['x-forwarded-for'];\n if (typeof xff === 'string' && xff.length > 0) {\n const first = xff.split(',')[0]?.trim();\n if (first) return first;\n }\n }\n\n // 2. 直连 socket IP\n const remote = req.socket?.remoteAddress;\n if (remote) {\n // 去掉 IPv6 前缀 ::ffff:,统一返回 IPv4 形式\n if (remote.startsWith('::ffff:')) {\n return remote.slice(7);\n }\n return remote;\n }\n\n return '';\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\n\nexport interface CorsOptions {\n origin?: string | string[] | true;\n methods?: string[];\n allowedHeaders?: string[];\n exposeHeaders?: string[];\n credentials?: boolean;\n maxAge?: number;\n}\n\nconst DEFAULT_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];\n\n/**\n * 创建 CORS 中间件(洋葱模型)\n *\n * - origin=true: 允许所有来源(反射请求的 Origin)\n * - origin=string: 允许指定来源\n * - origin=string[]: 允许多个来源\n *\n * OPTIONS 预检请求直接返回 204,不调用 next()。\n */\nexport function cors(options: CorsOptions = {}): FaapiMiddleware {\n const {\n origin = true,\n methods = DEFAULT_METHODS,\n allowedHeaders,\n exposeHeaders,\n credentials = false,\n maxAge,\n } = options;\n\n return async (ctx, next) => {\n const reqOrigin = ctx.headers.get('origin');\n if (!reqOrigin) {\n await next();\n return;\n }\n\n // 确定允许的来源\n let allowOrigin: string | null = null;\n if (origin === true) {\n allowOrigin = reqOrigin; // 反射 Origin\n } else if (typeof origin === 'string') {\n allowOrigin = reqOrigin === origin ? origin : null;\n } else if (Array.isArray(origin)) {\n allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;\n }\n\n // 当 origin 为动态值(true 或数组)时,响应内容(是否带 ACAO)随 Origin 头变化\n // ——不匹配被拒的响应同样必须带 Vary: Origin。缺了它,CDN/浏览器按 URL 缓存\n // \"无 ACAO 的拒绝响应\"后,可能服务给后续合法 Origin 的请求(缓存污染面)\n if (origin === true || Array.isArray(origin)) {\n const existingVary = ctx.headers.get('vary');\n if (existingVary) {\n if (!existingVary.toLowerCase().includes('origin')) {\n ctx.setHeader('Vary', `${existingVary}, Origin`);\n }\n } else {\n ctx.setHeader('Vary', 'Origin');\n }\n }\n\n if (!allowOrigin) {\n await next();\n return;\n }\n\n // 设置 CORS 响应头\n ctx.setHeader('Access-Control-Allow-Origin', allowOrigin);\n\n ctx.setHeader('Access-Control-Allow-Methods', methods.join(', '));\n\n if (allowedHeaders) {\n ctx.setHeader('Access-Control-Allow-Headers', allowedHeaders.join(', '));\n } else {\n // 反射请求头\n const requestHeaders = ctx.headers.get('access-control-request-headers');\n if (requestHeaders) {\n ctx.setHeader('Access-Control-Allow-Headers', requestHeaders);\n }\n }\n\n if (exposeHeaders && exposeHeaders.length > 0) {\n ctx.setHeader('Access-Control-Expose-Headers', exposeHeaders.join(', '));\n }\n\n if (credentials) {\n ctx.setHeader('Access-Control-Allow-Credentials', 'true');\n }\n\n if (maxAge !== undefined) {\n ctx.setHeader('Access-Control-Max-Age', String(maxAge));\n }\n\n // 处理 preflight 请求:直接返回 204,不调用 next()\n if (ctx.method === 'OPTIONS') {\n return new Response(null, { status: 204 });\n }\n\n await next();\n };\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\n\nexport interface HelmetOptions {\n contentSecurityPolicy?: string | false;\n xFrameOptions?: 'DENY' | 'SAMEORIGIN' | false;\n xContentTypeOptions?: boolean;\n referrerPolicy?: string | false;\n strictTransportSecurity?: string | false;\n xDnsPrefetchControl?: boolean;\n xDownloadOptions?: boolean;\n xPermittedCrossDomainPolicies?: string | false;\n crossOriginOpenerPolicy?: string | false;\n crossOriginResourcePolicy?: string | false;\n crossOriginEmbedderPolicy?: string | false;\n originAgentCluster?: boolean;\n xPoweredBy?: boolean;\n}\n\nconst DEFAULTS: Required<Omit<HelmetOptions, 'contentSecurityPolicy'>> & {\n contentSecurityPolicy: string | false;\n} = {\n contentSecurityPolicy: \"default-src 'self'\",\n xFrameOptions: 'SAMEORIGIN',\n xContentTypeOptions: true,\n referrerPolicy: 'no-referrer',\n strictTransportSecurity: 'max-age=31536000; includeSubDomains',\n xDnsPrefetchControl: true,\n xDownloadOptions: true,\n xPermittedCrossDomainPolicies: 'none',\n crossOriginOpenerPolicy: 'same-origin',\n crossOriginResourcePolicy: 'same-origin',\n crossOriginEmbedderPolicy: false,\n originAgentCluster: true,\n xPoweredBy: true,\n};\n\nexport function helmet(options: HelmetOptions = {}): FaapiMiddleware {\n const opts = { ...DEFAULTS, ...options };\n\n return async (ctx, next) => {\n if (opts.contentSecurityPolicy !== false) {\n ctx.setHeader('Content-Security-Policy', opts.contentSecurityPolicy);\n }\n if (opts.xFrameOptions !== false) {\n ctx.setHeader('X-Frame-Options', opts.xFrameOptions);\n }\n if (opts.xContentTypeOptions) {\n ctx.setHeader('X-Content-Type-Options', 'nosniff');\n }\n if (opts.referrerPolicy !== false) {\n ctx.setHeader('Referrer-Policy', opts.referrerPolicy);\n }\n if (opts.strictTransportSecurity !== false) {\n ctx.setHeader('Strict-Transport-Security', opts.strictTransportSecurity);\n }\n if (opts.xDnsPrefetchControl) {\n ctx.setHeader('X-DNS-Prefetch-Control', 'off');\n }\n if (opts.xDownloadOptions) {\n ctx.setHeader('X-Download-Options', 'noopen');\n }\n if (opts.xPermittedCrossDomainPolicies !== false) {\n ctx.setHeader('X-Permitted-Cross-Domain-Policies', opts.xPermittedCrossDomainPolicies);\n }\n if (opts.crossOriginOpenerPolicy !== false) {\n ctx.setHeader('Cross-Origin-Opener-Policy', opts.crossOriginOpenerPolicy);\n }\n if (opts.crossOriginResourcePolicy !== false) {\n ctx.setHeader('Cross-Origin-Resource-Policy', opts.crossOriginResourcePolicy);\n }\n if (opts.crossOriginEmbedderPolicy !== false) {\n ctx.setHeader('Cross-Origin-Embedder-Policy', opts.crossOriginEmbedderPolicy);\n }\n if (opts.originAgentCluster) {\n ctx.setHeader('Origin-Agent-Cluster', '?1');\n }\n if (opts.xPoweredBy) {\n ctx.setHeader('X-Powered-By', 'faapi');\n }\n\n return await next();\n };\n}\n","import zlib from 'node:zlib';\nimport { promisify } from 'node:util';\nimport type { FaapiMiddleware } from './middlewareTypes';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\n\nconst gzipAsync = promisify(zlib.gzip);\nconst deflateAsync = promisify(zlib.deflate);\nconst brotliAsync = promisify(zlib.brotliCompress);\n\nexport interface CompressionOptions {\n /**\n * 最小压缩字节数,body 低于该值不压缩(小 payload 压缩后反而变大)\n * @default 1024\n */\n threshold?: number;\n}\n\nconst DEFAULT_THRESHOLD = 1024;\n\n/** 可压缩的 Content-Type(text/event-stream 除外——流式语义不应缓冲) */\nconst COMPRESSIBLE_CHECKS: Array<(contentType: string) => boolean> = [\n (ct) => ct === 'application/json',\n (ct) => ct === 'application/javascript',\n (ct) => ct === 'image/svg+xml',\n (ct) => ct.startsWith('text/') && ct !== 'text/event-stream',\n];\n\nfunction isCompressible(contentType: string): boolean {\n const ct = contentType.split(';')[0]!.trim().toLowerCase();\n if (ct === '') return false;\n return COMPRESSIBLE_CHECKS.some((check) => check(ct));\n}\n\ninterface AcceptedEncoding {\n encoding: string;\n quality: number;\n}\n\n/**\n * 解析 Accept-Encoding 头(含 q 值)\n *\n * `gzip, br;q=0.8, identity;q=0, *;q=0.5` → [{gzip,1},{br,0.8},{identity,0},{*,0.5}]\n */\nexport function parseAcceptEncoding(header: string): AcceptedEncoding[] {\n return header\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part !== '')\n .map((part) => {\n const [encoding, ...params] = part.split(';');\n let quality = 1;\n for (const param of params) {\n const trimmed = param.trim();\n if (trimmed.startsWith('q=')) {\n const q = Number(trimmed.slice(2));\n if (Number.isFinite(q)) quality = q;\n }\n }\n return { encoding: (encoding ?? '').trim().toLowerCase(), quality };\n })\n .filter((e) => e.encoding !== '' && e.quality > 0);\n}\n\n/**\n * 从已接受的编码中选择压缩算法\n *\n * 服务器偏好 br > gzip > deflate(同等 q 值下);`*` 通配按 gzip 处理。\n * 未命中返回 null(客户端不接受压缩)。\n */\nexport function selectEncoding(accepted: AcceptedEncoding[]): 'br' | 'gzip' | 'deflate' | null {\n const byName = new Map<string, number>();\n for (const { encoding, quality } of accepted) {\n // 同一编码出现多次取最高 q\n byName.set(encoding, Math.max(byName.get(encoding) ?? 0, quality));\n }\n // q=0 表示明确不接受\n for (const [encoding, quality] of byName) {\n if (quality === 0) byName.delete(encoding);\n }\n\n for (const candidate of ['br', 'gzip', 'deflate'] as const) {\n if ((byName.get(candidate) ?? 0) > 0) return candidate;\n }\n if ((byName.get('*') ?? 0) > 0) return 'gzip';\n return null;\n}\n\nasync function compressBody(\n encoding: 'br' | 'gzip' | 'deflate',\n body: string,\n): Promise<Uint8Array> {\n const buf = Buffer.from(body, 'utf-8');\n switch (encoding) {\n case 'br':\n return brotliAsync(buf);\n case 'gzip':\n return gzipAsync(buf);\n case 'deflate':\n return deflateAsync(buf);\n }\n}\n\n/** 向 meta.headers 合并 Vary 值(CORS 等中间件可能已设置 Vary: Origin,不能覆盖) */\nfunction mergeVary(meta: ResponseMeta, value: string): void {\n const existing = meta.headers['Vary'] ?? meta.headers['vary'];\n if (!existing) {\n meta.headers['Vary'] = value;\n return;\n }\n // 大小写不敏感地检查是否已包含\n if (!existing.toLowerCase().includes(value.toLowerCase())) {\n const key = meta.headers['Vary'] !== undefined ? 'Vary' : 'vary';\n meta.headers[key] = `${existing}, ${value}`;\n }\n}\n\n/**\n * 创建响应压缩中间件(内建,默认关闭,`config.compression` 显式启用)\n *\n * 位于外层中间件链最前(包住 CORS/helmet/logger/全局),`await next()` 后拿到\n * 最终 Response:满足压缩条件(可压缩类型 + 大小达标 + 客户端接受)时缓冲 body\n * 异步压缩后构造新 Response 替换;否则透传。`Vary: Accept-Encoding` 通过\n * meta.headers 合并(与 CORS 的 Vary: Origin 共存),两种路径都生效。\n */\nexport function compression(options: CompressionOptions = {}): FaapiMiddleware {\n const threshold = options.threshold ?? DEFAULT_THRESHOLD;\n\n return async (ctx, next) => {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n const response = await next();\n if (!response) return response;\n\n // 无条件协商 Vary(响应内容随 Accept-Encoding 变化,与是否压缩本响应无关)\n mergeVary(meta, 'Accept-Encoding');\n\n // 跳过条件:客户端不接受 / 不可压缩类型 / 已编码 / no-transform / 204、304 等\n const acceptEncoding = ctx.request.headers.get('accept-encoding') ?? '';\n const encoding = selectEncoding(parseAcceptEncoding(acceptEncoding));\n const contentType = response.headers.get('content-type') ?? '';\n if (\n !encoding ||\n !isCompressible(contentType) ||\n response.headers.has('content-encoding') ||\n (response.headers.get('cache-control') ?? '').includes('no-transform') ||\n response.status === 204 ||\n response.status === 304 ||\n response.body === null\n ) {\n return response;\n }\n\n // 缓冲 body(toResponse 的 JSON body 本就是字符串);SSE 已被 content-type 排除\n // 延迟落头(headers-only meta)需并入重建的 Response,否则发送层取不到\n const deferredHeaders = consumePendingMetaHeaders(response);\n\n const bodyText = await response.text();\n if (bodyText.length < threshold) {\n // 未达阈值:body 已被 text() 消费,需重建等价 Response\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n const compressed = await compressBody(encoding, bodyText);\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n headers.set('Content-Encoding', encoding);\n // Content-Length 由 Response 构造时按压缩后字节数自动设置(先删避免重复)\n headers.delete('Content-Length');\n return new Response(compressed as unknown as BodyInit, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n","import { createHash } from 'node:crypto';\nimport type { FaapiMiddleware } from './middlewareTypes';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\n\nexport interface EtagOptions {\n /**\n * 生成弱 ETag(`W/\"<hash>\"`)。弱校验器允许压缩等表示差异下的 304 协商,\n * 与 compression 中间件配合正确;强 ETag 需对每个表示(编码)单独生成\n * @default true\n */\n weak?: boolean;\n}\n\nconst DEFAULTS: Required<EtagOptions> = { weak: true };\n\n/** 计算 ETag:SHA-1 指纹 base64;weak=true 生成弱校验器(W/ 前缀) */\nfunction computeEtag(body: string, weak: boolean): string {\n const hash = createHash('sha1').update(body).digest('base64');\n return weak ? `W/\"${hash}\"` : `\"${hash}\"`;\n}\n\n/** If-None-Match 比对:weak 模式用 RFC 7232 弱比较(忽略 W/ 前缀);strong 模式精确比对 */\nfunction ifNoneMatchMatches(ifNoneMatch: string, etag: string, weak: boolean): boolean {\n const normalize = (tag: string) => tag.trim().replace(/^W\\//i, '');\n if (weak) {\n const target = normalize(etag);\n return ifNoneMatch.split(',').some((tag) => {\n const trimmed = tag.trim();\n return trimmed === '*' || normalize(trimmed) === target;\n });\n }\n // 强比较:ETag 原样相等(含 W/ 前缀语义,弱 ETag 永不匹配强比较)\n return ifNoneMatch.split(',').some((tag) => tag.trim() === etag || tag.trim() === '*');\n}\n\n/**\n * 创建 ETag/304 协商中间件(内建,默认关闭,`config.etag` 显式启用)\n *\n * `await next()` 后对 GET/HEAD 2xx 响应计算弱 ETag 并写入 meta(handler 显式\n * `ctx.setETag()` 时不覆盖);请求 `If-None-Match` 弱比较命中时返回 304(无 body)。\n *\n * 中间件位置在 compression 内层:先算 ETag/304 再压缩——304 无 body 压缩自动跳过;\n * 弱 ETag 基于未压缩表示计算,语义正确。\n */\nexport function etag(options: EtagOptions = {}): FaapiMiddleware {\n const opts = { ...DEFAULTS, ...options };\n\n return async (ctx, next) => {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n const response = await next();\n if (!response) return response;\n\n // 仅协商幂等方法的 2xx 响应\n const method = ctx.request.method.toUpperCase();\n if (method !== 'GET' && method !== 'HEAD') return response;\n if (response.status < 200 || response.status >= 300) return response;\n\n // handler 已显式设置 ETag(ctx.setETag 写 meta.headers.etag)→ 控制权归 handler\n if (meta.headers['etag'] !== undefined || response.headers.has('etag')) {\n return response;\n }\n // 流式响应(SSE 等)不缓冲\n if (\n response.body === null ||\n (response.headers.get('content-type') ?? '').includes('text/event-stream')\n ) {\n return response;\n }\n\n const bodyText = await response.text();\n const etagValue = computeEtag(bodyText, opts.weak);\n\n // If-None-Match 弱比较命中 → 304(无 body,携带 ETag)\n const ifNoneMatch = ctx.request.headers.get('if-none-match');\n if (ifNoneMatch && ifNoneMatchMatches(ifNoneMatch, etagValue, opts.weak)) {\n return new Response(null, {\n status: 304,\n statusText: response.statusText,\n headers: { ETag: etagValue },\n });\n }\n\n // 200 路径:body 已被 text() 消费,重建响应;ETag 经 meta 传递(mergeMeta 兜底应用)\n meta.headers['etag'] = etagValue;\n const headers = new Headers(response.headers);\n // 延迟落头并入重建的 Response(etag 中间件位于 compression 内层,先消费)\n const deferredHeaders = consumePendingMetaHeaders(response);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\n\nexport type LoggerFn = (messageOrObj: string | Record<string, unknown>, message?: string) => void;\n\nexport interface LoggerOptions {\n /**\n * 自定义日志函数\n *\n * - 传入 `console.log`(默认):纯文本格式 `GET /api/users 200 12ms`\n * - 传入 pino logger:结构化日志 `logger.info({ method, path, status, durationMs }, 'request completed')`\n * - 传入 winston logger:`logger.info('GET /api/users 200 12ms', { method, path })`\n */\n log?: LoggerFn;\n}\n\n/**\n * 创建请求日志中间件(洋葱模型)\n *\n * 日志格式(文本模式):GET /api/users 200 12ms\n * 错误格式(文本模式):POST /api/users 400 45ms - Error: ...\n *\n * 结构化模式:传入 pino/winston 等 logger 实例时,会自动传递结构化字段。\n *\n * before/after 一体,闭包变量共享开始时间,无需污染 ctx。\n * 错误用 try/catch 捕获,记录后重新抛出(让上层处理)。\n * 成功时从 next() 返回的 Response 读取状态码。\n *\n * log 函数每次请求时读取(options.log ?? console.log),运行时替换 console.log 会生效。\n */\nexport function logger(options: LoggerOptions = {}): FaapiMiddleware {\n return async (ctx, next) => {\n const log = options.log ?? console.log;\n const start = Date.now();\n try {\n const response = await next();\n const duration = Date.now() - start;\n const entry = {\n method: ctx.method,\n path: ctx.path,\n status: response.status,\n durationMs: duration,\n };\n log(entry, `${ctx.method} ${ctx.path} ${response.status} ${duration}ms`);\n return response;\n } catch (err) {\n const duration = Date.now() - start;\n const message = err instanceof Error ? err.message : String(err);\n const status = (err as { statusCode?: number })?.statusCode ?? 500;\n const entry = {\n method: ctx.method,\n path: ctx.path,\n status,\n durationMs: duration,\n error: message,\n };\n log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);\n throw err;\n }\n };\n}\n","/**\n * WebSocket 协议升级处理\n *\n * 监听 HTTP server 的 upgrade 事件,匹配 WS 路由后调用 ws 库完成协议升级,\n * 加载 handler.ts 的 WS 导出,绑定事件回调。\n *\n * 握手阶段复用洋葱中间件链(鉴权/CORS/限流/日志等),与同目录 HTTP 路由共享中间件。\n * 中间件塞入 ctx 的值(如 ctx.user)传入 WS handler。连接建立后切到事件模型,不走中间件。\n *\n * @see wsHandler.md WS handler 约定与握手中间件链设计\n */\nimport type { IncomingMessage } from 'node:http';\nimport type { Server } from 'node:http';\nimport type { Socket } from 'node:net';\nimport fs from 'node:fs';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport type { RawData } from 'ws';\nimport path from 'node:path';\nimport type { WsRouteMatch, RoutesRef } from '../router/routeTypes';\nimport { matchWsRoute } from '../router/matchRoute';\nimport { createContext } from '../runtime/createContext';\nimport { compose, mergeMeta } from '../runtime/invokeHandler';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport { getClientIp } from '../utils/getClientIp';\nimport { nodeHttpToWebHeaders, buildErrorResponse } from './serverUtils';\nimport {\n ensureCompiled,\n ensureMiddlewaresCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n prodPathToSourcePath,\n} from '../cli/compileOnDemand';\nimport { loadMergedMiddlewares } from '../middleware/loadMiddlewares';\nimport type { AppRegistries } from '../injection/registries';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\nimport {\n wrapWsSocket,\n type WsContext,\n type WsEventHandlers,\n type WsHandler,\n} from '../runtime/wsHandler';\n\n/**\n * 从 IncomingMessage 提取 URL pathname(不含 query)\n */\nfunction getPathname(req: IncomingMessage): string {\n const url = req.url ?? '/';\n const idx = url.indexOf('?');\n return idx >= 0 ? url.slice(0, idx) : url;\n}\n\n/**\n * 加载 WS handler 并执行,返回事件回调对象(可能为空)\n *\n * 直接动态 import handler.ts 并取 `WS` 导出(不经过 loadRouteModule,\n * 因为 WS 不是 HTTP 方法,loadRouteModule 的 method 维度不适用)。\n * watch 模式下通过时间戳 query string 绕过 ESM 缓存。\n *\n * Dev 按需编译模式:先确保编译再 import,避免 import 不存在的文件污染 Vite SSR 状态。\n */\nasync function loadWsHandler(\n filePath: string,\n ctx: WsContext,\n rootDir?: string,\n): Promise<WsEventHandlers | void> {\n // Dev 按需模式:先确保编译再 import(同 loadRouteModule 逻辑)\n if (isDevOnDemandEnabled() && rootDir) {\n const dist = getDevDist();\n if (dist) {\n const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);\n if (sourcePath && fs.existsSync(sourcePath)) {\n await ensureCompiled(sourcePath, rootDir, dist);\n }\n }\n }\n\n const module = (await importWithCacheBust(filePath, isDevOnDemandEnabled())) as Record<\n string,\n unknown\n >;\n\n const handler = module['WS'];\n if (typeof handler !== 'function') {\n throw new Error(`WS export not found in ${filePath}`);\n }\n return (handler as WsHandler)(ctx);\n}\n\n/**\n * 绑定 ws 库原生事件到 faapi 的 WsEventHandlers\n *\n * 注意:wss.handleUpgrade 回调触发时 socket 已经处于 OPEN 状态,\n * 'open' 事件不会再触发,需要直接同步调用 onOpen。\n */\nfunction bindEvents(rawSocket: WebSocket, handlers: WsEventHandlers | void): void {\n if (!handlers) return;\n const ws = wrapWsSocket(rawSocket);\n\n if (handlers.onOpen) {\n // handleUpgrade 回调触发时 socket 已是 OPEN,直接同步调用\n if (rawSocket.readyState === WebSocket.OPEN) {\n handlers.onOpen!(ws);\n } else {\n rawSocket.once('open', () => handlers.onOpen!(ws));\n }\n }\n if (handlers.onMessage) {\n rawSocket.on('message', (data: RawData, isBinary: boolean) => {\n // ws 库 message 事件传 Buffer | ArrayBuffer | Buffer[],归一化为 Buffer;\n // 二进制帧透传 Buffer(utf8 解码不可逆,protobuf/msgpack 等二进制协议会损坏),\n // 文本帧转 utf8 字符串——与 onMessage 类型声明 string | Buffer 一致\n const buf = toWsBuffer(data);\n handlers.onMessage!(ws, isBinary ? buf : buf.toString('utf8'));\n });\n }\n if (handlers.onClose) {\n rawSocket.on('close', (code: number, reason: Buffer) => {\n handlers.onClose!(ws, code, reason.toString('utf8'));\n });\n }\n if (handlers.onError) {\n rawSocket.on('error', (err: Error) => {\n handlers.onError!(ws, err);\n });\n }\n}\n\n/** ws 库 RawData(Buffer | ArrayBuffer | Buffer[])归一化为 Buffer */\nfunction toWsBuffer(data: RawData): Buffer {\n if (Buffer.isBuffer(data)) return data;\n if (Array.isArray(data)) return Buffer.concat(data);\n return Buffer.from(data as ArrayBuffer);\n}\n\n/**\n * 把 Web Response 写回原始 socket(握手被中间件拦截或出错时使用)\n *\n * socket 未升级为 WebSocket 时,按 HTTP 响应格式写回并销毁。\n */\nasync function sendResponseToSocket(socket: Socket, response: Response): Promise<void> {\n const body = await response.text().catch(() => '');\n const statusLine = `HTTP/1.1 ${response.status} ${response.statusText || ''}\\r\\n`;\n const headerLines: string[] = [];\n let hasContentLength = false;\n // 延迟落头(headers-only meta)并入写回的 header 行\n const deferredHeaders = consumePendingMetaHeaders(response);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n if (key.toLowerCase() === 'content-length') hasContentLength = true;\n headerLines.push(`${key}: ${value}`);\n }\n for (const [key, value] of response.headers) {\n if (key.toLowerCase() === 'content-length') {\n hasContentLength = true;\n }\n headerLines.push(`${key}: ${value}`);\n }\n if (!hasContentLength) {\n headerLines.push(`Content-Length: ${Buffer.byteLength(body)}`);\n }\n socket.write(statusLine + headerLines.join('\\r\\n') + '\\r\\n\\r\\n' + body);\n socket.destroy();\n}\n\nexport interface AttachWsOptions {\n /** HTTP server 实例(已由 createServer 创建) */\n server: Server;\n /** 路由可变引用容器(watch 模式热替换时 routesRef.wsCurrent 被更新) */\n routesRef: RoutesRef;\n /** 项目根目录,用于解析路由文件绝对路径 */\n rootDir: string;\n /** 业务配置,注入到 WsContext.config */\n config?: Record<string, unknown>;\n /** 全局中间件(来自 faapi.config.ts,WS 握手最外层) */\n globalMiddlewares?: FaapiMiddleware[];\n /** 全局注入器(来自 faapi.config.ts,WS handler 可通过 ctx 间接访问全局依赖) */\n globalInjectors?: InjectorMap;\n /**\n * 是否信任反向代理头(X-Forwarded-For),默认 false\n *\n * true 时 `ctx.ip` 取 `x-forwarded-for` 第一个 IP;false(默认)时直取 socket 地址。\n */\n trustedProxy?: boolean;\n /** app 级注册表——经 WsContext 进入 WS 握手链路 */\n registries?: AppRegistries;\n}\n\n/**\n * 在 HTTP server 上挂载 WebSocket 升级处理\n *\n * 创建独立的 WebSocketServer(noServer 模式),监听 server 的 upgrade 事件。\n * 路由匹配成功后,走洋葱中间件链:\n * - 中间件放行(await next):finalHandler 内调用 ws.handleUpgrade 完成协议升级、绑定事件回调\n * - 中间件拦截(返回 Response):把 Response 写回 socket 后销毁,不进行协议升级\n * - 中间件抛错:由 formatErrorResponse/内置兜底生成错误 Response,写回 socket 后销毁\n *\n * 中间件链顺序:全局中间件(外)→ 目录中间件(内)→ finalHandler。\n * watch 模式下 wsRoutes 通过 routesRef 引用更新。\n */\nexport function attachWebSocket(options: AttachWsOptions): WebSocketServer {\n const {\n server,\n routesRef,\n rootDir,\n config,\n globalMiddlewares,\n trustedProxy = false,\n registries,\n } = options;\n\n const wss = new WebSocketServer({ noServer: true });\n\n server.on('upgrade', async (req: IncomingMessage, socket: Socket, head: Buffer) => {\n try {\n await handleUpgradeRequest(req, socket, head);\n } catch (err) {\n // 兜底 catch:upgrade 监听器是 async 函数,路由匹配 / header 构造 / 上下文创建等\n // 抛出的未捕获错误会变成 unhandled rejection(Node 15+ 默认崩掉进程)。\n // HTTP 路径有 catch 兜底,WS 路径保持对称——写回 500 后销毁 socket。\n console.error('[faapi] WS upgrade 处理失败:', err);\n if (!socket.destroyed) {\n try {\n if (!socket.writableEnded) {\n socket.write('HTTP/1.1 500 Internal Server Error\\r\\n\\r\\n');\n }\n } catch {\n // socket 已不可写,忽略\n }\n socket.destroy();\n }\n }\n });\n\n async function handleUpgradeRequest(\n req: IncomingMessage,\n socket: Socket,\n head: Buffer,\n ): Promise<void> {\n const currentWsRoutes = routesRef.wsCurrent;\n const pathname = getPathname(req);\n const match: WsRouteMatch | null = matchWsRoute(currentWsRoutes, pathname);\n\n if (!match) {\n // 路由不匹配:返回 404 并销毁 socket\n socket.write('HTTP/1.1 404 Not Found\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n\n const { route, params } = match;\n const headers = nodeHttpToWebHeaders(req);\n const host = req.headers.host ?? 'localhost';\n const url = `http://${host}${req.url ?? '/'}`;\n\n // 构造 Web Request 与 FaapiContext(与 HTTP 请求一致,供中间件使用)\n const request = new Request(url, { method: 'GET', headers });\n const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n\n // 标记握手是否已完成协议升级(用于判断 socket 是否可写)\n let upgraded = false;\n\n // finalHandler:加载 WS handler + 协议升级 + 绑定事件\n const finalHandler = async (): Promise<Response> => {\n let handlers: WsEventHandlers | void;\n try {\n const absoluteFilePath = path.resolve(rootDir, route.filePath);\n // WsContext 是 FaapiContext 的结构子集,直接复用 ctx 实例\n // 中间件塞入的字段(如 ctx.user)对 WS handler 可见\n handlers = await loadWsHandler(absoluteFilePath, ctx as unknown as WsContext, rootDir);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.error(`[faapi] WS handler 加载失败 ${route.filePath}: ${reason}`);\n return new Response('Internal Server Error', { status: 500 });\n }\n\n // 协议升级:等 wss.handleUpgrade 回调完成 bindEvents\n await new Promise<void>((resolve, reject) => {\n wss.handleUpgrade(req, socket, head, (rawSocket: WebSocket) => {\n try {\n bindEvents(rawSocket, handlers);\n wss.emit('connection', rawSocket, req);\n upgraded = true;\n resolve();\n } catch (err) {\n reject(err);\n }\n });\n });\n\n // 占位 Response,表示握手成功(socket 已升级,不会被写出)\n return new Response(null, { status: 200 });\n };\n\n // 执行中间件链:全局中间件(外)+ 目录中间件(内)\n let response: Response;\n try {\n // 按需加载中间件:route.middlewares 为 undefined 时从 middlewarePaths 加载(Vite 风格)\n if (route.middlewares === undefined && route.middlewarePaths) {\n // dev 按需模式:先编译中间件源码(含依赖闭包)再 import\n await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);\n const bundle = await loadMergedMiddlewares(route.middlewarePaths);\n if (bundle) {\n route.middlewares = bundle.middlewares;\n } else {\n route.middlewares = [];\n }\n }\n const dirMiddlewares = route.middlewares ?? [];\n const allMiddlewares =\n globalMiddlewares && globalMiddlewares.length > 0\n ? [...globalMiddlewares, ...dirMiddlewares]\n : dirMiddlewares;\n if (allMiddlewares.length > 0) {\n response = await compose(allMiddlewares, ctx, finalHandler);\n } else {\n response = await finalHandler();\n }\n } catch (err) {\n if (upgraded) {\n // 握手已完成(socket 已被 ws 库接管),中间件 after 阶段抛错无法改写响应\n console.error('[faapi] WS 握手后中间件抛错:', err);\n return;\n }\n // 错误兜底链:内置 formatErrorResponse 兜底\n // 业务方如需自定义错误响应,在全局中间件中 try/catch next() 即可\n response = buildErrorResponse(err);\n }\n\n // 握手成功:socket 已升级,中间件 after 阶段已执行完,无需再写\n if (upgraded) {\n return;\n }\n\n // 中间件拦截或错误:把 Response 写回 socket 后销毁\n await sendResponseToSocket(socket, mergeMeta(response, meta));\n }\n\n return wss;\n}\n","import type { IncomingMessage } from 'node:http';\nimport { formatErrorResponse } from '../errors/formatErrorResponse';\n\n/**\n * 将 Node.js IncomingMessage 的 headers 转为 Web Headers\n *\n * 处理数组型 header 值(如 set-cookie)和 undefined 值。\n */\nexport function nodeHttpToWebHeaders(req: IncomingMessage): Headers {\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const v of value) headers.append(key, v);\n } else {\n headers.set(key, value);\n }\n }\n return headers;\n}\n\n/**\n * 构建错误响应(兜底链)\n *\n * 1. 框架内置 formatErrorResponse 处理(handler 抛错时)——读 config.response.fail\n * 自定义包装函数,确保错误格式与 ctx.fail() 主动错误响应一致\n * 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n *\n * 业务方如需进一步自定义错误响应,在全局中间件中 try/catch next() 即可。\n *\n * @param err handler 抛出的错误\n * @param config 业务方配置(用于读取 response.fail,可选——未传时走框架默认 fail 函数)\n */\nexport function buildErrorResponse(\n err: unknown,\n config?: Record<string, unknown> | undefined,\n): Response {\n try {\n return formatErrorResponse(err, config);\n } catch {\n // 极端情况:内置兜底也失败,返回最简 500\n return new Response(\n JSON.stringify({ error: { code: 'INTERNAL_ERROR', message: 'Internal Server Error' } }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n}\n","/**\n * WebSocket Handler 类型定义与 socket 封装\n *\n * 用户在 handler.ts 中导出 WS 函数,返回事件回调对象。\n * 框架在协议升级成功后调用对应回调,传递封装后的 WsSocket。\n *\n * @see wsHandler.md 功能说明\n */\n\n/**\n * faapi 封装的 WebSocket socket 抽象\n *\n * 不直接暴露 ws 库的原生 socket,提供更安全、易用的 API。\n * send 对象时自动 JSON.stringify。\n */\nexport interface WsSocket {\n /** 发送数据(string/Buffer 直发,对象自动 JSON.stringify) */\n send(data: string | Buffer | object): void;\n /** 关闭连接 */\n close(code?: number, reason?: string | Buffer): void;\n /** 连接状态:0=connecting, 1=open, 2=closing, 3=closed */\n readonly readyState: number;\n}\n\n/**\n * WebSocket 事件回调集合\n *\n * 用户在 WS handler 中返回此对象,框架在对应事件触发时调用。\n * 所有回调可选,未提供则忽略事件。\n */\nexport interface WsEventHandlers {\n /** 连接建立时触发 */\n onOpen?: (ws: WsSocket) => void;\n /** 收到客户端消息时触发 */\n onMessage?: (ws: WsSocket, message: string | Buffer) => void;\n /** 连接关闭时触发 */\n onClose?: (ws: WsSocket, code: number, reason: string) => void;\n /** 发生错误时触发 */\n onError?: (ws: WsSocket, error: Error) => void;\n}\n\n/**\n * WebSocket 上下文(握手阶段构造)\n *\n * 与 HTTP FaapiContext 类似但精简,提供路由参数、查询参数、请求头、配置。\n * 可通过 declare module '@faapi/faapi' 增强自定义字段。\n */\nexport interface WsContext {\n /** 动态路由参数(如 [id] → params.id) */\n params: Record<string, string>;\n /** URL 查询参数 */\n query: URLSearchParams;\n /** 请求头 */\n headers: Headers;\n /** 业务配置(来自 faapi.config.ts) */\n config: Record<string, unknown>;\n /** 中间件塞入的用户信息(鉴权等) */\n user?: unknown;\n /** 允许通过 declare module 扩展 */\n [key: string]: unknown;\n}\n\n/**\n * WS handler 签名:接收 WsContext,返回事件回调对象(或无返回)\n */\nexport type WsHandler = (ctx: WsContext) => WsEventHandlers | void;\n\n/**\n * 将 ws 库的原生 WebSocket 封装为 faapi 的 WsSocket\n *\n * - send 对象时自动 JSON.stringify\n * - close/re.readyState 透传原生 socket\n */\nexport function wrapWsSocket(rawSocket: {\n send: (data: string | Buffer, cb?: (err?: Error) => void) => void;\n close: (code?: number, reason?: string | Buffer) => void;\n readonly readyState: number;\n}): WsSocket {\n return {\n send(data: string | Buffer | object): void {\n const payload =\n typeof data === 'string' || Buffer.isBuffer(data) ? data : JSON.stringify(data);\n rawSocket.send(payload);\n },\n close(code?: number, reason?: string | Buffer): void {\n rawSocket.close(code, reason);\n },\n get readyState(): number {\n return rawSocket.readyState;\n },\n };\n}\n","import { WebSocket, type RawData } from 'ws';\n\n/**\n * WebSocket 测试客户端\n *\n * 公开导出 connectWs + MessageQueue + waitForWsOpen,业务方测试 WS 路由时\n * 免去手写\"消息竞态防护 + 三事件监听 + 端口拼接\"样板代码。\n *\n * 详见 src/wsTestClient.md。\n */\n\n/**\n * connectWs 入参\n */\nexport interface WsTestClientOptions {\n /** 等待 open 的超时(ms),默认 2000 */\n timeout?: number;\n /** 握手请求头(如 authorization) */\n headers?: Record<string, string>;\n /** WS 子协议 */\n protocols?: string | string[];\n}\n\n/**\n * connectWs 返回值\n *\n * 业务方通过 ws.send() 发消息,queue.next() 取消息,close() 关闭。\n */\nexport interface WsTestClient {\n /** ws 库原生实例,业务方可直接 ws.send() / ws.close() */\n ws: WebSocket;\n /** 已开始缓冲的消息队列,调 next(timeout?) 取下一条 */\n queue: MessageQueue;\n /**\n * 关闭 ws 并等待 'close' 事件\n *\n * 内部:\n * 1. 若 ws 仍 OPEN/CLOSING,调 ws.close()\n * 2. 等待 'close' 事件(超时 1000ms 强制 resolve)\n *\n * 幂等:重复调用不抛错。\n */\n close(): Promise<void>;\n}\n\n/**\n * 消息队列:避免 once('message') 与服务端 onOpen 推送的竞态\n *\n * 服务端在 handleUpgrade 回调里同步触发 onOpen 并 send('connected'),\n * 客户端 'open' 事件触发后到注册 once('message') 之间存在窗口,\n * 若 'connected' 在此窗口内到达,once 会错过。\n *\n * 队列在创建 ws 时立即监听 'message',按 FIFO 顺序消费。\n */\nexport class MessageQueue {\n private queue: string[] = [];\n private waiters: Array<(msg: string) => void> = [];\n private listener: (data: RawData) => void;\n\n constructor(ws: WebSocket) {\n this.listener = (data: RawData) => {\n const msg = normalizeRawData(data);\n const waiter = this.waiters.shift();\n if (waiter) {\n waiter(msg);\n } else {\n this.queue.push(msg);\n }\n };\n ws.on('message', this.listener);\n }\n\n /**\n * 取下一条消息\n *\n * 队列有则立即 resolve,无则注册 waiter 等待下一条 'message' 事件。\n * 超时未到 → reject('WebSocket message timeout'),waiter 被清理。\n *\n * @param timeout 超时毫秒,默认 2000\n */\n next(timeout = 2000): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n // 先定义 wrapped,再定义引用 wrapped 的 timer,避免 TDZ 风格可读性问题\n const wrapped = (msg: string) => {\n clearTimeout(timer);\n resolve(msg);\n };\n const timer = setTimeout(() => {\n const idx = this.waiters.indexOf(wrapped);\n if (idx >= 0) this.waiters.splice(idx, 1);\n reject(new Error('WebSocket message timeout'));\n }, timeout);\n\n const msg = this.queue.shift();\n if (msg !== undefined) {\n wrapped(msg);\n } else {\n this.waiters.push(wrapped);\n }\n });\n }\n}\n\n/**\n * 将 ws 库的 RawData 统一转为 utf8 字符串\n *\n * RawData 可能形态:\n * - Buffer(最常见)\n * - Buffer[](消息被分片,需先 concat)\n * - ArrayBuffer / ArrayBufferView(部分运行时)\n */\nfunction normalizeRawData(data: RawData): string {\n if (Buffer.isBuffer(data)) {\n return data.toString('utf8');\n }\n if (Array.isArray(data)) {\n return Buffer.concat(data).toString('utf8');\n }\n // ArrayBuffer / ArrayBufferView\n return Buffer.from(data as unknown as Uint8Array).toString('utf8');\n}\n\n/**\n * Promise 化等待 ws 'open' 事件\n *\n * 同时监听 'open' / 'error' / 'close' 三事件,任一触发都清理 timer,\n * 避免 timer 泄漏。\n *\n * @param ws WebSocket 实例\n * @param timeout 超时毫秒,默认 2000\n * @returns 'open' → resolve;'error' → reject(err);'close' → reject;超时 → reject\n */\nexport function waitForWsOpen(ws: WebSocket, timeout = 2000): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error('WebSocket open timeout'));\n }, timeout);\n\n const cleanup = () => {\n clearTimeout(timer);\n ws.removeListener('open', onOpen);\n ws.removeListener('error', onError);\n ws.removeListener('close', onClose);\n };\n\n const onOpen = () => {\n cleanup();\n resolve();\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const onClose = () => {\n cleanup();\n reject(new Error('WebSocket closed before open'));\n };\n\n ws.once('open', onOpen);\n ws.once('error', onError);\n ws.once('close', onClose);\n });\n}\n\n/**\n * 一键连接 WS server\n *\n * 内部流程:\n * 1. baseUrl 协议转换(http → ws,https → wss)\n * 2. new WebSocket(url, protocols, { headers })\n * 3. 立即创建 MessageQueue(开始缓冲消息,避免竞态)\n * 4. waitForWsOpen 等待连接建立(三事件监听 + 超时清理)\n * 5. 返回 WsTestClient\n *\n * 连接失败(中间件拦截 / 路径未匹配 / 超时)→ reject。\n *\n * @param baseUrl createTestServer().baseUrl(http://...)\n * @param pathname WS 路径,如 '/api/chat',可含 query\n * @param options timeout / headers / protocols\n * @returns WsTestClient 实例\n */\nexport async function connectWs(\n baseUrl: string,\n pathname: string,\n options: WsTestClientOptions = {},\n): Promise<WsTestClient> {\n const { timeout = 2000, headers, protocols } = options;\n\n // 协议转换:http → ws,https → wss(业务方传入 createTestServer().baseUrl)\n const wsBaseUrl = baseUrl.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:');\n const url = `${wsBaseUrl}${pathname}`;\n\n const ws = new WebSocket(url, protocols, headers ? { headers } : undefined);\n const queue = new MessageQueue(ws);\n\n // 等待 open;失败时主动 close ws,避免 socket 处于 CONNECTING 状态泄漏\n try {\n await waitForWsOpen(ws, timeout);\n } catch (err) {\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n throw err;\n }\n\n // 关闭状态标记(幂等保护)\n let closed = false;\n\n return {\n ws,\n queue,\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n\n // 若 ws 仍 OPEN/CLOSING,主动 close\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n\n // 等待 'close' 事件(超时 1000ms 强制 resolve,避免 server 端不回 close)\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, 1000);\n ws.once('close', () => {\n clearTimeout(timer);\n resolve();\n });\n });\n },\n };\n}\n"],"mappings":"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","names":["path","body","url","path","headers","body","path","fs","path","fs","path","url","path","fs","path","path","fs","ts","fs","path","path","fs","ts","ts","ts","ts","ts","ts","path","unionInner","path","path","fs","path","fs","fg","path","fs","ts","path","fs","path","fs","SPEC_RE","fg","path","fs","path","fs","path","fs","fs","inFlight","fs","path","inFlight","path","fs","path","fs","path","Readable","path","path","params","text","headers","DEFAULTS","etag","fs","path","fs","path","Readable","path","DEFAULT_BODY_LIMIT","cors","helmet","logger","path","fs","WebSocket","msg"]}
|