@faapi/faapi 6.26.0 → 6.27.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/response/bufferedBody.ts","../src/utils/isPlainObject.ts","../src/utils/stringifyJson.ts","../src/runtime/sse.ts","../src/errors/FaapiError.ts","../src/errors/httpErrors.ts","../src/response/responseFormatter.ts","../src/logger/formatEntry.ts","../src/logger/logger.ts","../src/utils/queryToObject.ts","../src/utils/readResource.ts","../src/utils/prodPaths.ts","../src/runtime/createContext.ts","../src/response/pendingMeta.ts","../src/response/toResponse.ts","../src/injection/resolveInjection.ts","../src/task/taskRegistry.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/router/sortRoutes.ts","../src/cli/generateSchemaFiles.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/generateZodArtifacts.ts","../src/utils/atomicWrite.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/mergeVary.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/cli/copyResources.ts","../src/server/serverUtils.ts","../src/runtime/wsHandler.ts","../src/wsTestClient.ts"],"sourcesContent":["/**\n * 缓冲型 body 的 Response 标记(side-channel,与 pendingMeta 同一模式)\n *\n * `new Response(string | Buffer | Uint8Array)` 的 body 是**有限内存型**——发送层\n * 可整体读出后 `res.end` 直写,免去 `Readable.fromWeb + pipe` 的全套流机器(每响应\n * 数个中间流对象,JSON 响应的常态路径)。活跃流(SSE、handler 自建 ReadableStream)\n * 不能这样读——整体缓冲会破坏流式语义并可能永久挂起,但仅凭 Response 对象无法区分\n * 两种形态,因此在构造点标记。\n *\n * WeakSet:Response 被丢弃后标记自动可 GC,无手动清理。\n */\nconst bufferedBodyResponses = new WeakSet<Response>();\n\n/** 标记一个 body 为有限内存型(构造点调用;Response 已带标记时重复调用安全) */\nexport function markBufferedBody(response: Response): void {\n bufferedBodyResponses.add(response);\n}\n\n/** 查询 Response 是否带缓冲型 body 标记(发送层快路径判定用) */\nexport function hasBufferedBody(response: Response): boolean {\n return bufferedBodyResponses.has(response);\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 { isPlainObject } from './isPlainObject';\n\n/**\n * 框架统一 JSON 序列化出口:JSON 原生类型之外的一切值转换为可逆的原生表示。\n *\n * 原生 `JSON.stringify` 对规范外类型的行为不可接受:BigInt 抛 TypeError(响应 500)、\n * Map/Set/RegExp 静默序列化为 \"{}\"(丢数据)、NaN/±Infinity 静默变 null(丢信息)。\n * 设计契约是「线上格式即契约」:序列化把规范外类型转成可逆的原生表示(Date → 毫秒\n * 时间戳、BigInt → 字符串、Map → entries 数组、Set → 数组),客户端按转换后类型\n * 消费或自行还原,不提供序列化之外的第二条数据通路。完整转换表见 ./stringifyJson.md。\n *\n * 实现为「预变换值树 + 原生 stringify」而非 replacer——JSON.stringify 的 replacer\n * 在 toJSON 之后运行,拦不到 Date(此时已是 ISO 字符串)。预变换规则:\n *\n * - Date(含子类)→ `getTime()` 毫秒时间戳\n * - BigInt → `toString()` 字符串\n * - Map → entries 数组、Set → 值数组(键值/元素递归转换)\n * - 非有限 number(NaN/±Infinity)→ `\"NaN\"` / `\"±Infinity\"` 字符串\n * - RegExp → `\"/source/flags\"` 字符串\n * - 带 toJSON 的对象 → 递归转换其返回值(与原生语义对齐,且 toJSON 结果中的\n * Date/BigInt 也被转换)\n * - plain object / array → 递归重建(查找嵌套的特殊类型)\n * - 其他对象(类实例、URL 等)→ 原样返回,由原生 stringify 处理(保留 toJSON 语义)\n * - 循环引用 → 抛 TypeError(结构错误显式失败,不静默产出坏 JSON);非循环的\n * 共享引用不受影响\n */\nexport function stringifyJson(value: unknown): string {\n // 快路径:整棵值树均为 JSON 原生类型(绝大多数响应的形态)时零分配直通原生\n // stringify。慢路径对 plain object/array 无条件递归重建——每个响应都要付出一次\n // 全量深拷贝(GC 压力翻倍);检测本身零分配(只做类型判断),无特殊类型时省掉\n // 整棵树的重建。检测遇循环引用与慢路径同语义抛 TypeError\n if (!needsConversion(value, new Set<object>())) {\n return JSON.stringify(value);\n }\n return JSON.stringify(convertValue(value, new Set<object>()));\n}\n\n/**\n * 检测值树是否需要转换(含 Date/BigInt/Map/Set/NaN/RegExp/toJSON 时走慢路径重建)\n *\n * 与 convertValue 相同的遍历结构与循环引用检测(ancestors),但零分配——\n * 只做类型判断,不重建任何对象/数组。\n */\nfunction needsConversion(value: unknown, ancestors: Set<object>): boolean {\n if (value === null) return false;\n const type = typeof value;\n if (type === 'bigint') return true;\n if (type === 'number') return !Number.isFinite(value as number);\n if (type !== 'object') return false;\n\n const obj = value as object;\n if (ancestors.has(obj)) {\n throw new TypeError('[faapi] Converting circular structure to JSON');\n }\n if (obj instanceof Date || obj instanceof Map || obj instanceof Set || obj instanceof RegExp) {\n return true;\n }\n // 带 toJSON 的对象走慢路径:其返回值中的特殊类型也要转换\n if (typeof (obj as { toJSON?: unknown }).toJSON === 'function') return true;\n\n if (Array.isArray(obj)) {\n ancestors.add(obj);\n for (let i = 0; i < obj.length; i++) {\n if (needsConversion(obj[i], ancestors)) {\n ancestors.delete(obj);\n return true;\n }\n }\n ancestors.delete(obj);\n return false;\n }\n if (isPlainObject(obj)) {\n ancestors.add(obj);\n // for-in + hasOwnProperty 避免分配键数组(Object.keys/entries 每节点一个数组);\n // 可枚举性与 getters 的调用语义与 Object.entries 一致\n for (const key in obj) {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;\n if (needsConversion((obj as Record<string, unknown>)[key], ancestors)) {\n ancestors.delete(obj);\n return true;\n }\n }\n ancestors.delete(obj);\n return false;\n }\n // 其他对象(类实例、URL 等):原样序列化,无需转换\n return false;\n}\n\n/** 递归转换值树中的规范外类型(ancestors 做循环引用检测) */\nfunction convertValue(value: unknown, ancestors: Set<object>): unknown {\n // 原始类型:bigint 转字符串,非有限 number(NaN/±Infinity)转字符串,其余原样\n if (value === null) return value;\n const type = typeof value;\n if (type === 'bigint') return (value as bigint).toString();\n if (type === 'number') return Number.isFinite(value as number) ? value : String(value);\n if (type !== 'object') return value;\n\n const obj = value as object;\n if (ancestors.has(obj)) {\n throw new TypeError('[faapi] Converting circular structure to JSON');\n }\n\n // Date(含子类)→ 毫秒时间戳。在 toJSON 检查之前:Date 自带 toJSON(ISO 字符串),\n // 契约要求时间戳形态\n if (obj instanceof Date) return obj.getTime();\n // Map → entries 数组、Set → 值数组(键值/元素递归转换,保持与输入侧 zod schema\n // 的 coerceMap/coerceSet 还原约定可逆)\n if (obj instanceof Map) {\n ancestors.add(obj);\n const entries = Array.from(obj, ([k, v]) => [\n convertValue(k, ancestors),\n convertValue(v, ancestors),\n ]);\n ancestors.delete(obj);\n return entries;\n }\n if (obj instanceof Set) {\n ancestors.add(obj);\n const values = Array.from(obj, (v) => convertValue(v, ancestors));\n ancestors.delete(obj);\n return values;\n }\n if (obj instanceof RegExp) return obj.toString();\n\n // 带 toJSON 的对象:递归转换其返回值(Date/BigInt 等出现在 toJSON 结果中同样被转换)\n const toJSON = (obj as { toJSON?: unknown }).toJSON;\n if (typeof toJSON === 'function') {\n ancestors.add(obj);\n const result = convertValue((toJSON as () => unknown).call(obj), ancestors);\n ancestors.delete(obj);\n return result;\n }\n\n // plain object / array:递归重建,查找嵌套的特殊类型\n if (Array.isArray(obj)) {\n ancestors.add(obj);\n const arr = obj.map((item) => convertValue(item, ancestors));\n ancestors.delete(obj);\n return arr;\n }\n if (isPlainObject(obj)) {\n ancestors.add(obj);\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(obj)) {\n out[key] = convertValue(val, ancestors);\n }\n ancestors.delete(obj);\n return out;\n }\n\n // 其他对象(类实例、URL 等):原样返回,由原生 stringify 处理(保留 toJSON 语义、\n // 枚举自有属性等原生行为)\n return obj;\n}\n","/**\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/onClose/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、X-Accel-Buffering: no 头,状态码默认 200\n * (X-Accel-Buffering: no 让 nginx 按响应跳过 proxy_buffering,SSE 小包不被攒到流结束才下发;\n * 非 nginx 反代不识别该头,当普通自定义头透传)\n * - close 后再 send 静默忽略,避免 handler 异步流程中误写已关闭的流\n * - sendError 向流写入 event: error 后关闭,用于流式输出中报错的优雅终止\n * - 背压:send/sendRaw 保持同步;desiredSize 暴露缓冲状态,waitForDrain() 供\n * 生产循环在缓冲超水位时暂停(pull 钩子唤醒),防慢客户端内存无界增长\n *\n * 与 ctx 的集成:\n * - ctx.sse() 调用 createSseWriter(),并把 response 缓存到 ctx 内部字段\n * - 首次写入(send/sendRaw/sendError)经 onFirstWrite 触发 server 层提前接管钩子,\n * 响应立即接入底层连接(流式即产即达);无钩子场景回落为 handler 返回后由\n * invokeHandler 检查 ctx 持有的 SSE response,有则优先使用\n */\n\nimport { stringifyJson } from '../utils/stringifyJson';\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(BigInt 安全,见 utils/stringifyJson),多行每行加前缀\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 = stringifyJson(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 * ctx.sse() 的选项\n */\nexport interface SseOptions {\n /**\n * 声明流生命周期独立于 handler 返回(长连接订阅模式)\n *\n * `true` 时 handler 返回后框架不自动 close——handler 只负责把连接挂到推送源\n * (事件总线、change stream),注册完立即返回,连接长存直到显式 close()、\n * 客户端断开或 handler 抛错兜底。清理推送源用 `onClose(callback)`。\n * 默认 false(handler 返回时框架自动 close 兜底)。\n */\n keepOpen?: boolean;\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 /**\n * 注册流结束回调\n *\n * 显式 close、框架兜底 close、客户端断开、sendError 任一路径结束流时恰好触发\n * 一次(多个回调按注册顺序执行,回调自身抛错被忽略);注册时流已结束则立即触发。\n * keepOpen 模式下用于退订/清理推送源,防止客户端断开后订阅随重连累积泄漏。\n */\n onClose(callback: () => void): void;\n /** 流是否已关闭(handler 主动 close 或框架自动 close) */\n readonly closed: boolean;\n /** 客户端是否已断开(ReadableStream 被 cancel) */\n readonly aborted: boolean;\n /** 创建时是否声明了 `{ keepOpen: true }`(handler 返回后框架不自动 close) */\n readonly keepOpen: boolean;\n /**\n * 流缓冲背压状态(透传 controller.desiredSize)\n *\n * `null` = 流已关闭/断开;`<= 0` 表示消费慢于生产(缓冲超过高水位),生产循环\n * 应 `await waitForDrain()` 暂停,否则快生产者(LLM token 流)+ 慢客户端会让\n * 缓冲无界增长。`send`/`sendRaw` 保持同步不抛,背压响应由调用方自决。\n */\n readonly desiredSize: number | null;\n /**\n * 等待流缓冲排空(背压感知)\n *\n * 缓冲低于高水位(desiredSize > 0)时立即返回;否则挂起直到消费者拉取使缓冲\n * 排空(ReadableStream pull 钩子唤醒)、或流关闭/客户端断开(避免悬挂)。\n * 典型用法见 runtime/sse.md「背压」章节。\n */\n waitForDrain(): Promise<void>;\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 *\n * onFirstWrite:首次成功写入(send/sendRaw/sendError)时以 response 调用一次。\n * HTTP 服务场景由 ctx.sse() 接到 server 层的提前接管钩子——首次写入即把响应\n * 接入底层连接,流式期间字节即产即达(见 createServer.md「SSE 提前接管」)。\n *\n * keepOpen:声明后 writer.keepOpen 为 true,invokeHandler 在 handler 正常返回时\n * 跳过自动 close(长连接订阅模式);handler 抛错路径的兜底 close 不受影响。\n */\nexport function createSseWriter(\n options: SseOptions & { onFirstWrite?: (response: Response) => void } = {},\n): SseWriter {\n const { onFirstWrite, keepOpen = false } = options;\n const encoder = new TextEncoder();\n let controller: ReadableStreamDefaultController<Uint8Array> | null = null;\n let closed = false;\n let aborted = false;\n /** 首次写入回调是否已触发(幂等:多次写入只触发一次) */\n let firstWriteTriggered = false;\n /** waitForDrain 挂起中的等待方——消费者拉取(pull)/关闭/断开时唤醒 */\n let drainWaiters: Array<() => void> = [];\n /** onClose 注册的结束回调——任一路径结束流时逐个执行一次 */\n let closeCallbacks: Array<() => void> = [];\n /** 结束回调是否已执行(保证恰好一次:close/cancel 幂等路径不重复触发) */\n let closeCallbacksFired = false;\n\n const safeRunCallback = (callback: () => void): void => {\n try {\n callback();\n } catch {\n // 回调自身抛错不影响流收尾与其他回调(与 onError 钩子「自身抛错被忽略」同约定)\n }\n };\n\n const runCloseCallbacks = (): void => {\n if (closeCallbacksFired) return;\n closeCallbacksFired = true;\n const callbacks = closeCallbacks;\n closeCallbacks = [];\n for (const callback of callbacks) safeRunCallback(callback);\n };\n\n const resolveDrainWaiters = (): void => {\n const waiters = drainWaiters;\n drainWaiters = [];\n for (const resolve of waiters) resolve();\n };\n\n // 首次成功 enqueue 后触发(幂等)。放在 enqueue 之后:接管开始消费时首个\n // chunk 已在流中,字节顺序不因接管时机改变\n const triggerFirstWrite = (): void => {\n if (firstWriteTriggered) return;\n firstWriteTriggered = true;\n onFirstWrite?.(response);\n };\n\n // 高水位 16 个 chunk(SSE chunk 小):给消费者留缓冲余量,超过即 desiredSize <= 0,\n // 生产方可感知背压。无显式策略时默认 HWM=1,waitForDrain 过于激进\n const stream = new ReadableStream<Uint8Array>(\n {\n start(c) {\n controller = c;\n },\n // 消费者排空缓冲后再要数据 → 背压解除,唤醒 waitForDrain 等待方\n pull() {\n resolveDrainWaiters();\n },\n cancel() {\n // 客户端断开连接(cancel ReadableStream)\n aborted = true;\n closed = true;\n controller = null;\n // 结束回调与等待方立即放行(清理出口 + 生产循环靠 aborted 退出)\n runCloseCallbacks();\n resolveDrainWaiters();\n },\n },\n { highWaterMark: 16 },\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 // nginx 认到该头后按响应跳过 proxy_buffering;其他反代/客户端忽略,无副作用\n 'X-Accel-Buffering': 'no',\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 triggerFirstWrite();\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 triggerFirstWrite();\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 triggerFirstWrite();\n } finally {\n writer.close();\n }\n },\n\n close(): void {\n if (closed) return;\n closed = true;\n // 结束回调先于 controller.close:清理出口(退订/清定时器)在流收尾前执行\n runCloseCallbacks();\n // 关闭唤醒全部等待方(resolved 的 Promise 不会让生产循环悬挂)\n resolveDrainWaiters();\n if (controller) {\n try {\n controller.close();\n } catch {\n // controller 可能已关闭,忽略\n }\n controller = null;\n }\n },\n\n onClose(callback: () => void): void {\n // 流已结束(含 cancel 置位的 aborted 路径):立即触发,保证恰好一次语义\n if (closeCallbacksFired) {\n safeRunCallback(callback);\n return;\n }\n closeCallbacks.push(callback);\n },\n\n get closed(): boolean {\n return closed;\n },\n\n get desiredSize(): number | null {\n return controller ? controller.desiredSize : null;\n },\n\n waitForDrain(): Promise<void> {\n // 已关闭/断开:立即返回(生产循环自行感知 closed/aborted 退出)\n if (closed || !controller) return Promise.resolve();\n // 缓冲低于高水位:背压未触发,立即返回\n if ((controller.desiredSize ?? 0) > 0) return Promise.resolve();\n return new Promise<void>((resolve) => {\n drainWaiters.push(resolve);\n });\n },\n\n get aborted(): boolean {\n return aborted;\n },\n\n get keepOpen(): boolean {\n return keepOpen;\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 { markBufferedBody } from './bufferedBody';\nimport { FaapiError } from '../errors/FaapiError';\nimport { ValidationError, MethodNotAllowedError, PayloadTooLargeError } from '../errors/httpErrors';\nimport { stringifyJson } from '../utils/stringifyJson';\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,不包外层(BigInt 安全序列化,见 utils/stringifyJson) */\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 const response = new Response(stringifyJson(body), { status, headers });\n markBufferedBody(response);\n return response;\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 // 通用 fail 分支:FaapiError 家族共享同一 failFn 调用形态,差异只在附加 headers\n // 与额外字段——此前四个同构 if 各自复制 failFn 调用,收敛为此处单一实现\n const buildFailBody = (e: FaapiError): ReturnType<typeof failFn> =>\n failFn({ status: e.statusCode, code: e.code, message: e.message });\n\n if (error instanceof ValidationError) {\n const body = buildFailBody(error);\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 return jsonOk(buildFailBody(error), error.statusCode, {\n Allow: error.allowedMethods.join(', '),\n });\n }\n\n if (error instanceof PayloadTooLargeError) {\n return jsonOk(buildFailBody(error), error.statusCode);\n }\n\n if (error instanceof FaapiError) {\n return jsonOk(buildFailBody(error), 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 { LogEntry } from './loggerTypes';\n\n/**\n * 默认文本格式化(logger.md):`[ISO] LEVEL [scope] message fields-JSON`\n *\n * console sink 与文件 sink(fileSink.ts)共用同一格式——同一条日志在控制台与\n * 文件中内容一致,采集侧无需区分来源。独立成模块避免 fileSink ← logger 的\n * 循环依赖(logger.ts 编排 fileSink,fileSink 只依赖格式化)。\n */\n\n/** fields 值序列化:Error 实例展开为 { name, message, stack }(pino err serializer 惯例),其余原样交给 JSON.stringify */\nfunction serializeFieldValue(value: unknown): unknown {\n if (value instanceof Error) {\n return { name: value.name, message: value.message, stack: value.stack };\n }\n return value;\n}\n\n/** 默认文本格式:[ISO] LEVEL [scope] message fields-JSON */\nexport function formatEntry(entry: LogEntry): string {\n let line = `[${entry.time}] ${entry.level.toUpperCase()} `;\n if (entry.scope) line += `[${entry.scope}] `;\n line += entry.message;\n if (entry.fields !== undefined) {\n const plain = Object.fromEntries(\n Object.entries(entry.fields).map(([k, v]) => [k, serializeFieldValue(v)]),\n );\n try {\n line += ` ${JSON.stringify(plain)}`;\n } catch (err) {\n // 循环引用等不可序列化 fields:降级为提示文本,日志调用不抛错(fallback.md)\n line += ` [unserializable fields: ${err instanceof Error ? err.message : String(err)}]`;\n }\n }\n return line;\n}\n","import type {\n LogConfig,\n LogEntry,\n LogLevel,\n LogSink,\n Logger,\n CreateLoggerOptions,\n} from './loggerTypes';\nimport { formatEntry } from './formatEntry';\nimport { createFileLogSink, type FileLogSinkHandle } from './fileSink';\n\n/**\n * 框架级结构化日志器(logger.md)——参考 egg.js 的单管道模型\n *\n * 一条日志管道、两个输出出口,各有独立阈值:\n * - 管道出口(文件 `dir` / 自定义 `sink`):`level` 阈值,不配不过滤(全量条目,\n * 分流由文件布局或下游 sink 决定)\n * - console 出口:`consoleLevel` 阈值,不配 `'info'`(`false` 关闭 console)\n *\n * 请求日志(middleware/logger.ts)无条件并入管道(`accessLog: false` 关闭),\n * `config.logger` 独立配置已废除——单一 `config.log` 入口管全部输出。\n *\n * 全局配置为进程级资源(stdout/文件本就进程唯一),由 `configureLogging` 设置\n * (createAppBase 启动时读 config.log 调用),多 app 同进程后启动覆盖先启动;\n * 状态经 globalThis 承载跨模块实例共享(dev 下 CLI bundle 与主入口是两份模块副本,\n * 模块级变量会导致业务侧 createLogger 看不到 CLI 侧配置的管道)。\n *\n * 日志调用永不抛错:fields 序列化失败降级为提示文本(fallback.md)。\n */\n\nconst LEVEL_RANK: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\n\nconst VALID_LEVELS: readonly LogLevel[] = ['debug', 'info', 'warn', 'error'];\n\nfunction assertLogLevel(value: unknown, source: string): asserts value is LogLevel {\n if (typeof value !== 'string' || !VALID_LEVELS.includes(value as LogLevel)) {\n throw new Error(\n `[faapi] Invalid log level from ${source}: ${String(value)}. Expected one of: ${VALID_LEVELS.join(', ')}.`,\n );\n }\n}\n\n/**\n * 全局日志管道状态\n *\n * level 为 null 表示未配置——管道出口不过滤(全量条目进文件/sink)。\n * consoleLevel 为 null 表示未配置——console 出口默认 'info';false 关闭 console。\n * sink/fileSink 互斥(configureLogging 校验)。disabled 对应 `config.log: false`\n * (管道与请求日志全部静默)。\n */\ninterface LoggingState {\n level: LogLevel | null;\n consoleLevel: LogLevel | false | null;\n sink: LogSink | null;\n fileSink: FileLogSinkHandle | null;\n stdout: boolean;\n accessLog: boolean;\n disabled: boolean;\n}\n\n/**\n * 管道状态的 globalThis key(`Symbol.for` 创建,跨模块实例命中同一键)\n *\n * 管道状态是进程级资源(stdout/文件本就进程唯一),但不能放模块级变量——dev 模式下\n * 框架存在两个模块实例:`faapi` CLI 跑在 `dist/cli/index.js`(tsup 打包内联框架代码),\n * 业务模块经包主入口加载 `dist/index.js`,两份副本 module cache 独立。模块级变量会让\n * `configureLogging`(CLI 侧 createAppBase 调用)只作用于 CLI 副本,业务 `createLogger`\n * (主入口副本)看到的 fileSink 恒为 null,dev 下 `config.log.dir` 的业务日志不落盘。\n * 与 `getApp` 单例同模式:globalThis + `Symbol.for` 键跨副本共享,prod(单实例)行为无变化。\n */\nconst LOGGING_STATE_KEY = Symbol.for('faapi.logger.state');\n\nfunction createDefaultLoggingState(): LoggingState {\n return {\n level: null,\n consoleLevel: null,\n sink: null,\n fileSink: null,\n stdout: true,\n accessLog: true,\n disabled: false,\n };\n}\n\n/** 全局日志管道状态(globalThis 承载,跨模块实例共享;首次访问时懒初始化) */\nfunction getLoggingState(): LoggingState {\n const g = globalThis as Record<symbol, LoggingState | undefined>;\n let state = g[LOGGING_STATE_KEY];\n if (!state) {\n state = createDefaultLoggingState();\n g[LOGGING_STATE_KEY] = state;\n }\n return state;\n}\n\n/**\n * 设置全局日志配置(进程级,`createAppBase` 启动时以 `config.log` 调用)\n *\n * - `false`:全部静默(含 error 与请求日志,测试降噪场景)\n * - `true` / `{}`:console 出口默认 info,管道出口不过滤,无文件输出\n * - `{ level, consoleLevel, sink, dir, stdout, accessLog }`:egg 模型精细配置\n * (level 管管道出口、consoleLevel 管 console 出口;sink 与 dir 互斥)\n * - `undefined`:重置为默认(编程式多 app 切换全局配置用)\n *\n * LOG_LEVEL env 仅作为管道 level 的环境来源(consoleLevel 无 env)——本函数是\n * env 读取点,非法值启动期报错(fail fast)。\n */\nexport function configureLogging(config?: LogConfig | boolean | undefined): void {\n const state = getLoggingState();\n const objectConfig = typeof config === 'object' ? config : {};\n if (objectConfig.sink && objectConfig.dir) {\n throw new Error(\n '[faapi] config.log.sink and config.log.dir are mutually exclusive: pick one output pipeline (custom sink OR built-in file logs).',\n );\n }\n // 旧文件流先关(切配置/重置不泄漏 fd;close 异步收尾,不影响新配置立即生效)\n void state.fileSink?.close();\n\n state.disabled = config === false;\n state.sink = objectConfig.sink ?? null;\n state.accessLog = objectConfig.accessLog ?? true;\n state.stdout = objectConfig.stdout ?? true;\n state.fileSink = objectConfig.dir\n ? createFileLogSink({ dir: objectConfig.dir, splitByLevel: objectConfig.splitByLevel })\n : null;\n if (objectConfig.consoleLevel !== undefined) {\n if (objectConfig.consoleLevel !== false) {\n assertLogLevel(objectConfig.consoleLevel, 'config.log.consoleLevel');\n }\n state.consoleLevel = objectConfig.consoleLevel;\n } else {\n state.consoleLevel = null;\n }\n if (objectConfig.level !== undefined) {\n assertLogLevel(objectConfig.level, 'config.log.level');\n state.level = objectConfig.level;\n } else if (process.env.LOG_LEVEL !== undefined) {\n assertLogLevel(process.env.LOG_LEVEL, 'LOG_LEVEL env');\n state.level = process.env.LOG_LEVEL as LogLevel;\n } else {\n state.level = null;\n }\n}\n\n/**\n * 管道阈值(文件/sink 收到的条目);undefined = 不过滤(未配置 level 时全量放行,\n * 分流由文件布局或下游 sink 决定)\n */\nfunction pipelineThreshold(): LogLevel | undefined {\n return getLoggingState().level ?? undefined;\n}\n\n/** console 出口阈值;false = console 关闭 */\nfunction consoleThreshold(): LogLevel | false {\n return getLoggingState().consoleLevel ?? 'info';\n}\n\n/** 阈值判定(threshold undefined = 不过滤) */\nfunction belowThreshold(level: LogLevel, threshold: LogLevel | undefined): boolean {\n return threshold !== undefined && LEVEL_RANK[level] < LEVEL_RANK[threshold];\n}\n\n/**\n * 当前生效的管道级别(任务隔离派发用:级别随 workerData 下发做 worker 侧预过滤,\n * 宿主侧 writeLogEntry 再次过滤——两级一致,派发后级别变更以宿主为准;\n * undefined = 不过滤,worker 侧同样放行全量)\n */\nexport function getEffectiveLogLevel(): LogLevel | undefined {\n return pipelineThreshold();\n}\n\nconst CONSOLE_METHOD: Record<LogLevel, 'debug' | 'info' | 'warn' | 'error'> = {\n debug: 'debug',\n info: 'info',\n warn: 'warn',\n error: 'error',\n};\n\n/** console 输出(对应级别方法,warn/error 走 stderr),受 consoleLevel 阈值过滤 */\nfunction consoleDispatch(entry: LogEntry): void {\n const threshold = consoleThreshold();\n if (threshold === false || belowThreshold(entry.level, threshold)) return;\n console[CONSOLE_METHOD[entry.level]](formatEntry(entry));\n}\n\n/**\n * 全局管道:disabled 检查 + 出口解析(各出口独立阈值——egg transport 模型)\n *\n * 优先级:全局 sink(显式接管,受 `level`,不经 console)→ 文件管道(`dir`,\n * 受 `level`;stdout 非 false 时 console 双写,受 `consoleLevel`)→ 纯 console\n * (受 `consoleLevel`)。`level` 与 `consoleLevel` 互不牵扯:文件可只存 error\n * 而.console 看全部。\n */\nfunction globalDispatch(entry: LogEntry): void {\n const state = getLoggingState();\n if (state.disabled) return;\n const pipeThreshold = pipelineThreshold();\n if (state.sink) {\n if (!belowThreshold(entry.level, pipeThreshold)) state.sink(entry);\n return;\n }\n if (state.fileSink) {\n if (!belowThreshold(entry.level, pipeThreshold)) state.fileSink.write(entry);\n if (state.stdout) consoleDispatch(entry);\n return;\n }\n consoleDispatch(entry);\n}\n\n/**\n * 写一条预构建的日志条目(直达全局管道,各出口按自身阈值过滤)\n *\n * 任务隔离执行桥接用:worker 内联日志器把条目作为纯数据 postMessage 回传宿主,\n * 宿主侧经本函数走统一管道(自定义 sink / 文件管道同样覆盖隔离任务)。\n */\nexport function writeLogEntry(entry: LogEntry): void {\n globalDispatch(entry);\n}\n\n/**\n * 刷盘文件日志缓冲(不结束流,flush 后可继续写)\n *\n * 优雅停机场景在 `lifecycle.onClose` 调用,确保缓冲中的条目落盘后再退出进程;\n * 测试场景断言文件内容前调用。无文件管道(未配置 `config.log.dir`)时为 no-op。\n */\nexport function flushLogging(): Promise<void> {\n const state = getLoggingState();\n return state.fileSink ? state.fileSink.flush() : Promise.resolve();\n}\n\n/**\n * 请求日志是否并入管道(middleware/logger.ts 默认输出决策用)\n *\n * 缺省并入(egg 模型:访问日志与业务日志同管道);`config.log.accessLog: false`\n * 或 `config.log: false` 时请求日志不输出。\n */\nexport function isAccessLogEnabled(): boolean {\n const state = getLoggingState();\n return !state.disabled && state.accessLog;\n}\n\n/**\n * 创建日志器\n *\n * @param scope 分类标签(如 'db'、'http'),默认无分类\n * @param options 实例级覆盖(level/sink/构造 fields)\n */\nexport function createLogger(scope?: string, options?: CreateLoggerOptions): Logger {\n const baseScope = scope;\n const baseFields = options?.fields;\n\n const write = (\n level: LogLevel,\n message: string,\n callFields: Record<string, unknown> | undefined,\n scopeSuffix: string | undefined,\n ): void => {\n // 实例级 level 是该 logger 的出口阈值;全局阈值在各出口独立判定(egg transport 模型)\n if (belowThreshold(level, options?.level)) return;\n const entry: LogEntry = {\n level,\n message,\n time: new Date().toISOString(),\n };\n const fullScope =\n scopeSuffix === undefined\n ? baseScope\n : baseScope === undefined\n ? scopeSuffix\n : `${baseScope}:${scopeSuffix}`;\n if (fullScope !== undefined) entry.scope = fullScope;\n if (baseFields !== undefined || callFields !== undefined) {\n entry.fields = { ...baseFields, ...callFields };\n }\n if (options?.sink) {\n // 实例级 sink:显式接管输出管道,不受全局 disabled / consoleLevel 影响\n options.sink(entry);\n } else {\n globalDispatch(entry);\n }\n };\n\n const makeLogger = (scopeSuffix: string | undefined): Logger => ({\n debug: (message, fields) => write('debug', message, fields, scopeSuffix),\n info: (message, fields) => write('info', message, fields, scopeSuffix),\n warn: (message, fields) => write('warn', message, fields, scopeSuffix),\n error: (message, fields) => write('error', message, fields, scopeSuffix),\n child: (childScope) =>\n makeLogger(scopeSuffix === undefined ? childScope : `${scopeSuffix}:${childScope}`),\n });\n\n return makeLogger(undefined);\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 */\n/**\n * 同一 URLSearchParams 实例的解析结果缓存\n *\n * GET 请求每请求会解析两次 query(resolveInput 组装 ctx 输入 + injectParams 注入\n * `query` 参数)——ctx.query 是 createContext 创建的同一 URLSearchParams 实例,\n * WeakMap 按实例缓存后第二次解析直接命中。实例随请求被 GC,无跨请求泄漏。\n */\nconst queryCache = new WeakMap<URLSearchParams, Record<string, unknown>>();\n\nexport function queryToObject(params: URLSearchParams): Record<string, unknown> {\n const cached = queryCache.get(params);\n if (cached) return cached;\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 queryCache.set(params, result);\n return result;\n}\n","import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { isInsideDir, toRealPath } from './prodPaths';\n\n/**\n * 当前 app 的 resources 目录绑定(进程级,globalThis + Symbol.for 承载)\n *\n * 为什么用 globalThis 而非模块级变量(同 appSingleton 的取舍):\n * ① Next.js Turbopack dev runtime 与主进程是两套 module cache,须跨实例共享;\n * ② 任务隔离 worker 的入口 bundle(workerEntry.ts)与 index bundle 是两份代码\n * 副本,由入口内联同名 symbol key 播种(两处字面量需一致,契约测试锚定)。\n * 单 app 强制(createAppBase 检测到存活 app 即抛错)保证绑定唯一且无歧义。\n */\nconst ACTIVE_RESOURCES_DIR_KEY = Symbol.for('faapi.resources.dir');\n\nfunction getActiveResourcesDir(): string | null {\n return (globalThis as Record<symbol, string | undefined>)[ACTIVE_RESOURCES_DIR_KEY] ?? null;\n}\n\n/**\n * 绑定/解绑当前 app 的 resources 目录(框架内部接线,不进公开导出面)\n *\n * 调用点:createAppBase 启动时绑定、app.close() 解绑、taskWorker 从快照播种\n * (内联 globalThis 写入)、createTestContext 按选项绑定(测试 handler 读资源)。\n */\nexport function setActiveResourcesDir(dir: string | null): void {\n if (dir === null) {\n delete (globalThis as Record<symbol, string | undefined>)[ACTIVE_RESOURCES_DIR_KEY];\n } else {\n (globalThis as Record<symbol, string | undefined>)[ACTIVE_RESOURCES_DIR_KEY] = dir;\n }\n}\n\n/**\n * 校验 relativePath 不越出 resources 目录,返回实际可读的目标绝对路径\n *\n * 两层校验:\n * 1. 字符串层——path.resolve 后必须仍在 resources 内(拦绝对路径与 `..` 穿越)\n * 2. 符号链接层——目标真实路径(realpath)必须仍在 resources 内(拦 symlink 指向\n * 目录外);目标不存在时 toRealPath 回退原路径,缺失由 readFile 抛自然 ENOENT\n */\nfunction resolveResourceTarget(resourcesDir: string, relativePath: string): string {\n if (typeof relativePath !== 'string' || relativePath === '') {\n throw new Error('[faapi] readResource: relativePath must be a non-empty string');\n }\n\n const root = toRealPath(path.resolve(resourcesDir));\n const resolved = path.resolve(root, relativePath);\n if (!isInsideDir(resolved, root)) {\n throw new Error(\n `[faapi] readResource: \"${relativePath}\" escapes the resources directory (absolute paths and \"..\" are not allowed)`,\n );\n }\n\n const realTarget = toRealPath(resolved);\n if (!isInsideDir(realTarget, root)) {\n throw new Error(\n `[faapi] readResource: \"${relativePath}\" resolves outside the resources directory via a symlink`,\n );\n }\n return realTarget;\n}\n\n/**\n * 读取当前 app 的运行时资源内容(参数为相对路径,只能读 resources 内的文件)\n *\n * 唯一读取入口:HTTP/WS handler、任务、插件、lifecycle 钩子、编程式调用全部用\n * 本函数——resources 目录在 app 启动时绑定(createAppBase),调用方不传路径根。\n * 越界校验见 resolveResourceTarget——绝对路径、`..` 穿越、符号链接逃逸均显式抛错,\n * resources 内部的合法软链不误伤。\n */\nexport function readResource(relativePath: string): Promise<Buffer>;\nexport function readResource(relativePath: string, encoding: BufferEncoding): Promise<string>;\nexport async function readResource(\n relativePath: string,\n encoding?: BufferEncoding,\n): Promise<Buffer | string> {\n const resourcesDir = getActiveResourcesDir();\n if (!resourcesDir) {\n throw new Error(\n '[faapi] readResource: no active app in this process — resources are bound at app startup (createAppBase); testing direct-call can pass `resourcesDir` to createTestContext to bind',\n );\n }\n const target = resolveResourceTarget(resourcesDir, relativePath);\n return encoding === undefined ? fs.readFile(target) : fs.readFile(target, encoding);\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 { FaapiContext, ResponseMeta, CookieOptions, FailOptions } from './contextTypes';\nimport { markBufferedBody } from '../response/bufferedBody';\nimport { createSseWriter, type SseOptions, type SseWriter } from './sse';\nimport { wrapOkResult, formatFailResponse, jsonOk } from '../response/responseFormatter';\nimport { createLogger } from '../logger/logger';\nimport { queryToObject } from '../utils/queryToObject';\nimport { setActiveResourcesDir } from '../utils/readResource';\n\n/**\n * 解析请求 ID:请求头 `x-request-id` 第一段(逗号分隔取首段,网关透传场景跨服务\n * 串联),缺失或空白时 crypto.randomUUID() 生成(参考 Fastify genReqId 默认语义)\n */\nfunction resolveRequestId(request: Request): string {\n const header = request.headers.get('x-request-id');\n const first = header?.split(',')[0]?.trim();\n if (first) return first;\n return crypto.randomUUID();\n}\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 resourcesDir?: string,\n): FaapiContext {\n return createContextFromUrl(\n request,\n new URL(request.url),\n params,\n config,\n ip,\n registries,\n resourcesDir,\n );\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 resourcesDir?: string,\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 // 请求级日志器:scope http,字段自动携带 requestId/method/path(业务日志与请求\n // 日志经 requestId 关联);每请求构造一次,child 派生更细分类时字段继承\n const requestId = resolveRequestId(request);\n const log = createLogger('http', {\n fields: { requestId, method: request.method, path: url.pathname },\n });\n\n const ctx = {\n requestId,\n log,\n request,\n params,\n rawParams: params,\n // ctx.query 为对象口径(createContext 挂原始 string 值对象;handler 声明\n // query 类型时管线校验后替换为转换值)。原始 URLSearchParams 恒在 rawQuery。\n query: queryToObject(url.searchParams),\n rawQuery: 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 // 运行时资源根目录(HTTP/WS 链路由 createServer/handleWsUpgrade 传入;\n // testing 直调未传时为 undefined,见 contextTypes.resourcesDir 注释)\n resourcesDir,\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 const response = new Response(html, {\n status: status ?? 200,\n headers,\n });\n markBufferedBody(response);\n return response;\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 推送事件。HTTP 服务场景下首次\n * 写入(send/sendRaw/sendError)即触发 server 层提前接管钩子,把 writer.response\n * 接入底层连接——流式期间字节即产即达(见 createServer.md「SSE 提前接管」)。\n * 无钩子场景(app.inject / 测试直调 invokeHandler)回落为 handler 返回后交出。\n *\n * `{ keepOpen: true }` 声明流生命周期独立于 handler 返回:handler 返回后框架\n * 不自动 close(长连接订阅模式,清理推送源用 writer.onClose);handler 抛错\n * 的兜底 close 不受影响。详见 runtime/sse.md「长连接订阅模式」。\n *\n * 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。\n */\n sse(options?: SseOptions): SseWriter {\n const writer = createSseWriter({\n keepOpen: options?.keepOpen,\n onFirstWrite: (response) => {\n (\n ctx as FaapiContext & { __earlyRespond?: (response: Response) => void }\n ).__earlyRespond?.(response);\n },\n });\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 // 任务客户端:同一 app 实例的 TaskClient(入队/查询),ctx.tasks 与\n // `tasks` 参数注入 / app.tasks 指向同一队列\n ctx.tasks = registries.taskHandle.get(ctx) as FaapiContext['tasks'];\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 resourcesDir,\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 // 绑定全局 readResource 读取根(免传参 readResource 依赖进程级绑定):测试\n // handler 调 readResource('x.md') 时按此绑定解析。测试内多次构造后调覆盖先调;\n // 真实 app 的绑定/解绑由 createAppBase / app.close() 接线\n if (resourcesDir) {\n setActiveResourcesDir(resourcesDir);\n }\n\n return createContext(request, params, config, ip, registries, resourcesDir);\n}\n\n/**\n * createTestContext 的选项\n */\nexport interface CreateTestContextOptions {\n /** app 级注册表(测试 handler 声明 agent/agents 参数时注入用,可选) */\n registries?: FaapiContext['registries'];\n /**\n * 运行时资源根目录(可选)\n *\n * 传入时同时做两件事:绑定全局 readResource 读取根(handler 直调\n * `readResource('x.md')` 依赖)+ 挂到 ctx.resourcesDir 数据字段\n */\n resourcesDir?: string;\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","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 { stringifyJson } from '../utils/stringifyJson';\nimport type { ResponseMeta } from '../runtime/contextTypes';\nimport { deferMetaHeaders, isHeadersOnlyMeta } from './pendingMeta';\nimport { markBufferedBody } from './bufferedBody';\n\n/**\n * 将 handler 返回值统一转换为 Response\n *\n * 规则:\n * - Response 对象:原样返回\n * - ReadableStream:直接作为 body\n * - Buffer / Uint8Array:作为二进制 body\n * - 普通对象/数组:JSON.stringify(BigInt 安全,BigInt → 字符串),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 markBuffered(\n new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n }),\n );\n }\n if (value instanceof Uint8Array) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return markBuffered(\n new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n }),\n );\n }\n\n // 普通对象/数组:JSON.stringify(BigInt 安全,BigInt → 字符串),Content-Type: application/json\n if (isPlainObject(value) || Array.isArray(value)) {\n const body = stringifyJson(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return markBuffered(\n new Response(body, {\n status: meta?.status ?? 200,\n headers,\n }),\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 markBuffered(\n new Response(value, {\n status: meta?.status ?? 200,\n headers,\n }),\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 markBuffered(\n new Response(String(value), {\n status: meta?.status ?? 200,\n headers,\n }),\n );\n }\n\n // 其他类型 fallback:JSON.stringify(BigInt 安全)\n const body = stringifyJson(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return markBuffered(\n new Response(body, {\n status: meta?.status ?? 200,\n headers,\n }),\n );\n}\n\n/** 标记缓冲型 body 并原样返回(发送层快路径直写,见 bufferedBody.ts) */\nfunction markBuffered(response: Response): Response {\n markBufferedBody(response);\n return response;\n}\n","import ts from 'typescript';\n\n/**\n * 参数注入类型\n *\n * Phase 2.3 扩展 `agent` / `agents`:\n * - `agent` —— AgentHandle(由 `@faapi/agent` 插件工厂注入;调用时需显式传\n * `options.agent` 指定 agent 名,无默认 agent)\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 | 'tasks'\n | 'log'\n | 'rawQuery'\n | 'rawParams'\n | 'rawBody'\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 tasks: 'tasks', // 任务子系统:TaskClient(入队/查询)\n log: 'log', // 请求级日志器(ctx.log 同一实例:scope http,自动带 requestId 等字段)\n // raw 系:恒原始输入(无 schema、无校验、管线永不转换),与 query/params/body\n // 的「校验转换后」口径二分互补\n rawQuery: 'rawQuery', // URLSearchParams(原始查询参数)\n rawParams: 'rawParams', // Record<string, string>(URL 原始路径段)\n rawBody: 'rawBody', // string | undefined(请求体原始文本)\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","import type { TaskMetadata } from './taskTypes';\n\n/**\n * app 实例级任务注册表(与 tool/agent/skill registry 同构,方案 A 实例化)\n *\n * 任务清单来自编译期产物(faapi-tasks.js),reload 时整体替换(hydrate 语义)。\n * 每个 app 持有独立实例,多 app 同进程互不串台;详见 taskRegistry.md。\n */\nexport interface TaskRegistry {\n /** 全量替换(任务清单来自编译期产物,reload 时整体重新生成) */\n hydrate(tasks: TaskMetadata[]): void;\n /** 按任务名查找 */\n get(name: string): TaskMetadata | undefined;\n /** 所有已注册任务(副本) */\n list(): TaskMetadata[];\n clear(): void;\n}\n\nexport function createTaskRegistry(): TaskRegistry {\n let registry = new Map<string, TaskMetadata>();\n return {\n hydrate(tasks) {\n const next = new Map<string, TaskMetadata>();\n for (const task of tasks) {\n next.set(task.name, task);\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","import type { ToolMetadata } from '../ast/extractToolMetadata';\nimport type { AgentMetadata, AgentCore } from '../ast/extractAgentMetadata';\nimport type { FaapiContext } from '../runtime/contextTypes';\nimport type { TaskRegistry } from '../task/taskRegistry';\nimport { createTaskRegistry } from '../task/taskRegistry';\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,声明文件定位) */\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// ─── Task handle 工厂 ────────────────────────────────────────────────\n\n/** task 客户端工厂函数(由 createAppBase 注册,返回 TaskClient 门面) */\nexport type TaskHandleFactory = (ctx: FaapiContext) => unknown;\n\nexport interface TaskHandleStore {\n /** 注册工厂(null 清理);二次注册覆盖 */\n register(factory: TaskHandleFactory | null): void;\n /** 工厂已注册时返回 TaskClient,未注册返回 undefined */\n get(ctx: FaapiContext): unknown;\n clear(): void;\n}\n\nexport function createTaskHandleStore(): TaskHandleStore {\n let currentFactory: TaskHandleFactory | 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 task: TaskRegistry;\n agentHandle: AgentHandleStore;\n taskHandle: TaskHandleStore;\n}\n\n/**\n * 任务侧注册表只读视图(AppRegistries 的查询投影)\n *\n * 注入任务执行上下文(`TaskContext.registries`)——任务执行侧不在 handler 请求链路上\n * (拿不到 `FaapiContext.registries`),`getApp()` 在隔离 worker 线程内也不可用\n * (globalThis 独立)。刻意不暴露 `hydrate`/`clear` 写接口:任务不是注册表的所有者。\n */\nexport interface TaskRegistriesView {\n agent: Pick<\n AgentRegistry,\n | 'getAgent'\n | 'getAgentEntry'\n | 'listAgents'\n | 'asTool'\n | 'resolveAgentTools'\n | 'resolveSubAgents'\n >;\n tool: Pick<ToolRegistry, 'get' | 'list'>;\n skill: Pick<SkillRegistry, 'get' | 'list'>;\n}\n\n/** 从 AppRegistries 构造任务侧只读视图(活引用——进程内执行路径直接使用) */\nexport function createTaskRegistriesView(registries: AppRegistries): TaskRegistriesView {\n return {\n agent: {\n getAgent: (name) => registries.agent.getAgent(name),\n getAgentEntry: (name) => registries.agent.getAgentEntry(name),\n listAgents: () => registries.agent.listAgents(),\n asTool: (name) => registries.agent.asTool(name),\n resolveAgentTools: (name) => registries.agent.resolveAgentTools(name),\n resolveSubAgents: (name) => registries.agent.resolveSubAgents(name),\n },\n tool: {\n get: (name) => registries.tool.get(name),\n list: () => registries.tool.list(),\n },\n skill: {\n get: (name) => registries.skill.get(name),\n list: () => registries.skill.list(),\n },\n };\n}\n\n/** 空视图(`createTaskQueue` 未传 registries 时的兜底——直接构造队列的测试/嵌入场景) */\nexport function createEmptyTaskRegistriesView(): TaskRegistriesView {\n return {\n agent: {\n getAgent: () => undefined,\n getAgentEntry: () => undefined,\n listAgents: () => [],\n asTool: () => undefined,\n resolveAgentTools: () => [],\n resolveSubAgents: () => [],\n },\n tool: { get: () => undefined, list: () => [] },\n skill: { get: () => undefined, list: () => [] },\n };\n}\n\n/** 创建一套 app 级注册表(`createAppBase` 每次调用创建独立实例) */\nexport function createAppRegistries(): AppRegistries {\n const tool = createToolRegistry();\n const agent = createAgentRegistry(tool);\n const skill = createSkillRegistry();\n const task = createTaskRegistry();\n const agentHandle = createAgentHandleStore();\n const taskHandle = createTaskHandleStore();\n return { tool, agent, skill, task, agentHandle, taskHandle };\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)。\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)\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 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 // ctx.query 与管线校验挂载的是同一对象:声明 query 类型时为转换值\n //(number/boolean 字段已 coerce,未声明字段保留原始字符串),未声明时\n // 为原始 query 对象(createContext 挂载后管线不触及)\n return ctx.query;\n case 'params':\n // 路径参数:管线已按声明类型校验并回写 ctx.params(转换后的值),\n // 未声明 params 类型的路由保持原始字符串\n return ctx.params;\n // raw 系:恒原始,管线永不覆盖(ctx 字段即注入值)\n case 'rawQuery':\n return ctx.rawQuery;\n case 'rawParams':\n return ctx.rawParams;\n case 'rawBody':\n return ctx.rawBody;\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 // 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined\n case 'tasks':\n return ctx.registries ? ctx.registries.taskHandle.get(ctx) : undefined;\n // 请求级日志器:与 ctx.log 同一实例(scope http,自动带 requestId/method/path 字段)\n case 'log':\n return ctx.log;\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 * keepOpen 声明豁免返回路径的自动 close——流生命周期独立于 handler 返回\n * (长连接订阅模式,handler 注册完推送源即返回,连接长存);handler 抛错\n * 路径(autoCloseSseOnError)不受 keepOpen 影响,仍无条件关闭。\n */\n const pickSseAndAutoClose = (): Response | null => {\n const sseWriter = (ctx as FaapiContext & { __sseWriter?: SseWriter }).__sseWriter;\n if (!sseWriter) return null;\n // 兜底:handler 未显式 close 时自动关闭(keepOpen 豁免)\n if (!sseWriter.keepOpen && !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 的统一尾部:注入 → SSE 优先 → 自动包装。\n // 无中间件与有中间件(洋葱 finalHandler)此前是两份相同逻辑的复制,收敛于此\n const executeAndFormat = 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 // 无中间件:直接执行 handler\n if (!middlewares || middlewares.length === 0) {\n return executeAndFormat();\n }\n\n // 有中间件:洋葱模型调度\n const finalHandler = executeAndFormat;\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 { 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 /** 全局中间件(外层洋葱) */\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,避免污染断言)\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 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,避免污染断言)\n const { server } = createServer({\n routes: sorted,\n rootDir,\n dist: schemaDist,\n cors,\n helmet,\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 fs from 'node:fs';\nimport { 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 * bustViteCache 分支的模块实例缓存:(filePath, mtime) → 模块\n *\n * 每请求 `?t=${Date.now()}` 新建 URL 会让 ESM 注册表为同一文件积累永不回收的模块\n * 实例(内存无界增长),且下游按模块对象做的 WeakMap 缓存(injection 分析等)\n * 永久 miss,每请求重跑 AST 解析。按 mtime 缓存后:同文件同 mtime 复用同一实例,\n * 文件重编译(mtime 变化)自动失效换新,无需显式清理(条目按路径收敛于项目规模)。\n */\nconst bustCache = new Map<string, { mtimeMs: number; module: Record<string, unknown> }>();\n\n/** cache-bust 单调计数:同毫秒内多次失效加载也要生成不同 URL(Date.now 精度不足) */\nlet bustCounter = 0;\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 * (filePath, mtime) 缓存复用:同文件同 mtime 返回同一实例(下游 WeakMap 缓存可命中),\n * 文件重编译后自动失效。\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 // 按 (filePath, mtime) 复用模块实例——每请求新建 URL 是内存泄漏 + 缓存击穿\n let mtimeMs = 0;\n try {\n mtimeMs = fs.statSync(filePath).mtimeMs;\n } catch {\n // stat 失败按 mtime 0 走 import,模块加载按路径抛真实错误\n }\n const cached = bustCache.get(filePath);\n if (cached && cached.mtimeMs === mtimeMs) {\n return cached.module;\n }\n let url = pathToFileURL(filePath).href;\n url += `?t=${Date.now()}-${++bustCounter}`;\n const module = (await import(url)) as Record<string, unknown>;\n bustCache.set(filePath, { mtimeMs, module });\n return module;\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 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 type { RouteManifest, WsRouteManifest } from '../router/routeTypes';\nimport type { RouteSchemaSource } from './collectRouteSchemaSources';\nimport type { RuntimeType } from '../ast/resolveTypeNode';\nimport { collectRouteSchemaSources } from './collectRouteSchemaSources';\nimport {\n generateZodSchemaSourceParts,\n usesCoerceHelpers,\n HELPERS_FILENAME,\n} from '../ast/generateZodSchema';\nimport { generateZodArtifacts } from './generateZodArtifacts';\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 * @param wsRoutes WS 路由清单(可选)——为声明了约定 interface(Query/Params)的\n * WS 路由生成 WSQuery / WSParams schema(握手校验转换用)\n */\nexport async function generateSchemaFiles(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n wsRoutes?: WsRouteManifest,\n): Promise<void> {\n if (routes.length === 0 && (wsRoutes?.length ?? 0) === 0) return;\n\n const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir, wsRoutes);\n\n await generateZodArtifacts(sources, {\n generateFileSource: generateSchemaFileSource,\n resolversByFile,\n rootDir,\n dist,\n });\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 * `sourceFile` 可选:未触达 checker 的纯语法遍历路径(binder 未运行)没有\n * parent 指针,`node.getSourceFile()` 返回 undefined,此时调用方需显式传入\n * `program.getSourceFile(filePath)` 的结果以携带位置。\n */\n static at(\n node: ts.Node,\n typeText: string,\n reason: string,\n sourceFile?: ts.SourceFile,\n ): SchemaExtractionError {\n const sf = sourceFile ?? node.getSourceFile();\n if (!sf) {\n return new SchemaExtractionError(typeText, reason);\n }\n const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));\n return new SchemaExtractionError(typeText, reason, undefined, {\n file: sf.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, hasBody } from '../runtime/inputType';\nimport { getSchemaName } from '../validator/schemaName';\nimport { analyzeInjectionInSourceFile } from '../injection/analyzeInjection';\nimport type { RouteManifest, WsRouteManifest } from '../router/routeTypes';\nimport path from 'node:path';\nimport ts from 'typescript';\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 */\n/**\n * 查找源文件中导出的约定命名类型(interface / type alias)\n *\n * WS handler 的输入声明是文件级约定(WS 函数形参是 ctx,不携带类型名):\n * handler.ts 导出 `WS` 且同文件声明 `export interface Query` / `export interface\n * Params`(type alias 同样生效)时,为该 WS 路由生成 WSQuery / WSParams schema。\n * 与 HTTP 导出共存时共享同一 interface(同文件同源)。\n */\nfunction findExportedTypeName(sourceFile: ts.SourceFile, name: string): boolean {\n for (const statement of sourceFile.statements) {\n const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : undefined;\n const isExported = modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);\n if (!isExported) continue;\n if (\n (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) &&\n statement.name?.text === name\n ) {\n return true;\n }\n }\n return false;\n}\n\nexport function collectRouteSchemaSources(\n routes: RouteManifest,\n rootDir?: string,\n wsRoutes?: WsRouteManifest,\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 // WS 路由文件:约定 interface(Query/Params)收集进同一批 Program(WS-only\n // 文件不在 HTTP 集合里,需要单独建 Program 与解析器)\n const wsFiles: Array<{ filePath: string; urlPath: string }> = [];\n for (const wsRoute of wsRoutes ?? []) {\n wsFiles.push({\n filePath: rootDir ? path.resolve(rootDir, wsRoute.filePath) : wsRoute.filePath,\n urlPath: wsRoute.urlPath,\n });\n }\n\n // 批量共享 Program:同一次提取只创建一个 Program,避免逐文件全量解析\n const allFiles = [...new Set([...methodsByFile.keys(), ...wsFiles.map((f) => f.filePath)])];\n const programByFile = createPrograms(allFiles);\n\n // 每个文件一个惰性解析器:入口类型立即解析,ref 按需解析(缓存幂等)\n const resolversByFile = new Map<string, LazyTypeResolver>();\n for (const filePath of allFiles) {\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 // params 形参:handler 声明了 `params: XxxParams` 时为每个方法生成\n // <METHOD>Params schema(GETParams / POSTParams / ...),运行时据此校验\n // 路径参数并把 coerce 后的值回写 ctx.params。路径段来源均为 string,\n // coerce 由命名后缀正则推断(Params → true)。未声明则不生成 schema,\n // 运行时跳过校验,ctx.params 保持原始字符串(与未声明行为一致)。\n const paramsArg = meta.params.find((p) => p.type === 'params');\n if (paramsArg?.typeName) {\n sources.push({\n urlPath: entry.urlPath,\n filePath,\n schemaName: getSchemaName(method, 'params'),\n typeInfo: resolver.resolve(paramsArg.typeName) ?? null,\n });\n }\n\n // 次输入 schema:方法主输入之外显式声明的输入同样「声明即校验」。\n // - body 方法(POST/PUT/PATCH)声明 query 形参 → <METHOD>Query(如 POSTQuery,\n // Query 后缀自动 coerce),运行时校验 query 并挂载 __validatedQuery\n // - query 主输入且 hasBody 的方法(DELETE)声明 body/form 形参 → <METHOD>Body\n // (如 DELETEBody),form 与 body 共享 schema 名、coerce=true 显式覆盖\n // - GET/HEAD 声明 body 不收集(hasBody=false,运行时 body 注入恒为 undefined,\n // 该声明属无效标注,保持既往行为)\n if (inputType === 'body') {\n const queryArg = meta.params.find((p) => p.type === 'query');\n if (queryArg?.typeName) {\n sources.push({\n urlPath: entry.urlPath,\n filePath,\n schemaName: getSchemaName(method, 'query'),\n typeInfo: resolver.resolve(queryArg.typeName) ?? null,\n });\n }\n } else if (hasBody(method)) {\n const bodyArg =\n meta.params.find((p) => p.type === 'body') ?? meta.params.find((p) => p.type === 'form');\n if (bodyArg?.typeName) {\n sources.push({\n urlPath: entry.urlPath,\n filePath,\n schemaName: getSchemaName(method, 'body'),\n typeInfo: resolver.resolve(bodyArg.typeName) ?? null,\n coerce: bodyArg.type === 'form' || undefined,\n });\n }\n }\n }\n }\n\n // WS 路由 schema 源:文件级约定声明(export interface/type Query / Params)\n // → WSQuery / WSParams schema(coerce 由 schemaName 后缀正则推断,与 HTTP 的\n // Query/Params 一致)。未声明约定的 WS 文件不生成 schema,握手时透传原始值\n //(params/query 保持 URL 原始字符串),与 HTTP 未声明形参行为对齐。\n for (const wsFile of wsFiles) {\n const program = programByFile.get(wsFile.filePath);\n const sourceFile = program?.getSourceFile(wsFile.filePath);\n if (!sourceFile) continue;\n const resolver = resolversByFile.get(wsFile.filePath)!;\n if (findExportedTypeName(sourceFile, 'Query')) {\n sources.push({\n urlPath: wsFile.urlPath,\n filePath: wsFile.filePath,\n schemaName: getSchemaName('WS', 'query'),\n typeInfo: resolver.resolve('Query') ?? null,\n });\n }\n if (findExportedTypeName(sourceFile, 'Params')) {\n sources.push({\n urlPath: wsFile.urlPath,\n filePath: wsFile.filePath,\n schemaName: getSchemaName('WS', 'params'),\n typeInfo: resolver.resolve('Params') ?? null,\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 // 输入接受 ISO 字符串与毫秒时间戳双形态(与响应序列化 Date → getTime() 毫秒\n // 时间戳可逆往返,见 utils/stringifyJson);其他类型原样传入\n // z.coerce.date() 会把 boolean 等也强转,不符合需求\n // date 类型本身已用 preprocess,coerce 模式下无需再包一层\n return 'z.preprocess((v) => (typeof v === \"string\" || typeof v === \"number\" ? 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 { existsSync } from 'node:fs';\nimport { atomicWriteFile } from '../utils/atomicWrite';\nimport type { RuntimeType } from '../ast/resolveTypeNode';\nimport {\n generateHelpersFileSource,\n usesCoerceHelpers,\n HELPERS_FILENAME,\n} from '../ast/generateZodSchema';\nimport { getSchemaOutputPath, getHelpersImportPath } from './generateSchemaFiles';\n\n/**\n * zod.js 产物生成的共享管线\n *\n * routes / tools / tasks 三条产物管线的「分组 → 算 helpers import 路径 → 生成\n * 源码 → 按需生成 faapi-helpers.js → 并行原子写」流程完全同构,此前三处复制粘贴\n * (新增字段或 coerce 语义变化需同步改三处)。本模块把差异项(每个 source 的\n * 文件路径、按文件生成源码的回调)抽为参数,流程只此一份。\n *\n * 产物布局约定(与 getSchemaOutputPath 一致):源文件剥 src/ 前缀后同级目录写\n * zod.js,faapi-helpers.js 固定在 dist 根部。\n */\n\n/** 管线输入的单条 schema 源(与 RouteSchemaSource/ToolSchemaSource/TaskSchemaSource 的公共面) */\nexport interface ZodArtifactSource {\n /** 源文件绝对路径(分组键 + 输出路径推导) */\n filePath: string;\n}\n\nexport interface GenerateZodArtifactsOptions<T extends ZodArtifactSource> {\n /** 同一文件的 schema 源列表 → 该文件 zod.js 源码 */\n generateFileSource: (\n fileSources: T[],\n resolveType: (name: string) => RuntimeType | undefined,\n helpersImportPath: string,\n ) => string;\n /** 文件绝对路径 → 惰性类型解析器(解析 ref) */\n resolversByFile: Map<string, { resolve(name: string): { runtimeType: RuntimeType } | null }>;\n rootDir: string;\n dist: string;\n}\n\n/**\n * 生成 zod.js 产物集(含按需的 faapi-helpers.js)\n *\n * helpers 生成条件:任一 zod.js 源码引用 coerce 函数。helpers.js 已存在时跳过\n * (三条管线共享同一份,先到的生成)。全部文件并行原子写——dev watch 重建与在途\n * 请求并发时,请求侧 import 不到半成品。\n */\nexport async function generateZodArtifacts<T extends ZodArtifactSource>(\n sources: T[],\n options: GenerateZodArtifactsOptions<T>,\n): Promise<void> {\n const { generateFileSource, resolversByFile, rootDir, dist } = options;\n if (sources.length === 0) return;\n\n // 按源文件分组(同一 handler.ts 的多个方法合并到一个 zod.js)\n const sourcesByFile = new Map<string, T[]>();\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 // 每文件生成源码(先暂存,helpers 检测需要全部源码)\n const fileEntries: { outputPath: string; source: string }[] = [];\n for (const [filePath, fileSources] of sourcesByFile) {\n const relFile = path.relative(rootDir, filePath).replace(/\\\\/g, '/');\n const outputPath = getSchemaOutputPath(relFile, dist, rootDir);\n const resolver = resolversByFile.get(filePath);\n\n // zod.js 所在目录相对 dist 的路径(helpers import 相对路径推导):\n // strip src/ 前缀后取目录部分,与 getSchemaOutputPath 的目录推导一致\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\n const source = generateFileSource(\n fileSources,\n (name) => resolver?.resolve(name)?.runtimeType,\n getHelpersImportPath(zodRelDir),\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 if (!existsSync(helpersPath)) {\n await atomicWriteFile(helpersPath, generateHelpersFileSource());\n }\n }\n\n // 并行原子写所有 zod.js\n await Promise.all(\n fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source)),\n );\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 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 * 注意与取反形式 `source.mtime > product.mtime`(\"源码更新\")完全等价——\n * 同毫秒保存时产物视为 fresh(`>=`),两种写法结论一致,只是表达方向不同\n */\nexport function 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 * - in-flight 编译失败时等待方重抛同一真实错误——吞掉后返回 false 会让调用方去\n * import 不存在的产物,报误导性的 ERR_MODULE_NOT_FOUND,掩盖真实编译错误\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 // 失败时 inFlight 的 rejection 原样传播(真实编译错误优先于误导性的模块缺失)\n const inFlight = state.inFlightCompilations.get(sourceAbsPath);\n if (inFlight) {\n await inFlight;\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 * - in-flight 生成失败时等待方重抛同一真实错误(与 ensureCompiled 同语义)\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 * @param wsRoutes WS 路由清单(可选)——同文件 WS 路由的约定 interface(Query/Params)\n * 一并生成,避免\"HTTP 请求先触发生成 → WS schema 缺失\"的静默降级;反向同理\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 wsRoutes?: import('../router/routeTypes').WsRouteManifest,\n): Promise<boolean> {\n // mutex: 同一 schemaPath 正在被别的请求生成 → 等待并返回 false;\n // 失败时 inFlight 的 rejection 原样传播(与 ensureCompiled 同语义)\n const inFlight = state.inFlightSchemaGenerations.get(schemaPath);\n if (inFlight) {\n await inFlight;\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 // HTTP 清单与 WS 清单合并过滤,保证生成的 zod.js 同时含两类 schema)\n const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);\n const fileWsRoutes = (wsRoutes ?? []).filter((r) => r.filePath === routeFilePath);\n if (fileRoutes.length === 0 && fileWsRoutes.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 const sourceWsRoutes = fileWsRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));\n\n // 触发生成:注册 in-flight Promise 防止并发重复生成\n const generatePromise = (async () => {\n // 生成失败时抛错(不吞错误),由 createServer 错误处理链接管\n await generateSchemaFiles(sourceRoutes, rootDir, dist, sourceWsRoutes);\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/** 全量编译的统一 ignore 规则:测试文件、声明文件、resources 运行时静态文件(原样复制不编译) */\nconst SOURCE_IGNORE = ['**/*.test.ts', '**/*.e2e.test.ts', '**/*.d.ts', `${APP_DIR}/resources/**`];\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: SOURCE_IGNORE,\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: SOURCE_IGNORE,\n });\n}\n","import type { Plugin } from 'esbuild';\nimport ts from 'typescript';\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 * - `.js` 说明符对应产物文件不存在时回退探测 `.ts`/`.tsx`/`.jsx` 源\n * (`moduleResolution: Node16` 风格的 `import './x.js'` 实际指向 `x.ts`,产物必有\n * `x.js`)——返回源路径,调用方按\"已带产物后缀\"保持说明符原样\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 if (fs.existsSync(base)) return base;\n // Node16 风格 .js 说明符 → .ts/.tsx/.jsx 源回退(产物必有对应 .js)\n if (base.endsWith('.js')) {\n for (const ext of ['.ts', '.tsx', '.jsx']) {\n const file = base.slice(0, -3) + ext;\n if (fs.existsSync(file)) return file;\n }\n }\n return 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` 阶段用 TypeScript AST 定位源码中真实的 import/export/动态 import\n * 说明符(注释与字符串字面量天然不参与),重写为产物相对路径(带 `.js` 后缀):\n *\n * 1. **相对 specifier**(`./xxx`、`../xxx`):解析到实际源文件,重写为产物相对路径。\n * - 无后缀 → 解析 + 加 `.js`\n * - `.ts`/`.tsx`/`.jsx` → 改为 `.js`\n * - `.js`/`.mjs`/`.cjs` → 不重写(视为产物路径);`.js` 说明符在产物文件不存在时\n * 回退探测 `.ts`/`.tsx`/`.jsx` 源(Node16 风格 `import './x.js'` 指向 `x.ts`),\n * 探测到即确认产物必有对应 `.js`,保持原样\n * - **解析失败(项目内不存在对应文件)→ 返回 esbuild error,构建直接失败**——\n * `bundle: false` 下 esbuild 不解析依赖,静默保留会让源码笔误推迟到生产运行时\n * (Node ESM 严格解析报 ERR_MODULE_NOT_FOUND)才暴露\n *\n * 2. **别名 specifier**(tsconfig.paths,如 `@/xxx`):调 `resolveAlias` 解析候选路径,\n * 命中则重写为产物相对路径。解析失败保持原样——paths 只是候选提示,未命中时裸\n * 说明符可能仍由 node_modules 运行时解析,不误报\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 形式(AST 定位,注释/字符串不误伤):\n * - `import { x } from './base'` / `import { x } from '@/base'` / `import './base'`(副作用导入)\n * - `export { x } from './base'` / `export * from './base'`\n * - `import('./base')`(动态 import,仅字面量实参)\n *\n * 绝对路径(`/`)、`file:` URL、`node:` 协议不处理,交 esbuild 默认。\n */\nexport function createAliasPlugin(\n config: TsconfigPathsConfig,\n options?: AliasPluginOptions,\n): Plugin {\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\n /** 探测别名候选对应的源文件(源后缀 → index 文件) */\n const probeCandidateFile = (candidate: string): string | null => {\n for (const ext of SOURCE_EXTS) {\n const file = candidate + ext;\n if (fs.existsSync(file)) return file;\n }\n for (const indexExt of INDEX_EXTS) {\n const file = candidate + indexExt;\n if (fs.existsSync(file)) return file;\n }\n return null;\n };\n\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\n // AST 定位真实说明符——正则会误伤注释与字符串(注释掉的 `// import ... from './deleted'`\n // 会被当成真实导入,严格报错时大面积误报),TypeScript 解析器只给出真实节点与精确位置\n const sourceFile = ts.createSourceFile(importer, source, ts.ScriptTarget.Latest, false);\n const literals: ts.StringLiteral[] = [];\n const visit = (node: ts.Node): void => {\n // import type / export type 被编译器整体擦除,说明符不会进入产物——不重写也不报错\n if (ts.isImportDeclaration(node)) {\n if (!node.importClause?.isTypeOnly && node.moduleSpecifier) {\n if (ts.isStringLiteral(node.moduleSpecifier)) literals.push(node.moduleSpecifier);\n }\n } else if (ts.isExportDeclaration(node)) {\n if (!node.isTypeOnly && node.moduleSpecifier) {\n if (ts.isStringLiteral(node.moduleSpecifier)) literals.push(node.moduleSpecifier);\n }\n } else if (\n ts.isCallExpression(node) &&\n node.expression.kind === ts.SyntaxKind.ImportKeyword &&\n node.arguments[0] &&\n ts.isStringLiteral(node.arguments[0])\n ) {\n literals.push(node.arguments[0]);\n }\n ts.forEachChild(node, visit);\n };\n ts.forEachChild(sourceFile, visit);\n\n const replacements: { start: number; end: number; text: string }[] = [];\n const unresolvable: ts.StringLiteral[] = [];\n\n for (const lit of literals) {\n const specifier = lit.text;\n // 绝对路径 / file: URL / node: 协议不处理\n if (\n specifier.startsWith('/') ||\n specifier.startsWith('file:') ||\n specifier.startsWith('node:')\n ) {\n continue;\n }\n\n // 相对 specifier(./ 或 ../):解析到实际文件并重写\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n const resolved = resolveRelativeSpecifier(importer, specifier);\n if (!resolved) {\n unresolvable.push(lit);\n continue;\n }\n // 已带产物后缀的说明符不重写(含 .js → .ts 源回退命中的 Node16 风格,产物必有 .js)\n if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) continue;\n\n let prodPath: string;\n // src 前缀剥离:importer 在 src 外、resolved 在 src 内\n // → 重写为剥离前缀的产物路径(相对 importer 产物位置)\n if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {\n prodPath = toProdImportFromImporter(\n importer,\n options!.rootDir!,\n toStrippedProdImportPath(resolved, options!.rootDir!),\n );\n } else {\n prodPath = toProdImportPath(resolved, importer);\n }\n const quote = source[lit.getStart(sourceFile)];\n replacements.push({\n start: lit.getStart(sourceFile),\n end: lit.getEnd(),\n text: quote + prodPath + quote,\n });\n continue;\n }\n\n // 别名 specifier:调 resolveAlias 解析\n for (const candidate of resolveAlias(specifier, config)) {\n const file = probeCandidateFile(candidate);\n if (!file) continue;\n\n let prodPath: string;\n if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {\n prodPath = toProdImportFromImporter(\n importer,\n options!.rootDir!,\n toStrippedProdImportPath(file, options!.rootDir!),\n );\n } else {\n prodPath = toProdImportPath(file, importer);\n }\n const quote = source[lit.getStart(sourceFile)];\n replacements.push({\n start: lit.getStart(sourceFile),\n end: lit.getEnd(),\n text: quote + prodPath + quote,\n });\n break;\n }\n }\n\n if (unresolvable.length > 0) {\n const lineStarts = sourceFile.getLineStarts();\n return {\n errors: unresolvable.map((lit) => {\n const { line, character } = sourceFile.getLineAndCharacterOfPosition(\n lit.getStart(sourceFile),\n );\n const lineEnd =\n line + 1 < lineStarts.length ? lineStarts[line + 1] : sourceFile.getEnd();\n return {\n text: `无法解析的相对导入 \"${lit.text}\"——项目内不存在对应源文件(faapi 逐文件编译(bundle: false)不解析依赖,该导入会原样进入产物,生产 Node ESM 运行时报 ERR_MODULE_NOT_FOUND)。请修正路径或补全后缀`,\n location: {\n file: importer,\n line: line + 1,\n column: character,\n lineText: source.slice(lineStarts[line], lineEnd).replace(/\\r?\\n$/, ''),\n },\n };\n }),\n };\n }\n\n if (replacements.length === 0) return undefined;\n // 从后往前应用替换,避免前面的替换使后面的位置失效\n replacements.sort((a, b) => b.start - a.start);\n let newSource = source;\n for (const r of replacements) {\n newSource = newSource.slice(0, r.start) + r.text + newSource.slice(r.end);\n }\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 { queryToObject } from '../utils/queryToObject';\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 } 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 { resolveResourcesDir } from '../cli/copyResources';\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 /** 请求体大小限制(字节) */\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 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 // 运行时资源根目录(挂到每个请求的 ctx.resourcesDir,供 handler 读静态文件)\n const resourcesDir = resolveResourcesDir(rootDir, dist);\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 — 默认启用,请求日志无条件并入统一日志管道(config.log.accessLog: false 关闭,\n // config.log: false 随管道全静默;编程式自定义输出经 middlewares: [logger({ log })]\n configMiddlewares.push(loggerMiddleware());\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 routesRef.wsCurrent,\n resourcesDir,\n ).catch((err) => {\n // 兜底留痕:进入这里说明 sendErrorResponse 自身也失败(如响应头已发的二次\n // 响应尝试),恰恰是最需要排查痕迹的极端场景,静默吞掉会让 500 无从定位\n console.error('[faapi] Request pipeline failed after error response attempt:', err);\n try {\n if (!res.headersSent) {\n res.statusCode = 500;\n res.end();\n } else if (!res.writableEnded) {\n res.end();\n }\n } catch {\n // 连接已不可用,忽略\n }\n });\n });\n\n // 挂载 WebSocket 升级处理(无条件挂载:upgrade 处理器内部对无匹配路由返回 404,\n // 空清单成本一次函数调用。若按初始清单条件挂载,dev watch 中新增第一个 WS 路由后\n // upgrade 监听器不会补挂,WS 路由永远 404)\n attachWebSocket({\n server,\n routesRef,\n rootDir,\n dist,\n config,\n globalMiddlewares,\n trustedProxy,\n registries,\n });\n\n return { server, routesRef };\n}\n\n/**\n * 准备请求上下文:已解析的 Web Request + FaapiContext\n *\n * 提取为独立函数,让 handleRequest 主流程聚焦于路由 + 中间件调度,\n * 便于单测与未来扩展(如自定义 context 字段来源)。\n *\n * Web Request / URL 由调用方(handleRequest)解析一次后传入——提前路由匹配\n * 需要在构造 ctx 之前拿到 method/urlPath,且 Request 的 body 流只能包装一次。\n *\n * preMatched:提前匹配的路由结果(handleRequest 在构造 ctx 前完成匹配,\n * 让全局中间件 next() 之前即可见 ctx.params/ctx.rawParams 原始段)。\n * 未命中传 null——ctx.params 挂空对象,404/405 仍由 routePipeline 抛出\n * (错误响应保持经过完整外层中间件链,CORS 头不丢)。\n */\nfunction prepareRequest(\n req: IncomingMessage,\n request: Request,\n url: URL,\n config: Record<string, unknown> | undefined,\n trustedProxy: boolean,\n registries?: AppRegistries,\n preMatched?: RouteMatch | null,\n resourcesDir?: string,\n): {\n request: Request;\n url: URL;\n ctx: FaapiContext;\n meta: ResponseMeta;\n method: string;\n urlPath: string;\n} {\n const method = request.method.toUpperCase();\n const urlPath = url.pathname;\n // 路由匹配后构造 ctx:params 初值即匹配的原始段(全局中间件 next() 前可见),\n // createContext 内部把 params 同时挂为 ctx.rawParams(恒原始引用)\n const ctx = createContextFromUrl(\n request,\n url,\n preMatched?.params ?? {},\n config,\n getClientIp(req, trustedProxy),\n registries,\n resourcesDir,\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\n/** 合并注入器缓存(route 记录 → 全局+目录合并结果;route 记录 reload 即换代) */\nconst mergedInjectorsCache = new WeakMap<object, InjectorMap>();\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 /** 提前匹配的路由结果;null 时由本管线抛 404/405(保持错误经过全局中间件链) */\n preMatched: RouteMatch | null;\n /** WS 路由清单(同文件 WS 路由的 schema 一并按需生成,防静默缺失) */\n wsRoutes?: WsRouteManifest;\n}): () => Promise<Response> {\n const {\n routes,\n method,\n urlPath,\n url,\n ctx,\n request,\n rootDir,\n dist,\n globalInjectors,\n preMatched,\n wsRoutes,\n } = opts;\n return async () => {\n // 1. 路由匹配:命中已在 handleRequest 提前完成(ctx.params/rawParams 已挂原始段);\n // 未命中在此抛 RouteNotFound / MethodNotAllowed\n const match = preMatched ?? resolveRouteOrThrow(routes, method, urlPath);\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 // rawBody(请求体原始文本)随之挂载(GET/HEAD 与 multipart 为 undefined)\n const { input, rawBody } = await resolveInputFromUrl(route.method, request, url);\n if (rawBody !== undefined) {\n ctx.rawBody = rawBody;\n }\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, wsRoutes);\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\n // 主输入是 query 的方法(GET/DELETE/HEAD):校验后的值替换 ctx.query\n //(声明 number/boolean 的字段已是转换后的值)。以原始 query 打底合并——\n // schema 是 z.object,声明之外的字段会被剥掉,未声明的兜底键保持原始\n // 字符串,行为与未声明 schema 时一致。ctx.query 即 handler 的 query 注入\n //(同一对象),无声明时 validateInput 原样透传、不替换。\n if (\n inputType === 'query' &&\n result.data !== input &&\n typeof result.data === 'object' &&\n result.data !== null\n ) {\n ctx.query = {\n ...(input as Record<string, unknown>),\n ...(result.data as Record<string, unknown>),\n };\n }\n\n // params 校验与回写:handler 声明了 `params: XxxParams` 时按声明类型校验\n // 路径参数(number/boolean 由 schema 内联 coerce 完成字符串→值转换),并把\n // 转换后的值回写 ctx.params——handler 注入、目录/全局中间件与诊断日志拿到的\n // 都是转换后的值。同样以原始 params 打底合并(防 catch-all 等声明之外的段被\n // z.object 剥掉);ctx.rawParams 保持原始引用不受影响。无声明时 validateInput\n // 原样透传,回写等于无操作。\n const paramsResult = await validateInput(schemaPath, route.method, 'params', match.params);\n if (!paramsResult.valid) {\n throw new ValidationError('参数校验失败', paramsResult.issues);\n }\n if (paramsResult.data !== match.params) {\n ctx.params =\n typeof paramsResult.data === 'object' && paramsResult.data !== null\n ? {\n ...match.params,\n ...(paramsResult.data as Record<string, string | number | boolean>),\n }\n : match.params;\n }\n\n // 次输入校验(声明即校验,与主输入同一 validateInput 通道):\n // body 方法(POST/PUT/PATCH)声明 query 形参时存在 POSTQuery schema——校验 query\n // 并替换 ctx.query(声明字段拿到转换值,未声明字段以原始 query 打底保留\n // 原始字符串);无声明时 schema 缺失、data === input,不替换,ctx.query 保持\n // createContext 挂载的原始 query 对象。\n if (inputType === 'body') {\n const rawQuery = queryToObject(url.searchParams);\n const queryResult = await validateInput(schemaPath, route.method, 'query', rawQuery);\n if (!queryResult.valid) {\n throw new ValidationError('参数校验失败', queryResult.issues);\n }\n if (\n queryResult.data !== rawQuery &&\n typeof queryResult.data === 'object' &&\n queryResult.data !== null\n ) {\n ctx.query = {\n ...(rawQuery as Record<string, unknown>),\n ...(queryResult.data as Record<string, unknown>),\n };\n }\n }\n // body 计算与主输入分流:\n // - POST/PUT/PATCH:主输入就是 body,用校验后的值\n // - DELETE:主输入是 query(校验 DELETEQuery),body 单独解析注入——\n // 若把校验后的 query 当 body 传入,handler 声明 body 会静默拿到 query;\n // 同时请求体流不被消费,keep-alive 连接无法复用。\n // 声明 body/form 形参时存在 DELETEBody schema:解析结果校验后再注入\n // (Date 字段转换与 POST body 一致;form 声明 coerce=true)。空请求体\n // (null)与 POST 同路径:有 schema 时 safeParse 失败 422,无 schema 透传\n // - GET/HEAD:无 body\n let body: unknown;\n if (inputType === 'query' && hasBody(route.method)) {\n const parsed = await resolveBodyForQueryMethod(request);\n if (parsed.rawBody !== undefined) {\n ctx.rawBody = parsed.rawBody;\n }\n const bodyResult = await validateInput(schemaPath, route.method, 'body', parsed.input);\n if (!bodyResult.valid) {\n throw new ValidationError('参数校验失败', bodyResult.issues);\n }\n body = bodyResult.data;\n } else if (hasBody(route.method)) {\n body = result.data;\n }\n // 挂载校验后的 body:目录中间件(handler 之前)可经 ctx.body 读取请求体;\n // 与 handler 的 body 注入是同一对象。GET/HEAD 及空请求体(null,无 schema\n // 透传场景)恒 undefined\n if (body !== undefined && body !== null) {\n ctx.body = body;\n }\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 // 合并结果对固定 route 恒定(injectors 在上方第 5 步按需加载后就绪,\n // reload 换新 route 记录),按 route WeakMap 缓存避免每请求 spread 重建\n let mergedInjectors = route.injectors;\n if (globalInjectors) {\n let merged = mergedInjectorsCache.get(route);\n if (!merged) {\n merged = { ...globalInjectors, ...route.injectors };\n mergedInjectorsCache.set(route, merged);\n }\n mergedInjectors = merged;\n }\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 wsRoutes?: WsRouteManifest,\n resourcesDir?: string,\n): Promise<void> {\n // meta/ctx 兜底:请求准备阶段抛错(如 content-length 超限的 413)时尚无 ctx\n let meta: ResponseMeta = { headers: {}, setCookies: [] };\n let ctx: FaapiContext | undefined;\n // SSE 提前接管标记:声明在 try 之外——catch 错误路径需读取(流已开始则不再发错误响应)\n let sseEarlySent = false;\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. 路由匹配提前:在构造 ctx 之前完成(matchRoute 纯函数无副作用),\n // 命中则 params 原始段进入 ctx.params/ctx.rawParams——全局中间件\n // await next() 之前即可读取;未命中传 null,404/405 仍由 routePipeline\n // 抛出(错误响应保持经过完整外层中间件链)\n const { request, url } = toWebRequest(req, bodyLimit, abortController.signal);\n const method = request.method.toUpperCase();\n const urlPath = url.pathname;\n const preMatched = matchRoute(routes, method, urlPath);\n\n // 2. 准备请求上下文(createContext)——Request/URL 由上方解析一次后传入\n const prepared = prepareRequest(\n req,\n request,\n url,\n config,\n trustedProxy,\n registries,\n preMatched,\n resourcesDir,\n );\n ctx = prepared.ctx;\n meta = prepared.meta;\n\n // SSE 提前接管(详见 createServer.md「SSE 提前接管」):ctx.sse() 首次写入\n // (send/sendRaw/sendError)触发本钩子,立即把 SSE Response 接入 res——流式\n // 期间字节即产即达,writer.aborted 也能实时感知客户端断开。early-sent 后\n // 正常/错误路径均不再发送响应\n (ctx as FaapiContext & { __earlyRespond?: (response: Response) => void }).__earlyRespond = (\n response: Response,\n ) => {\n if (sseEarlySent) return;\n sseEarlySent = true;\n // meta(含 CORS 头等)在首次写入时刻合并;此后的 setStatus/setHeader 不再生效。\n // fire-and-forget:接管完成在流关闭后,不阻塞 handler;管道错误按断连收尾\n void sendNodeResponse(mergeMeta(response, meta), res).catch(() => {\n if (!res.writableEnded) res.destroy();\n });\n };\n\n // 3. 创建路由执行管线(校验 + 中间件加载 + handler 调用)\n const routePipeline = createRoutePipeline({\n routes,\n method,\n urlPath,\n url,\n ctx,\n request,\n rootDir,\n dist,\n globalInjectors,\n preMatched,\n wsRoutes,\n });\n\n // 4. 执行外层中间件链(CORS → helmet → logger → 全局 → routePipeline,\n // 数组已在 createServer 启动期组装,此处仅按需 compose)\n const response =\n outerMiddlewares.length > 0\n ? await compose(outerMiddlewares, ctx, routePipeline)\n : await routePipeline();\n // 5. 发送响应。SSE 已在首次写入时提前接管(响应在线上):handler 返回值及\n // 中间件 await next() 之后替换的响应被忽略(日志等副作用仍正常执行)\n if (!sseEarlySent) {\n await sendSuccessResponse(response, res);\n }\n } catch (err: unknown) {\n // 客户端已断开或响应已完成:网络中断/连接销毁不是服务端错误,\n // 不向已销毁的连接写 500(写入无效),也不触发 onError 误报\n if (res.destroyed || res.writableEnded) return;\n // SSE 流已开始(响应头已发出):无法再改发错误响应——流由 writer 关闭自然\n // 收尾(invokeHandler 的 autoClose 保证),仍触发 onError 副作用供告警观测\n if (sseEarlySent) {\n if (onError && ctx) {\n try {\n await onError(err, ctx);\n } catch {\n // onError 自身抛错不影响已发出的响应\n }\n }\n return;\n }\n // 413(请求体超限):客户端可能仍在上传——响应附 Connection: close 并在写出\n // 后销毁请求连接。不关闭的话:请求体未消费,keep-alive 连接无法复用,客户端\n // 上传也只会收到晦涩的连接重置而非明确的 413\n if (err instanceof PayloadTooLargeError) {\n res.setHeader('Connection', 'close');\n res.once('finish', () => req.destroy());\n }\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 // 无动态路由时直接返回(path.split 也不必做——纯静态清单的热路径)\n if (index.dynamics.length === 0) {\n return null;\n }\n\n // 请求路径 segments 循环外 split 一次——N 条候选动态路由不再重复分割同一 path\n const pathSegments = path.split('/').filter(Boolean);\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, pathSegments, 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 已预编译,path 循环外 split 一次)\n if (index.dynamics.length > 0) {\n const pathSegments = path.split('/').filter(Boolean);\n for (const entry of index.dynamics) {\n const params = matchSegments(\n entry.segments,\n pathSegments,\n entry.route.paramNames,\n entry.route.isCatchAll,\n );\n if (params !== null) {\n methods.add(entry.route.method);\n }\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(\n pattern.split('/').filter(Boolean),\n path.split('/').filter(Boolean),\n paramNames,\n isCatchAll,\n );\n}\n\n/**\n * 基于**预编译 segments** 的动态匹配(热路径内部用)\n *\n * 与 matchDynamicPath 语义一致,但模式段由索引构建期一次性 split、请求段由\n * 调用方循环外 split 一次传入,免去对同一字符串的重复分割\n */\nfunction matchSegments(\n patternSegments: string[],\n pathSegments: string[],\n paramNames: string[],\n isCatchAll?: boolean,\n): Record<string, string> | null {\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/** 输入解析结果:input 为校验管线的输入值,rawBody 为请求体原始文本(无则 undefined) */\nexport interface ResolvedInput {\n input: unknown;\n /** 请求体原始文本:JSON 为未解析字符串、form-urlencoded 为原始编码文本;multipart/无请求体为 undefined */\n rawBody?: string;\n}\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(有 schema 时 safeParse 失败 422,\n * 无 schema 透传;与 DELETE 的 body 空体行为完全一致)\n * - 请求体非空但 JSON 格式非法,抛 ValidationError(code=INVALID_FORMAT),\n * 不再静默返回 null 导致后续报\"字段缺失\"\n *\n * @param method HTTP 方法\n * @param request Request 对象\n * @returns input(query 对象或 body 对象)+ rawBody(原始请求体文本)\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveInput(method: string, request: Request): Promise<ResolvedInput> {\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<ResolvedInput> {\n const inputType = getInputTypeForMethod(method);\n\n // 主输入是 body 的方法:解析请求体\n if (inputType === 'body') {\n return parseBodyByContentType(request);\n }\n\n // 主输入是 query 的方法(GET / DELETE / HEAD):从 URL 提取 query\n return { input: queryToObject(url.searchParams) };\n}\n\n/**\n * 按 Content-Type 解析请求体(POST/PUT/PATCH 主输入与 DELETE 次输入共用)\n *\n * - multipart/form-data:fields + files(二进制流,rawBody 为 undefined)\n * - application/x-www-form-urlencoded:字符串对象(rawBody 为原始编码文本)\n * - 默认按 JSON 解析(application/json 及其它未明确类型)\n *\n * 空请求体(或纯空白)视为无 body,input 为 null(rawBody 为 undefined)。\n */\nasync function parseBodyByContentType(request: Request): Promise<ResolvedInput> {\n const contentType = request.headers.get('content-type') ?? '';\n\n // multipart/form-data:字段 + 文件\n if (contentType.includes('multipart/form-data')) {\n return { input: await 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 { input: 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 { input: obj, rawBody: text };\n }\n\n // 默认按 JSON 解析(application/json 及其它未明确类型)\n const text = await request.text();\n\n // 空请求体:视为无 body(有 schema 时校验失败,无 schema 透传)\n if (isBlankText(text)) {\n return { input: 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 { input: result.data, rawBody: text };\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 * Content-Type 分流与 POST/PUT/PATCH 主输入完全对称(JSON / form-urlencoded /\n * multipart)。解析结果的校验由 createServer 管线执行——handler 声明 `body`/\n * `form` 形参时存在 DELETEBody schema,校验通过后注入(Date 字段转换与 POST\n * body 一致;form 声明 coerce=true)。空请求体 input 为 null,与 POST 同路径:\n * 有 schema 时 safeParse 失败 422,无 schema 透传(handler 未声明 body 形参,\n * 无暴露面)。\n *\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveBodyForQueryMethod(request: Request): Promise<ResolvedInput> {\n return parseBodyByContentType(request);\n}\n","import type { ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { consumePendingMetaHeaders } from './pendingMeta';\nimport { hasBufferedBody } from './bufferedBody';\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 // 快路径:构造点标记为缓冲型的 body(string/Buffer——绝大多数 JSON 响应的形态,\n // 见 bufferedBody.ts)整体读出后 res.end 直写,免掉 Readable.fromWeb + pipe 的\n // 全套流机器。未标记的(SSE、handler 自建流)保持 pipe 流式语义\n if (hasBufferedBody(response)) {\n const buffer = await readBufferedBody(response.body);\n await new Promise<void>((resolve) => {\n res.on('close', () => {\n // 正常完成也会触发 'close'('finish' 之后),未写完才是断连\n if (!res.writableEnded) res.destroy();\n });\n if (res.destroyed) {\n resolve();\n return;\n }\n res.end(buffer, () => resolve());\n });\n return;\n }\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\n/**\n * 读取缓冲型 body 的完整内容\n *\n * 仅对构造点标记 hasBufferedBody 的 Response 调用——body 保证有限(内存型),\n * 读到 done 即返回。读取错误向上抛(与 pipe 路径的 reject 语义一致,走错误响应)。\n */\nasync function readBufferedBody(body: ReadableStream): Promise<Uint8Array> {\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n } finally {\n try {\n reader.releaseLock();\n } catch {\n // releaseLock 在流 errored 后可能抛错,忽略\n }\n }\n if (chunks.length === 0) return new Uint8Array(0);\n if (chunks.length === 1) return chunks[0]!;\n return Buffer.concat(chunks);\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 { ResponseMeta } from '../runtime/contextTypes';\n\n/**\n * 向响应 meta.headers 大小写不敏感地合并 Vary 值\n *\n * 延迟落头(headers-only meta)通道是响应头的唯一权威来源——读请求头\n * (ctx.headers.get('vary'))既看不到其他中间件已设置的响应 Vary,还会把客户端\n * 伪造的 Vary 请求头带进响应。CORS(Vary: Origin)与 compression\n * (Vary: Accept-Encoding)共用此函数,互不覆盖。\n */\n\n/** 向 meta.headers 合并 Vary 值(CORS 等中间件可能已设置 Vary,不能覆盖) */\nexport function 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 // 大小写不敏感地检查是否已包含(按逗号分段精确匹配,避免 'X-Origin' 误判含 'Origin')\n const segments = existing.split(',').map((s) => s.trim().toLowerCase());\n if (!segments.includes(value.toLowerCase())) {\n const key = meta.headers['Vary'] !== undefined ? 'Vary' : 'vary';\n meta.headers[key] = `${existing}, ${value}`;\n }\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { mergeVary } from './mergeVary';\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 meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\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 // 合并读权威来源 meta.headers(延迟落头通道)——读请求头既看不到其他中间件\n // 已设置的响应 Vary,还会把客户端伪造的 Vary 请求头带进响应\n if (origin === true || Array.isArray(origin)) {\n mergeVary(meta, 'Origin');\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 { mergeVary } from './mergeVary';\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/**\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';\nimport { isAccessLogEnabled, writeLogEntry } from '../logger/logger';\nimport type { LogLevel } from '../logger/loggerTypes';\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 */\n log?: LoggerFn;\n}\n\n/** 请求日志条目(自定义 log 函数的第一参数;index 签名兼容 LoggerFn 的 Record 形参) */\ninterface RequestLogEntry {\n [key: string]: unknown;\n requestId: string;\n method: string;\n path: string;\n status: number;\n durationMs: number;\n error?: string;\n}\n\n/** status → 日志级别映射(管道条目用):5xx error、4xx warn、其余 info */\nfunction levelForStatus(status: number): LogLevel {\n if (status >= 500) return 'error';\n if (status >= 400) return 'warn';\n return 'info';\n}\n\n/**\n * 默认输出(`options.log` 未配置时):请求日志无条件并入统一日志管道\n *\n * 条目转 LogEntry 经 writeLogEntry 输出(scope `access`,fields 携带\n * requestId/method/path/status/durationMs)——与业务日志同文件/同 sink/console,\n * 受 `config.log.level`(管道阈值)与 `consoleLevel`(console 出口)统一过滤。\n * `config.log.accessLog: false` 或 `config.log: false` 时丢弃不输出。\n */\nfunction defaultLog(entry: RequestLogEntry, text: string): void {\n if (!isAccessLogEnabled()) return;\n writeLogEntry({\n level: levelForStatus(entry.status),\n message: text,\n time: new Date().toISOString(),\n scope: 'access',\n fields: entry,\n });\n}\n\n/**\n * 创建请求日志中间件(洋葱模型)\n *\n * 默认并入统一日志管道(egg 模型:访问日志与业务日志同管道,一份 config.log\n * 管全部输出),`config.log.accessLog: false` 关闭。\n *\n * 日志格式(文本 message):GET /api/users 200 12ms\n * 错误格式:POST /api/users 400 45ms - Error: ...\n *\n * before/after 一体,闭包变量共享开始时间,无需污染 ctx。\n * 错误用 try/catch 捕获,记录后重新抛出(让上层处理)。\n * 成功时从 next() 返回的 Response 读取状态码。\n *\n * log 函数每次请求时读取(options.log ?? defaultLog),运行时切换全局日志配置\n * 同样生效。编程式自定义输出:`middlewares: [logger({ log: myFn })]`。\n */\nexport function logger(options: LoggerOptions = {}): FaapiMiddleware {\n return async (ctx, next) => {\n const log = options.log ?? defaultLog;\n const start = Date.now();\n try {\n const response = await next();\n const duration = Date.now() - start;\n const entry = {\n requestId: ctx.requestId,\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 requestId: ctx.requestId,\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 { resolveResourcesDir } from '../cli/copyResources';\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 { queryToObject } from '../utils/queryToObject';\nimport { nodeHttpToWebHeaders, buildErrorResponse } from './serverUtils';\nimport { validateInput } from '../validator/validateInput';\nimport { ValidationError } from '../errors/httpErrors';\nimport { getRuntimeSchemaPath } from '../cli/generateSchemaFiles';\nimport {\n ensureCompiled,\n ensureSchemaGenerated,\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 *\n * 用户回调异常隔离(导出供测试锚定契约):onOpen/onMessage/onClose 抛错被捕获,\n * 故障半径限定当前连接——EventEmitter 监听器同步抛出会沿 emit 传播为\n * uncaughtException(Node 15+ 默认终止进程),HTTP 路径有完整错误链,WS 事件\n * 模型对齐。有 onError 时转交业务方(可 ws.close() 自决),无 onError 时\n * console.error;onError 自身抛错同样捕获仅留痕。框架不因回调抛错主动关连接。\n */\nexport function bindEvents(rawSocket: WebSocket, handlers: WsEventHandlers | void): void {\n if (!handlers) return;\n const ws = wrapWsSocket(rawSocket);\n\n /** 单个用户回调的异常隔离;onError 转交失败时降级 console.error(不递归抛) */\n const guard = (event: string, fn: () => void): void => {\n try {\n fn();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (handlers.onError) {\n try {\n handlers.onError(ws, error);\n } catch (hookErr) {\n console.error(`[faapi] WS onError 回调抛错 (after ${event}):`, hookErr);\n }\n } else {\n console.error(`[faapi] WS ${event} 回调抛错:`, error);\n }\n }\n };\n\n if (handlers.onOpen) {\n // handleUpgrade 回调触发时 socket 已是 OPEN,直接同步调用\n if (rawSocket.readyState === WebSocket.OPEN) {\n guard('onOpen', () => handlers.onOpen!(ws));\n } else {\n rawSocket.once('open', () => guard('onOpen', () => 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 guard('onMessage', () => handlers.onMessage!(ws, isBinary ? buf : buf.toString('utf8')));\n });\n }\n if (handlers.onClose) {\n rawSocket.on('close', (code: number, reason: Buffer) => {\n guard('onClose', () => handlers.onClose!(ws, code, reason.toString('utf8')));\n });\n }\n if (handlers.onError) {\n rawSocket.on('error', (err: Error) => {\n // onError 是错误出口本身,只防它抛错升级为 uncaughtException\n try {\n handlers.onError!(ws, err);\n } catch (hookErr) {\n console.error('[faapi] WS onError 回调抛错 (after onError):', hookErr);\n }\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 /** 产物输出目录(如 '.faapi' 或 'dist'),用于计算 WS schema 路径 */\n dist: 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 dist,\n config,\n globalMiddlewares,\n trustedProxy = false,\n registries,\n } = options;\n\n // 运行时资源根目录(与 HTTP 链路一致,挂到握手 ctx.resourcesDir)\n const resourcesDir = resolveResourcesDir(rootDir, dist);\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 // 全握手唯一一次 URL 解析:Request 构造与 query 校验共享(searchParams 复用)\n const fullUrl = new URL(`http://${host}${req.url ?? '/'}`);\n\n // 构造 Web Request 与 FaapiContext(与 HTTP 请求一致,供中间件使用)\n const request = new Request(fullUrl.toString(), { method: 'GET', headers });\n const ctx = createContext(\n request,\n params,\n config,\n getClientIp(req, trustedProxy),\n registries,\n resourcesDir,\n );\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n\n // 标记握手是否已完成协议升级(用于判断 socket 是否可写)\n let upgraded = false;\n\n // finalHandler:schema 校验转换 + 加载 WS handler + 协议升级 + 绑定事件\n const finalHandler = async (): Promise<Response> => {\n // 声明即校验(与 HTTP 管线同一 validateInput 通道):handler.ts 导出 WS 且\n // 同文件声明 `export interface Query` / `export interface Params` 时存在\n // WSQuery / WSParams schema(coerce=true,URL 段/查询串来源均为 string)。\n // 校验失败抛 ValidationError → 错误兜底链生成 422 JSON 响应写回 socket 拒绝\n // 握手;未声明约定的文件无 schema,validateInput 原样透传(params/query 保持\n // URL 原始字符串)。校验后的值回写 ctx.params/ctx.query(与 HTTP 一致:原始\n // 打底合并防剥未声明段),ctx.rawParams/ctx.rawQuery 恒为原始值。\n const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);\n // zod.js 不存在 = 该 WS 文件未声明约定 interface(无 WSQuery/WSParams 源时\n // 生成器不产出文件)→ 无校验,params/query 透传 URL 原始字符串(文档化行为,\n // 与 HTTP 未声明形参对齐)。文件存在但 schema key 缺失/加载失败仍按\n // validateInput 语义抛 InternalError,不静默放行。\n if (fs.existsSync(schemaPath)) {\n if (isDevOnDemandEnabled()) {\n const devDist = getDevDist();\n if (devDist) {\n await ensureSchemaGenerated(\n schemaPath,\n route.filePath,\n routesRef.current,\n rootDir,\n dist,\n routesRef.wsCurrent,\n );\n }\n }\n const searchParams = fullUrl.searchParams;\n const rawQueryObject = queryToObject(searchParams);\n const paramsResult = await validateInput(schemaPath, 'WS', 'params', params);\n if (!paramsResult.valid) {\n throw new ValidationError('参数校验失败', paramsResult.issues);\n }\n if (\n paramsResult.data !== params &&\n typeof paramsResult.data === 'object' &&\n paramsResult.data !== null\n ) {\n ctx.params = {\n ...params,\n ...(paramsResult.data as Record<string, string | number | boolean>),\n };\n }\n const queryResult = await validateInput(schemaPath, 'WS', 'query', rawQueryObject);\n if (!queryResult.valid) {\n throw new ValidationError('参数校验失败', queryResult.issues);\n }\n if (\n queryResult.data !== rawQueryObject &&\n typeof queryResult.data === 'object' &&\n queryResult.data !== null\n ) {\n ctx.query = {\n ...(rawQueryObject as Record<string, unknown>),\n ...(queryResult.data as Record<string, unknown>),\n };\n }\n }\n\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 path from 'node:path';\nimport fs from 'node:fs';\nimport { isInsideDir } from '../utils/prodPaths';\n\n/**\n * 运行时资源目录约定(源码侧,相对 rootDir)\n *\n * 放这里的文件原样复制进产物(不编译、不被任何扫描器识别),运行时经\n * `ctx.resourcesDir` / `app.resourcesDir` 定位读取。详见 copyResources.md。\n */\nexport const RESOURCES_DIR = 'src/resources';\n\n/** 产物内 resources 目录名(<dist>/resources,与源码布局打平 src/ 前缀后一致) */\nconst RESOURCES_OUTPUT_DIR = 'resources';\n\n/**\n * 判断绝对路径是否位于 src/resources 下(watcher 事件分流用)\n *\n * resources 文件事件走增量复制/删除,不进编译调度器;其余源码事件维持原链路。\n */\nexport function isResourceSourcePath(rootDir: string, absPath: string): boolean {\n return isInsideDir(absPath, path.resolve(rootDir, RESOURCES_DIR));\n}\n\n/**\n * 解析产物内 resources 根目录绝对路径\n *\n * 运行时定位入口(createAppCore / createServer / handleWsUpgrade 共用),\n * 与 createAppBase 的 dist 解析约定一致:dist 相对 rootDir。\n */\nexport function resolveResourcesDir(rootDir: string, dist: string): string {\n return path.resolve(rootDir, dist, RESOURCES_OUTPUT_DIR);\n}\n\n/**\n * 镜像复制 src/resources → <dist>/resources\n *\n * 先删产物目录再递归复制(dev 启动时清掉上次运行的 stale 文件;build 侧\n * emptyOutDir 已清空 dist,此删除是幂等兜底)。源目录不存在时跳过并返回\n * false——无资源目录的项目零负担,产物中也不产生 resources 目录。\n *\n * dev(devCommand 启动时)与 build(buildCommand 步骤 8)共用同一实现,\n * 差异仅由 dist 路径参数驱动。\n */\nexport async function copyResources(rootDir: string, dist: string): Promise<boolean> {\n const sourceDir = path.resolve(rootDir, RESOURCES_DIR);\n if (!fs.existsSync(sourceDir)) return false;\n\n const outDir = resolveResourcesDir(rootDir, dist);\n await fs.promises.rm(outDir, { recursive: true, force: true });\n await fs.promises.cp(sourceDir, outDir, { recursive: true });\n return true;\n}\n\n/**\n * watcher 增量复制:单个源资源文件 → 产物对应位置(自动创建父目录)\n *\n * 源文件已不存在时跳过(change 与 unlink 的竞态:删除后到达的 change 不做\n * 无用复制,产物清理由 removeResourceFile 负责)。源路径不在 src/resources\n * 下时抛错——调用方(watcher)按路径分流,越界传入视为编程错误显式失败。\n */\nexport async function copyResourceFile(\n rootDir: string,\n dist: string,\n absSourceFile: string,\n): Promise<void> {\n const sourceDir = path.resolve(rootDir, RESOURCES_DIR);\n if (!isInsideDir(absSourceFile, sourceDir)) {\n throw new Error(`[faapi] copyResourceFile: ${absSourceFile} is not under ${RESOURCES_DIR}`);\n }\n if (!fs.existsSync(absSourceFile)) return;\n\n const rel = path.relative(sourceDir, absSourceFile);\n const dest = path.join(resolveResourcesDir(rootDir, dist), rel);\n await fs.promises.mkdir(path.dirname(dest), { recursive: true });\n await fs.promises.cp(absSourceFile, dest);\n}\n\n/**\n * watcher 增量删除:移除产物中的对应资源文件\n *\n * 目标不存在时幂等成功(fs.rm force 语义)。源路径不在 src/resources 下时抛错,\n * 与 copyResourceFile 同一防御口径。\n */\nexport async function removeResourceFile(\n rootDir: string,\n dist: string,\n absSourceFile: string,\n): Promise<void> {\n const sourceDir = path.resolve(rootDir, RESOURCES_DIR);\n if (!isInsideDir(absSourceFile, sourceDir)) {\n throw new Error(`[faapi] removeResourceFile: ${absSourceFile} is not under ${RESOURCES_DIR}`);\n }\n const rel = path.relative(sourceDir, absSourceFile);\n const dest = path.join(resolveResourcesDir(rootDir, dist), rel);\n await fs.promises.rm(dest, { force: true });\n}\n","import type { IncomingMessage } from 'node:http';\nimport { markBufferedBody } from '../response/bufferedBody';\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 const fallback = 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 markBufferedBody(fallback);\n return fallback;\n }\n}\n","/**\n * WebSocket Handler 类型定义与 socket 封装\n *\n * 用户在 handler.ts 中导出 WS 函数,返回事件回调对象。\n * 框架在协议升级成功后调用对应回调,传递封装后的 WsSocket。\n *\n * @see wsHandler.md 功能说明\n */\n\nimport { stringifyJson } from '../utils/stringifyJson';\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 *\n * 输入口径与 HTTP 一致(二分):params/query 为校验转换后的值,rawParams/rawQuery\n * 恒为原始值。声明来源是 handler.ts 的文件级约定——导出 `WS` 且同文件声明\n * `export interface Query` / `export interface Params` 时,握手阶段按声明类型\n * 校验并转换(失败拒绝握手);未声明时 params/query 保持 URL 原始字符串。\n */\nexport interface WsContext {\n /** 动态路由参数(声明 Params 时校验转换,未声明为 URL 原始字符串) */\n params: Record<string, string | number | boolean>;\n /** 动态路由参数的原始值(恒 URL 字符串,校验不触及) */\n rawParams: Record<string, string>;\n /** URL 查询参数(声明 Query 时校验转换,未声明为原始字符串对象) */\n query: Record<string, unknown>;\n /** URL 查询参数的原始值(恒 URLSearchParams) */\n rawQuery: URLSearchParams;\n /** 请求头 */\n headers: Headers;\n /** 业务配置(来自 faapi.config.ts) */\n config: Record<string, unknown>;\n /**\n * 运行时资源根目录绝对路径(<rootDir>/<dist>/resources)\n *\n * 与 HTTP FaapiContext 同源(握手 ctx 是 FaapiContext 的结构子集),handler\n * 的 WS 回调里读静态文件用。testing 直调未传时为 undefined。\n */\n resourcesDir?: string;\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 // BigInt 安全序列化(含 BigInt 的对象 send 不再抛错),见 utils/stringifyJson\n const payload =\n typeof data === 'string' || Buffer.isBuffer(data) ? data : stringifyJson(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":";AAWA,IAAM,wBAAwB,oBAAI,QAAkB;AAG7C,SAAS,iBAAiB,UAA0B;AACzD,wBAAsB,IAAI,QAAQ;AACpC;AAGO,SAAS,gBAAgB,UAA6B;AAC3D,SAAO,sBAAsB,IAAI,QAAQ;AAC3C;;;ACrBO,SAAS,cAAc,OAAkD;AAC9E,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,WAAO;AAAA,EACT;AAGA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO;AAAA,EACT;AAGA,QAAM,QAAQ,OAAO,eAAe,KAAK;AACzC,SAAO,UAAU,QAAQ,UAAU,OAAO;AAC5C;;;ACaO,SAAS,cAAc,OAAwB;AAKpD,MAAI,CAAC,gBAAgB,OAAO,oBAAI,IAAY,CAAC,GAAG;AAC9C,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,SAAO,KAAK,UAAU,aAAa,OAAO,oBAAI,IAAY,CAAC,CAAC;AAC9D;AAQA,SAAS,gBAAgB,OAAgB,WAAiC;AACxE,MAAI,UAAU,KAAM,QAAO;AAC3B,QAAM,OAAO,OAAO;AACpB,MAAI,SAAS,SAAU,QAAO;AAC9B,MAAI,SAAS,SAAU,QAAO,CAAC,OAAO,SAAS,KAAe;AAC9D,MAAI,SAAS,SAAU,QAAO;AAE9B,QAAM,MAAM;AACZ,MAAI,UAAU,IAAI,GAAG,GAAG;AACtB,UAAM,IAAI,UAAU,+CAA+C;AAAA,EACrE;AACA,MAAI,eAAe,QAAQ,eAAe,OAAO,eAAe,OAAO,eAAe,QAAQ;AAC5F,WAAO;AAAA,EACT;AAEA,MAAI,OAAQ,IAA6B,WAAW,WAAY,QAAO;AAEvE,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,cAAU,IAAI,GAAG;AACjB,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAI,gBAAgB,IAAI,CAAC,GAAG,SAAS,GAAG;AACtC,kBAAU,OAAO,GAAG;AACpB,eAAO;AAAA,MACT;AAAA,IACF;AACA,cAAU,OAAO,GAAG;AACpB,WAAO;AAAA,EACT;AACA,MAAI,cAAc,GAAG,GAAG;AACtB,cAAU,IAAI,GAAG;AAGjB,eAAW,OAAO,KAAK;AACrB,UAAI,CAAC,OAAO,UAAU,eAAe,KAAK,KAAK,GAAG,EAAG;AACrD,UAAI,gBAAiB,IAAgC,GAAG,GAAG,SAAS,GAAG;AACrE,kBAAU,OAAO,GAAG;AACpB,eAAO;AAAA,MACT;AAAA,IACF;AACA,cAAU,OAAO,GAAG;AACpB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAGA,SAAS,aAAa,OAAgB,WAAiC;AAErE,MAAI,UAAU,KAAM,QAAO;AAC3B,QAAM,OAAO,OAAO;AACpB,MAAI,SAAS,SAAU,QAAQ,MAAiB,SAAS;AACzD,MAAI,SAAS,SAAU,QAAO,OAAO,SAAS,KAAe,IAAI,QAAQ,OAAO,KAAK;AACrF,MAAI,SAAS,SAAU,QAAO;AAE9B,QAAM,MAAM;AACZ,MAAI,UAAU,IAAI,GAAG,GAAG;AACtB,UAAM,IAAI,UAAU,+CAA+C;AAAA,EACrE;AAIA,MAAI,eAAe,KAAM,QAAO,IAAI,QAAQ;AAG5C,MAAI,eAAe,KAAK;AACtB,cAAU,IAAI,GAAG;AACjB,UAAM,UAAU,MAAM,KAAK,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM;AAAA,MAC1C,aAAa,GAAG,SAAS;AAAA,MACzB,aAAa,GAAG,SAAS;AAAA,IAC3B,CAAC;AACD,cAAU,OAAO,GAAG;AACpB,WAAO;AAAA,EACT;AACA,MAAI,eAAe,KAAK;AACtB,cAAU,IAAI,GAAG;AACjB,UAAM,SAAS,MAAM,KAAK,KAAK,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;AAChE,cAAU,OAAO,GAAG;AACpB,WAAO;AAAA,EACT;AACA,MAAI,eAAe,OAAQ,QAAO,IAAI,SAAS;AAG/C,QAAM,SAAU,IAA6B;AAC7C,MAAI,OAAO,WAAW,YAAY;AAChC,cAAU,IAAI,GAAG;AACjB,UAAM,SAAS,aAAc,OAAyB,KAAK,GAAG,GAAG,SAAS;AAC1E,cAAU,OAAO,GAAG;AACpB,WAAO;AAAA,EACT;AAGA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,cAAU,IAAI,GAAG;AACjB,UAAM,MAAM,IAAI,IAAI,CAAC,SAAS,aAAa,MAAM,SAAS,CAAC;AAC3D,cAAU,OAAO,GAAG;AACpB,WAAO;AAAA,EACT;AACA,MAAI,cAAc,GAAG,GAAG;AACtB,cAAU,IAAI,GAAG;AACjB,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC5C,UAAI,GAAG,IAAI,aAAa,KAAK,SAAS;AAAA,IACxC;AACA,cAAU,OAAO,GAAG;AACpB,WAAO;AAAA,EACT;AAIA,SAAO;AACT;;;AC9FO,SAAS,eAAe,OAAyB;AACtD,MAAI,MAAM;AAGV,MAAI,MAAM,YAAY,QAAW;AAC/B,WAAO,KAAK,MAAM,OAAO;AAAA;AAAA,EAC3B;AAGA,MAAI,MAAM,UAAU,QAAW;AAC7B,WAAO,UAAU,MAAM,KAAK;AAAA;AAAA,EAC9B;AAGA,MAAI,MAAM,OAAO,QAAW;AAC1B,WAAO,OAAO,MAAM,EAAE;AAAA;AAAA,EACxB;AAGA,MAAI,MAAM,UAAU,QAAW;AAC7B,WAAO,UAAU,MAAM,KAAK;AAAA;AAAA,EAC9B;AAGA,MAAI,MAAM,SAAS,QAAW;AAC5B,QAAI;AACJ,QAAI,OAAO,MAAM,SAAS,UAAU;AAClC,gBAAU,MAAM;AAAA,IAClB,WAAW,MAAM,SAAS,MAAM;AAC9B,gBAAU;AAAA,IACZ,OAAO;AACL,gBAAU,cAAc,MAAM,IAAI;AAAA,IACpC;AAEA,UAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,eAAW,QAAQ,OAAO;AACxB,aAAO,SAAS,IAAI;AAAA;AAAA,IACtB;AAAA,EACF;AAGA,SAAO;AACP,SAAO;AACT;AA2FO,SAAS,gBACd,UAAwE,CAAC,GAC9D;AACX,QAAM,EAAE,cAAc,WAAW,MAAM,IAAI;AAC3C,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,aAAiE;AACrE,MAAI,SAAS;AACb,MAAI,UAAU;AAEd,MAAI,sBAAsB;AAE1B,MAAI,eAAkC,CAAC;AAEvC,MAAI,iBAAoC,CAAC;AAEzC,MAAI,sBAAsB;AAE1B,QAAM,kBAAkB,CAAC,aAA+B;AACtD,QAAI;AACF,eAAS;AAAA,IACX,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,oBAAoB,MAAY;AACpC,QAAI,oBAAqB;AACzB,0BAAsB;AACtB,UAAM,YAAY;AAClB,qBAAiB,CAAC;AAClB,eAAW,YAAY,UAAW,iBAAgB,QAAQ;AAAA,EAC5D;AAEA,QAAM,sBAAsB,MAAY;AACtC,UAAM,UAAU;AAChB,mBAAe,CAAC;AAChB,eAAW,WAAW,QAAS,SAAQ;AAAA,EACzC;AAIA,QAAM,oBAAoB,MAAY;AACpC,QAAI,oBAAqB;AACzB,0BAAsB;AACtB,mBAAe,QAAQ;AAAA,EACzB;AAIA,QAAM,SAAS,IAAI;AAAA,IACjB;AAAA,MACE,MAAM,GAAG;AACP,qBAAa;AAAA,MACf;AAAA;AAAA,MAEA,OAAO;AACL,4BAAoB;AAAA,MACtB;AAAA,MACA,SAAS;AAEP,kBAAU;AACV,iBAAS;AACT,qBAAa;AAEb,0BAAkB;AAClB,4BAAoB;AAAA,MACtB;AAAA,IACF;AAAA,IACA,EAAE,eAAe,GAAG;AAAA,EACtB;AAEA,QAAM,WAAW,IAAI,SAAS,QAAQ;AAAA,IACpC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,YAAY;AAAA;AAAA,MAEZ,qBAAqB;AAAA,IACvB;AAAA,EACF,CAAC;AAED,QAAM,SAAoB;AAAA,IACxB,KAAK,OAAuB;AAC1B,UAAI,UAAU,CAAC,WAAY;AAC3B,YAAM,OAAO,eAAe,KAAK;AACjC,iBAAW,QAAQ,QAAQ,OAAO,IAAI,CAAC;AACvC,wBAAkB;AAAA,IACpB;AAAA,IAEA,QAAQ,OAAkC;AACxC,UAAI,UAAU,CAAC,WAAY;AAC3B,YAAM,QAAQ,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK,IAAI;AAClE,iBAAW,QAAQ,KAAK;AACxB,wBAAkB;AAAA,IACpB;AAAA,IAEA,UAAU,OAAsB;AAC9B,UAAI,UAAU,CAAC,WAAY;AAC3B,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAM,OAAO,eAAe,EAAE,OAAO,SAAS,MAAM,QAAQ,CAAC;AAC7D,UAAI;AACF,mBAAW,QAAQ,QAAQ,OAAO,IAAI,CAAC;AACvC,0BAAkB;AAAA,MACpB,UAAE;AACA,eAAO,MAAM;AAAA,MACf;AAAA,IACF;AAAA,IAEA,QAAc;AACZ,UAAI,OAAQ;AACZ,eAAS;AAET,wBAAkB;AAElB,0BAAoB;AACpB,UAAI,YAAY;AACd,YAAI;AACF,qBAAW,MAAM;AAAA,QACnB,QAAQ;AAAA,QAER;AACA,qBAAa;AAAA,MACf;AAAA,IACF;AAAA,IAEA,QAAQ,UAA4B;AAElC,UAAI,qBAAqB;AACvB,wBAAgB,QAAQ;AACxB;AAAA,MACF;AACA,qBAAe,KAAK,QAAQ;AAAA,IAC9B;AAAA,IAEA,IAAI,SAAkB;AACpB,aAAO;AAAA,IACT;AAAA,IAEA,IAAI,cAA6B;AAC/B,aAAO,aAAa,WAAW,cAAc;AAAA,IAC/C;AAAA,IAEA,eAA8B;AAE5B,UAAI,UAAU,CAAC,WAAY,QAAO,QAAQ,QAAQ;AAElD,WAAK,WAAW,eAAe,KAAK,EAAG,QAAO,QAAQ,QAAQ;AAC9D,aAAO,IAAI,QAAc,CAAC,YAAY;AACpC,qBAAa,KAAK,OAAO;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,IAEA,IAAI,UAAmB;AACrB,aAAO;AAAA,IACT;AAAA,IAEA,IAAI,WAAoB;AACtB,aAAO;AAAA,IACT;AAAA,IAEA,IAAI,WAAqB;AACvB,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;;;ACvWO,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACkB,MAChB,SACgB,YAChB;AACA,UAAM,OAAO;AAJG;AAEA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EANkB;AAAA,EAEA;AAKpB;;;ACKA,SAAS,iBAAiB,QAAmC;AAE3D,QAAM,SAAS,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,oBAAoB,EAAE,SAAS,eAAe;AAC3F,SAAO,SAAS,MAAM;AACxB;AAEO,IAAM,kBAAN,cAA8B,WAAW;AAAA,EAC9C,YACE,SACgB,QAChB;AACA,UAAM,oBAAoB,SAAS,iBAAiB,MAAM,CAAC;AAF3C;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EAJkB;AAKpB;AAEO,IAAM,qBAAN,cAAiC,WAAW;AAAA,EACjD,YAAYA,QAAc;AACxB,UAAM,mBAAmB,oBAAoBA,MAAI,IAAI,GAAG;AACxD,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,wBAAN,cAAoC,WAAW;AAAA,EACpD,YACE,QACAA,QACgB,gBAChB;AACA,UAAM,sBAAsB,UAAU,MAAM,oBAAoBA,MAAI,IAAI,GAAG;AAF3D;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EAJkB;AAKpB;AAEO,IAAM,gBAAN,cAA4B,WAAW;AAAA,EAC5C,YAAY,SAAiB;AAC3B,UAAM,kBAAkB,SAAS,GAAG;AACpC,SAAK,OAAO;AAAA,EACd;AACF;AAeO,IAAM,uBAAN,cAAmC,WAAW;AAAA,EACnD,YAAY,SAAiB;AAC3B,UAAM,qBAAqB,sCAAsC,OAAO,UAAU,GAAG;AACrF,SAAK,OAAO;AAAA,EACd;AACF;;;ACpDO,SAAS,UAAU,MAAwC;AAChE,SAAO,EAAE,KAAK;AAChB;AAOO,SAAS,YAAY,GAIA;AAC1B,QAAM,QAAiC,EAAE,SAAS,EAAE,QAAQ;AAC5D,MAAI,EAAE,SAAS,OAAW,OAAM,OAAO,EAAE;AACzC,SAAO,EAAE,MAAM;AACjB;AAGO,SAAS,kBACd,QAC4B;AAC5B,SAAQ,QAAsD;AAChE;AAGO,SAAS,YAAY,QAA6C;AACvE,SAAO,kBAAkB,MAAM,GAAG,MAAM;AAC1C;AAGO,SAAS,cAAc,QAA6C;AACzE,SAAO,kBAAkB,MAAM,GAAG,QAAQ;AAC5C;AAOO,SAAS,OAAO,MAAe,SAAS,KAAK,cAAsC;AACxF,SAAO,QAAQ,MAAM,QAAQ,YAAY;AAC3C;AAGA,SAAS,QAAQ,MAAe,QAAgB,cAAsC;AACpF,QAAM,UAAU,IAAI,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AAClE,MAAI,cAAc;AAChB,UAAM,QAAQ,IAAI,QAAQ,YAAY;AACtC,UAAM,QAAQ,CAAC,OAAO,QAAQ,QAAQ,IAAI,KAAK,KAAK,CAAC;AAAA,EACvD;AACA,QAAM,WAAW,IAAI,SAAS,cAAc,IAAI,GAAG,EAAE,QAAQ,QAAQ,CAAC;AACtE,mBAAiB,QAAQ;AACzB,SAAO;AACT;AAcO,SAAS,aACd,QACA,QACS;AACT,MAAI,kBAAkB,SAAU,QAAO;AACvC,SAAO,YAAY,MAAM,EAAE,MAAM;AACnC;AAUO,SAAS,mBACd,SACA,QACU;AACV,QAAM,SAAS,cAAc,MAAM;AACnC,QAAM,OAAO,OAAO;AAAA,IAClB,QAAQ,QAAQ;AAAA,IAChB,MAAM,QAAQ;AAAA,IACd,SAAS,QAAQ;AAAA,EACnB,CAAC;AACD,SAAO,OAAO,MAAM,QAAQ,UAAU,GAAG;AAC3C;AAkBO,SAAS,oBACd,OACA,QACU;AACV,QAAM,SAAS,cAAc,MAAM;AAInC,QAAM,gBAAgB,CAAC,MACrB,OAAO,EAAE,QAAQ,EAAE,YAAY,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ,CAAC;AAEnE,MAAI,iBAAiB,iBAAiB;AACpC,UAAMC,QAAO,cAAc,KAAK;AAIhC,UAAM,UACJ,OAAOA,UAAS,YAAYA,UAAS,OAChC,EAAE,GAAIA,MAAiC,IACxC,EAAE,OAAOA,MAAK;AACpB,UAAM,gBAAgB,QAAQ;AAC9B,QAAI,iBAAiB,OAAO,kBAAkB,UAAU;AACtD,cAAQ,QAAQ,EAAE,GAAG,eAAe,QAAQ,MAAM,OAAO;AAAA,IAC3D,WAAW,OAAOA,UAAS,YAAYA,UAAS,MAAM;AAEpD,cAAQ,SAAS,MAAM;AAAA,IACzB,OAAO;AACL,cAAQ,QAAQ,EAAE,QAAQ,MAAM,OAAO;AAAA,IACzC;AACA,WAAO,OAAO,SAAS,MAAM,UAAU;AAAA,EACzC;AAEA,MAAI,iBAAiB,uBAAuB;AAC1C,WAAO,OAAO,cAAc,KAAK,GAAG,MAAM,YAAY;AAAA,MACpD,OAAO,MAAM,eAAe,KAAK,IAAI;AAAA,IACvC,CAAC;AAAA,EACH;AAEA,MAAI,iBAAiB,sBAAsB;AACzC,WAAO,OAAO,cAAc,KAAK,GAAG,MAAM,UAAU;AAAA,EACtD;AAEA,MAAI,iBAAiB,YAAY;AAC/B,WAAO,OAAO,cAAc,KAAK,GAAG,MAAM,UAAU;AAAA,EACtD;AAGA,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,QAAM,OAAO,OAAO,EAAE,QAAQ,KAAK,MAAM,kBAAkB,QAAQ,CAAC;AACpE,SAAO,OAAO,MAAM,GAAG;AACzB;;;AChLA,SAAS,oBAAoB,OAAyB;AACpD,MAAI,iBAAiB,OAAO;AAC1B,WAAO,EAAE,MAAM,MAAM,MAAM,SAAS,MAAM,SAAS,OAAO,MAAM,MAAM;AAAA,EACxE;AACA,SAAO;AACT;AAGO,SAAS,YAAY,OAAyB;AACnD,MAAI,OAAO,IAAI,MAAM,IAAI,KAAK,MAAM,MAAM,YAAY,CAAC;AACvD,MAAI,MAAM,MAAO,SAAQ,IAAI,MAAM,KAAK;AACxC,UAAQ,MAAM;AACd,MAAI,MAAM,WAAW,QAAW;AAC9B,UAAM,QAAQ,OAAO;AAAA,MACnB,OAAO,QAAQ,MAAM,MAAM,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC;AAAA,IAC1E;AACA,QAAI;AACF,cAAQ,IAAI,KAAK,UAAU,KAAK,CAAC;AAAA,IACnC,SAAS,KAAK;AAEZ,cAAQ,4BAA4B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACtF;AAAA,EACF;AACA,SAAO;AACT;;;ACLA,IAAM,aAAuC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE;AAwCpF,IAAM,oBAAoB,uBAAO,IAAI,oBAAoB;AAEzD,SAAS,4BAA0C;AACjD,SAAO;AAAA,IACL,OAAO;AAAA,IACP,cAAc;AAAA,IACd,MAAM;AAAA,IACN,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,UAAU;AAAA,EACZ;AACF;AAGA,SAAS,kBAAgC;AACvC,QAAM,IAAI;AACV,MAAIC,SAAQ,EAAE,iBAAiB;AAC/B,MAAI,CAACA,QAAO;AACV,IAAAA,SAAQ,0BAA0B;AAClC,MAAE,iBAAiB,IAAIA;AAAA,EACzB;AACA,SAAOA;AACT;AAuDA,SAAS,oBAA0C;AACjD,SAAO,gBAAgB,EAAE,SAAS;AACpC;AAGA,SAAS,mBAAqC;AAC5C,SAAO,gBAAgB,EAAE,gBAAgB;AAC3C;AAGA,SAAS,eAAe,OAAiB,WAA0C;AACjF,SAAO,cAAc,UAAa,WAAW,KAAK,IAAI,WAAW,SAAS;AAC5E;AAWA,IAAM,iBAAwE;AAAA,EAC5E,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAGA,SAAS,gBAAgB,OAAuB;AAC9C,QAAM,YAAY,iBAAiB;AACnC,MAAI,cAAc,SAAS,eAAe,MAAM,OAAO,SAAS,EAAG;AACnE,UAAQ,eAAe,MAAM,KAAK,CAAC,EAAE,YAAY,KAAK,CAAC;AACzD;AAUA,SAAS,eAAe,OAAuB;AAC7C,QAAMC,SAAQ,gBAAgB;AAC9B,MAAIA,OAAM,SAAU;AACpB,QAAM,gBAAgB,kBAAkB;AACxC,MAAIA,OAAM,MAAM;AACd,QAAI,CAAC,eAAe,MAAM,OAAO,aAAa,EAAG,CAAAA,OAAM,KAAK,KAAK;AACjE;AAAA,EACF;AACA,MAAIA,OAAM,UAAU;AAClB,QAAI,CAAC,eAAe,MAAM,OAAO,aAAa,EAAG,CAAAA,OAAM,SAAS,MAAM,KAAK;AAC3E,QAAIA,OAAM,OAAQ,iBAAgB,KAAK;AACvC;AAAA,EACF;AACA,kBAAgB,KAAK;AACvB;AAQO,SAAS,cAAc,OAAuB;AACnD,iBAAe,KAAK;AACtB;AAmBO,SAAS,qBAA8B;AAC5C,QAAMC,SAAQ,gBAAgB;AAC9B,SAAO,CAACA,OAAM,YAAYA,OAAM;AAClC;AAQO,SAAS,aAAa,OAAgB,SAAuC;AAClF,QAAM,YAAY;AAClB,QAAM,aAAa,SAAS;AAE5B,QAAM,QAAQ,CACZ,OACA,SACA,YACA,gBACS;AAET,QAAI,eAAe,OAAO,SAAS,KAAK,EAAG;AAC3C,UAAM,QAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA,OAAM,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC/B;AACA,UAAM,YACJ,gBAAgB,SACZ,YACA,cAAc,SACZ,cACA,GAAG,SAAS,IAAI,WAAW;AACnC,QAAI,cAAc,OAAW,OAAM,QAAQ;AAC3C,QAAI,eAAe,UAAa,eAAe,QAAW;AACxD,YAAM,SAAS,EAAE,GAAG,YAAY,GAAG,WAAW;AAAA,IAChD;AACA,QAAI,SAAS,MAAM;AAEjB,cAAQ,KAAK,KAAK;AAAA,IACpB,OAAO;AACL,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AAEA,QAAM,aAAa,CAAC,iBAA6C;AAAA,IAC/D,OAAO,CAAC,SAAS,WAAW,MAAM,SAAS,SAAS,QAAQ,WAAW;AAAA,IACvE,MAAM,CAAC,SAAS,WAAW,MAAM,QAAQ,SAAS,QAAQ,WAAW;AAAA,IACrE,MAAM,CAAC,SAAS,WAAW,MAAM,QAAQ,SAAS,QAAQ,WAAW;AAAA,IACrE,OAAO,CAAC,SAAS,WAAW,MAAM,SAAS,SAAS,QAAQ,WAAW;AAAA,IACvE,OAAO,CAAC,eACN,WAAW,gBAAgB,SAAY,aAAa,GAAG,WAAW,IAAI,UAAU,EAAE;AAAA,EACtF;AAEA,SAAO,WAAW,MAAS;AAC7B;;;AClRA,IAAM,aAAa,oBAAI,QAAkD;AAElE,SAAS,cAAc,QAAkD;AAC9E,QAAM,SAAS,WAAW,IAAI,MAAM;AACpC,MAAI,OAAQ,QAAO;AACnB,QAAM,SAAkC,CAAC;AACzC,aAAW,CAAC,KAAK,KAAK,KAAK,QAAQ;AACjC,UAAM,WAAW,OAAO,GAAG;AAC3B,QAAI,aAAa,QAAW;AAC1B,aAAO,GAAG,IAAI;AAAA,IAChB,WAAW,MAAM,QAAQ,QAAQ,GAAG;AAClC,eAAS,KAAK,KAAK;AAAA,IACrB,OAAO;AACL,aAAO,GAAG,IAAI,CAAC,UAAU,KAAK;AAAA,IAChC;AAAA,EACF;AACA,aAAW,IAAI,QAAQ,MAAM;AAC7B,SAAO;AACT;;;ACpCA,OAAOC,WAAU;AACjB,OAAOC,SAAQ;;;ACDf,OAAO,UAAU;AACjB,OAAO,QAAQ;AAGR,IAAM,UAAU;AAWhB,SAAS,eAAe,UAAkB,MAAsB;AACrE,MAAI,MAAM,SAAS,QAAQ,OAAO,GAAG;AAErC,MAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,UAAM,IAAI,MAAM,CAAC;AAAA,EACnB;AACA,QAAM,SAAS,IAAI,QAAQ,SAAS,KAAK;AACzC,SAAO,OAAO,WAAW,GAAG,IAAI,GAAG,IAAI,SAAS,GAAG,IAAI,IAAI,MAAM;AACnE;AAKO,SAAS,gBAAgB,UAA0B;AACxD,MAAI,SAAS,SAAS,KAAK,EAAG,QAAO,SAAS,MAAM,GAAG,EAAE,IAAI;AAC7D,MAAI,SAAS,SAAS,MAAM,EAAG,QAAO,SAAS,MAAM,GAAG,EAAE,IAAI;AAC9D,MAAI,SAAS,SAAS,MAAM,EAAG,QAAO,SAAS,MAAM,GAAG,EAAE,IAAI;AAC9D,SAAO;AACT;AAOO,SAAS,WAAW,GAAmB;AAC5C,MAAI;AACF,WAAO,GAAG,aAAa,CAAC;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQO,SAAS,YAAY,UAAkB,KAAsB;AAClE,QAAM,MAAM,KAAK,SAAS,KAAK,QAAQ;AACvC,SAAO,QAAQ,MAAM,CAAC,IAAI,WAAW,IAAI,KAAK,CAAC,KAAK,WAAW,GAAG;AACpE;;;AD5CA,IAAM,2BAA2B,uBAAO,IAAI,qBAAqB;AAY1D,SAAS,sBAAsB,KAA0B;AAC9D,MAAI,QAAQ,MAAM;AAChB,WAAQ,WAAkD,wBAAwB;AAAA,EACpF,OAAO;AACL,IAAC,WAAkD,wBAAwB,IAAI;AAAA,EACjF;AACF;;;AEnBA,SAAS,iBAAiB,SAA0B;AAClD,QAAM,SAAS,QAAQ,QAAQ,IAAI,cAAc;AACjD,QAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AAC1C,MAAI,MAAO,QAAO;AAClB,SAAO,OAAO,WAAW;AAC3B;AAKA,SAAS,aAAa,cAA2C;AAC/D,QAAM,UAAU,oBAAI,IAAoB;AACxC,MAAI,CAAC,aAAc,QAAO;AAC1B,aAAW,QAAQ,aAAa,MAAM,GAAG,GAAG;AAC1C,UAAM,CAAC,MAAM,GAAG,IAAI,IAAI,KAAK,MAAM,GAAG;AACtC,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,SAAS;AACX,cAAQ,IAAI,SAAS,KAAK,KAAK,GAAG,EAAE,KAAK,CAAC;AAAA,IAC5C;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,gBAAgB,MAAc,OAAe,SAAiC;AACrF,MAAI,SAAS,GAAG,IAAI,IAAI,KAAK;AAC7B,MAAI,SAAS,OAAQ,WAAU,YAAY,QAAQ,MAAM;AACzD,MAAI,SAAS,KAAM,WAAU,UAAU,QAAQ,IAAI;AACnD,MAAI,SAAS,WAAW,OAAW,WAAU,aAAa,QAAQ,MAAM;AACxE,MAAI,SAAS,QAAS,WAAU,aAAa,QAAQ,QAAQ,YAAY,CAAC;AAC1E,MAAI,SAAS,SAAU,WAAU;AACjC,MAAI,SAAS,OAAQ,WAAU;AAC/B,MAAI,SAAS,SAAU,WAAU,cAAc,QAAQ,QAAQ;AAC/D,SAAO;AACT;AAYO,SAAS,cACd,SACA,QACA,SAAkC,CAAC,GACnC,KAAa,IACb,YACA,cACc;AACd,SAAO;AAAA,IACL;AAAA,IACA,IAAI,IAAI,QAAQ,GAAG;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AASO,SAAS,qBACd,SACA,KACA,QACA,SAAkC,CAAC,GACnC,KAAa,IACb,YACA,cACc;AACd,QAAM,OAAqB,EAAE,SAAS,CAAC,GAAG,YAAY,CAAC,EAAE;AACzD,QAAM,gBAAgB,aAAa,QAAQ,QAAQ,IAAI,QAAQ,KAAK,EAAE;AACtE,QAAM,aAAqC,CAAC;AAC5C,aAAW,CAAC,KAAK,GAAG,KAAK,eAAe;AACtC,eAAW,GAAG,IAAI;AAAA,EACpB;AAIA,QAAM,YAAY,iBAAiB,OAAO;AAC1C,QAAM,MAAM,aAAa,QAAQ;AAAA,IAC/B,QAAQ,EAAE,WAAW,QAAQ,QAAQ,QAAQ,MAAM,IAAI,SAAS;AAAA,EAClE,CAAC;AAED,QAAM,MAAM;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA;AAAA;AAAA,IAGX,OAAO,cAAc,IAAI,YAAY;AAAA,IACrC,UAAU,IAAI;AAAA,IACd,SAAS,QAAQ;AAAA,IACjB,QAAQ,QAAQ;AAAA,IAChB,MAAM,IAAI;AAAA,IACV;AAAA,IACA,IAAI,QAAQ,QAAQ,IAAI,YAAY,KAAK;AAAA,IACzC,SAAS;AAAA,IACT;AAAA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IAEA,UAAU,QAAgB;AACxB,WAAK,SAAS;AAAA,IAChB;AAAA,IAEA,UAAU,KAAa,OAAe;AACpC,WAAK,QAAQ,GAAG,IAAI;AAAA,IACtB;AAAA,IAEA,QAAQ,OAAe;AACrB,WAAK,QAAQ,MAAM,IAAI;AAAA,IACzB;AAAA,IAEA,SAASC,MAAa,SAAS,KAAe;AAC5C,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB;AAAA,QACA,SAAS,EAAE,UAAUA,KAAI;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,IAEA,KAAK,MAAe,QAA2B;AAC7C,aAAO,OAAO,MAAM,UAAU,GAAG;AAAA,IACnC;AAAA,IAEA,KAAK,MAAc,QAA2B;AAC5C,YAAM,UAAkC,EAAE,gBAAgB,2BAA2B;AACrF,YAAM,WAAW,IAAI,SAAS,MAAM;AAAA,QAClC,QAAQ,UAAU;AAAA,QAClB;AAAA,MACF,CAAC;AACD,uBAAiB,QAAQ;AACzB,aAAO;AAAA,IACT;AAAA,IAEA,UAAU,MAAkC;AAC1C,aAAO,cAAc,IAAI,IAAI;AAAA,IAC/B;AAAA,IAEA,UAAU,MAAc,OAAe,SAA+B;AACpE,WAAK,WAAW,KAAK,gBAAgB,MAAM,OAAO,OAAO,CAAC;AAAA,IAC5D;AAAA,IAEA,aAAa,MAAoB;AAC/B,WAAK,WAAW,KAAK,gBAAgB,MAAM,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;AAAA,IAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgBA,IAAI,SAAiC;AACnC,YAAM,SAAS,gBAAgB;AAAA,QAC7B,UAAU,SAAS;AAAA,QACnB,cAAc,CAAC,aAAa;AAC1B,UACE,IACA,iBAAiB,QAAQ;AAAA,QAC7B;AAAA,MACF,CAAC;AACD,YAAM,aAAa;AAInB,iBAAW,gBAAgB,OAAO;AAClC,iBAAW,cAAc;AACzB,aAAO;AAAA,IACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWA,GAAG,MAAyB;AAC1B,YAAM,OAAO,aAAa,MAAM,MAAM;AAGtC,aAAO,OAAO,MAAM,GAAG;AAAA,IACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAcA,KAAK,SAAgC;AACnC,aAAO,mBAAmB,SAAS,MAAM;AAAA,IAC3C;AAAA,EACF;AAGA,MAAI,YAAY;AACd,QAAI,aAAa;AAGjB,QAAI,QAAQ,WAAW,WAAW,IAAI,GAAG;AAAA,EAC3C;AAGA,QAAM,SAAS,QAAQ;AACvB,MAAI,OAAO,WAAW,YAAY;AAChC,WAAO,GAAG;AAAA,EACZ;AAEA,SAAO;AACT;AAkBO,SAAS,kBAAkB,SAAiD;AACjF,QAAM;AAAA,IACJ,SAAS;AAAA,IACT,MAAAC;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,CAAC;AAAA,IACV,SAAS,CAAC;AAAA,IACV,KAAK;AAAA,IACL;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,QAAM,MAAM,IAAI,IAAI,mBAAmBA,MAAI,EAAE;AAC7C,MAAI,OAAO;AACT,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,UAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,mBAAW,QAAQ,OAAO;AACxB,cAAI,aAAa,OAAO,KAAK,OAAO,IAAI,CAAC;AAAA,QAC3C;AAAA,MACF,OAAO;AACL,YAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,IAAI,QAAQ,IAAI,SAAS,GAAG;AAAA,IAC1C;AAAA,IACA;AAAA,EACF,CAAC;AAKD,MAAI,cAAc;AAChB,0BAAsB,YAAY;AAAA,EACpC;AAEA,SAAO,cAAc,SAAS,QAAQ,QAAQ,IAAI,YAAY,YAAY;AAC5E;;;AC3SA,IAAM,UAAU,oBAAI,QAA0C;AAOvD,SAAS,iBAAiB,UAAoB,SAAuC;AAC1F,QAAM,WAAW,QAAQ,IAAI,QAAQ;AACrC,MAAI,UAAU;AACZ,WAAO,OAAO,UAAU,OAAO;AAC/B;AAAA,EACF;AACA,UAAQ,IAAI,UAAU,EAAE,GAAG,QAAQ,CAAC;AACtC;AAKO,SAAS,0BAA0B,UAAwD;AAChG,QAAM,UAAU,QAAQ,IAAI,QAAQ;AACpC,MAAI,QAAS,SAAQ,OAAO,QAAQ;AACpC,SAAO;AACT;AAGO,SAAS,kBAAkB,MAA6B;AAC7D,SAAO,KAAK,WAAW,UAAa,KAAK,WAAW,WAAW;AACjE;;;ACtBA,eAAsB,WAAW,OAAgB,MAAwC;AAEvF,MAAI,iBAAiB,SAAS;AAC5B,WAAO,WAAW,MAAM,OAAO,IAAI;AAAA,EACrC;AAGA,QAAM,YAAY,CAACC,aAA2B;AAC5C,QAAI,CAAC,KAAM;AACX,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,KAAK,OAAO,GAAG;AACrD,MAAAA,SAAQ,IAAI,KAAK,GAAG;AAAA,IACtB;AACA,eAAW,UAAU,KAAK,cAAc,CAAC,GAAG;AAC1C,MAAAA,SAAQ,OAAO,cAAc,MAAM;AAAA,IACrC;AAAA,EACF;AAGA,MAAI,iBAAiB,UAAU;AAC7B,QACE,SACC,KAAK,WAAW,UACf,OAAO,KAAK,KAAK,OAAO,EAAE,SAAS,KACnC,KAAK,WAAW,SAAS,IAC3B;AAGA,UAAI,kBAAkB,IAAI,GAAG;AAC3B,yBAAiB,OAAO,KAAK,OAAO;AACpC,eAAO;AAAA,MACT;AACA,YAAMA,WAAU,IAAI,QAAQ,MAAM,OAAO;AACzC,gBAAUA,QAAO;AACjB,aAAO,IAAI,SAAS,MAAM,MAAM;AAAA,QAC9B,QAAQ,KAAK,UAAU,MAAM;AAAA,QAC7B,SAAAA;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAGA,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAMA,WAAU,IAAI,QAAQ;AAC5B,cAAUA,QAAO;AACjB,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,SAAAA,SAAQ,CAAC;AAAA,EAC/C;AAGA,MAAI,OAAO,mBAAmB,eAAe,iBAAiB,gBAAgB;AAC5E,UAAMA,WAAU,IAAI,QAAQ,EAAE,gBAAgB,2BAA2B,CAAC;AAC1E,cAAUA,QAAO;AACjB,WAAO,IAAI,SAAS,OAAO;AAAA,MACzB,QAAQ,MAAM,UAAU;AAAA,MACxB,SAAAA;AAAA,IACF,CAAC;AAAA,EACH;AAGA,MAAI,OAAO,WAAW,eAAe,OAAO,SAAS,KAAK,GAAG;AAC3D,UAAMA,WAAU,IAAI,QAAQ,EAAE,gBAAgB,2BAA2B,CAAC;AAC1E,cAAUA,QAAO;AACjB,WAAO;AAAA,MACL,IAAI,SAAS,OAAmB;AAAA,QAC9B,QAAQ,MAAM,UAAU;AAAA,QACxB,SAAAA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACA,MAAI,iBAAiB,YAAY;AAC/B,UAAMA,WAAU,IAAI,QAAQ,EAAE,gBAAgB,2BAA2B,CAAC;AAC1E,cAAUA,QAAO;AACjB,WAAO;AAAA,MACL,IAAI,SAAS,OAAmB;AAAA,QAC9B,QAAQ,MAAM,UAAU;AAAA,QACxB,SAAAA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,cAAc,KAAK,KAAK,MAAM,QAAQ,KAAK,GAAG;AAChD,UAAMC,QAAO,cAAc,KAAK;AAChC,UAAMD,WAAU,IAAI,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AAClE,cAAUA,QAAO;AACjB,WAAO;AAAA,MACL,IAAI,SAASC,OAAM;AAAA,QACjB,QAAQ,MAAM,UAAU;AAAA,QACxB,SAAAD;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAMA,WAAU,IAAI,QAAQ,EAAE,gBAAgB,aAAa,CAAC;AAC5D,cAAUA,QAAO;AACjB,WAAO;AAAA,MACL,IAAI,SAAS,OAAO;AAAA,QAClB,QAAQ,MAAM,UAAU;AAAA,QACxB,SAAAA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW;AAC3D,UAAMA,WAAU,IAAI,QAAQ,EAAE,gBAAgB,aAAa,CAAC;AAC5D,cAAUA,QAAO;AACjB,WAAO;AAAA,MACL,IAAI,SAAS,OAAO,KAAK,GAAG;AAAA,QAC1B,QAAQ,MAAM,UAAU;AAAA,QACxB,SAAAA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,OAAO,cAAc,KAAK;AAChC,QAAM,UAAU,IAAI,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AAClE,YAAU,OAAO;AACjB,SAAO;AAAA,IACL,IAAI,SAAS,MAAM;AAAA,MACjB,QAAQ,MAAM,UAAU;AAAA,MACxB;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAGA,SAAS,aAAa,UAA8B;AAClD,mBAAiB,QAAQ;AACzB,SAAO;AACT;;;AC3JA,OAAO,QAAQ;AAsDR,IAAM,iBAAgD;AAAA,EAC3D,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,KAAK;AAAA;AAAA,EACL,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA;AAAA,EACP,QAAQ;AAAA;AAAA,EACR,OAAO;AAAA;AAAA,EACP,KAAK;AAAA;AAAA;AAAA;AAAA,EAGL,UAAU;AAAA;AAAA,EACV,WAAW;AAAA;AAAA,EACX,SAAS;AAAA;AACX;AASA,IAAM,iBAAiB,oBAAI,QAAgC;AAYpD,SAAS,iBAAiB,IAAsD;AACrF,QAAM,SAAS,eAAe,IAAI,EAAE;AACpC,MAAI,QAAQ;AACV,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,GAAG,SAAS;AAC1B,QAAM,SAAS,qBAAqB,KAAK;AAEzC,QAAM,QAAQ,OAAO,IAAI,CAAC,UAAU;AAClC,UAAM,OAAO,eAAe,MAAM,IAAI,KAAK;AAC3C,WAAO;AAAA,MACL,MAAM,MAAM;AAAA,MACZ;AAAA,MACA,SAAS;AAAA;AAAA,IACX;AAAA,EACF,CAAC;AAED,iBAAe,IAAI,IAAI,KAAK;AAC5B,SAAO;AACT;AAWA,SAAS,qBAAqB,OAAwC;AAEpE,QAAM,aAAa,GAAG;AAAA,IACpB;AAAA,IACA;AAAA,IACA,GAAG,aAAa;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,aAAuB,CAAC;AAE9B,WAAS,MAAM,MAAqB;AAElC,QAAI,GAAG,sBAAsB,IAAI,KAAK,KAAK,WAAW,SAAS,GAAG;AAChE,iBAAW,SAAS,KAAK,YAAY;AACnC,yBAAiB,OAAO,UAAU;AAAA,MACpC;AACA;AAAA,IACF;AAGA,SAAK,GAAG,gBAAgB,IAAI,KAAK,GAAG,qBAAqB,IAAI,MAAM,KAAK,WAAW,SAAS,GAAG;AAC7F,iBAAW,SAAS,KAAK,YAAY;AACnC,yBAAiB,OAAO,UAAU;AAAA,MACpC;AACA;AAAA,IACF;AAEA,OAAG,aAAa,MAAM,KAAK;AAAA,EAC7B;AAEA,QAAM,UAAU;AAEhB,SAAO,WAAW,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE;AAC5C;AAYA,SAAS,iBAAiB,OAAgC,OAAuB;AAC/E,QAAM,OAAO,MAAM;AAGnB,MAAI,GAAG,aAAa,IAAI,GAAG;AACzB,UAAM,KAAK,KAAK,IAAI;AACpB;AAAA,EACF;AAGA,MAAI,GAAG,uBAAuB,IAAI,GAAG;AACnC,eAAW,WAAW,KAAK,UAAU;AACnC,UAAI,GAAG,iBAAiB,OAAO,GAAG;AAChC,cAAM,WAAW,QAAQ;AACzB,YAAI,GAAG,aAAa,QAAQ,GAAG;AAC7B,gBAAM,KAAK,SAAS,IAAI;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AACA;AAAA,EACF;AAGA,MAAI,GAAG,sBAAsB,IAAI,GAAG;AAClC,eAAW,WAAW,KAAK,UAAU;AACnC,UAAI,WAAW,GAAG,iBAAiB,OAAO,GAAG;AAC3C,cAAM,WAAW,QAAQ;AACzB,YAAI,GAAG,aAAa,QAAQ,GAAG;AAC7B,gBAAM,KAAK,SAAS,IAAI;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AACA;AAAA,EACF;AACF;;;AC9LO,SAAS,qBAAmC;AACjD,MAAI,WAAW,oBAAI,IAA0B;AAC7C,SAAO;AAAA,IACL,QAAQ,OAAO;AACb,YAAM,OAAO,oBAAI,IAA0B;AAC3C,iBAAW,QAAQ,OAAO;AACxB,aAAK,IAAI,KAAK,MAAM,IAAI;AAAA,MAC1B;AACA,iBAAW;AAAA,IACb;AAAA,IACA,IAAI,MAAM;AACR,aAAO,SAAS,IAAI,IAAI;AAAA,IAC1B;AAAA,IACA,OAAO;AACL,aAAO,MAAM,KAAK,SAAS,OAAO,CAAC;AAAA,IACrC;AAAA,IACA,QAAQ;AACN,iBAAW,oBAAI,IAAI;AAAA,IACrB;AAAA,EACF;AACF;;;ACLO,SAAS,qBAAmC;AACjD,MAAI,WAAW,oBAAI,IAA0B;AAC7C,SAAO;AAAA,IACL,QAAQ,OAAO;AACb,YAAM,OAAO,oBAAI,IAA0B;AAC3C,iBAAW,QAAQ,OAAO;AACxB,aAAK,IAAI,KAAK,MAAM,IAAI;AAAA,MAC1B;AACA,iBAAW;AAAA,IACb;AAAA,IACA,IAAI,MAAM;AACR,aAAO,SAAS,IAAI,IAAI;AAAA,IAC1B;AAAA,IACA,OAAO;AACL,aAAO,MAAM,KAAK,SAAS,OAAO,CAAC;AAAA,IACrC;AAAA,IACA,QAAQ;AACN,iBAAW,oBAAI,IAAI;AAAA,IACrB;AAAA,EACF;AACF;AAmCO,SAAS,oBAAoB,MAAmC;AACrE,MAAI,WAAW,oBAAI,IAA2B;AAE9C,QAAM,WAAW,CAAC,SAAwC,SAAS,IAAI,IAAI;AAE3E,SAAO;AAAA,IACL,QAAQ,QAAQ;AACd,YAAM,OAAO,oBAAI,IAA2B;AAC5C,iBAAW,SAAS,QAAQ;AAC1B,aAAK,IAAI,MAAM,MAAM,KAAK;AAAA,MAC5B;AACA,iBAAW;AAAA,IACb;AAAA,IACA;AAAA,IACA,cAAc,MAAM;AAClB,aAAO,SAAS,IAAI,IAAI;AAAA,IAC1B;AAAA,IACA,aAAa;AACX,YAAM,SAAS,oBAAI,IAAuB;AAC1C,iBAAW,SAAS,SAAS,OAAO,EAAG,QAAO,IAAI,MAAM,MAAM,KAAK;AACnE,aAAO,MAAM,KAAK,OAAO,OAAO,CAAC;AAAA,IACnC;AAAA,IACA,OAAO,MAAM;AACX,YAAM,QAAQ,SAAS,IAAI;AAC3B,UAAI,CAAC,MAAO,QAAO;AACnB,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,SAAS,MAAM,IAAI;AAAA,QACzB,WAAW,MAAM;AAAA,QACjB,aAAa,MAAM;AAAA,QACnB,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,IACA,kBAAkB,MAAM;AACtB,YAAM,QAAQ,SAAS,IAAI;AAC3B,UAAI,CAAC,MAAO,QAAO,CAAC;AAEpB,YAAM,SAAS,oBAAI,IAA0B;AAC7C,UAAI,MAAM,OAAO;AACf,mBAAW,YAAY,MAAM,OAAO;AAClC,gBAAM,WAAW,KAAK,IAAI,QAAQ;AAClC,cAAI,SAAU,QAAO,IAAI,SAAS,MAAM,QAAQ;AAAA,QAElD;AAAA,MACF;AACA,aAAO,MAAM,KAAK,OAAO,OAAO,CAAC;AAAA,IACnC;AAAA,IACA,iBAAiB,MAAM;AACrB,YAAM,QAAQ,SAAS,IAAI;AAC3B,UAAI,CAAC,SAAS,CAAC,MAAM,OAAQ,QAAO,CAAC;AAErC,YAAM,SAAsB,CAAC;AAC7B,iBAAW,WAAW,MAAM,QAAQ;AAClC,cAAM,MAAM,SAAS,OAAO;AAC5B,YAAI,IAAK,QAAO,KAAK,GAAG;AAAA,MAE1B;AACA,aAAO;AAAA,IACT;AAAA,IACA,QAAQ;AACN,iBAAW,oBAAI,IAAI;AAAA,IACrB;AAAA,EACF;AACF;AAeO,SAAS,sBAAqC;AACnD,MAAI,WAAW,oBAAI,IAAuB;AAC1C,SAAO;AAAA,IACL,QAAQ,QAAQ;AACd,YAAM,OAAO,oBAAI,IAAuB;AACxC,iBAAW,SAAS,QAAQ;AAC1B,aAAK,IAAI,MAAM,MAAM,KAAK;AAAA,MAC5B;AACA,iBAAW;AAAA,IACb;AAAA,IACA,OAAO,OAAO;AACZ,eAAS,IAAI,MAAM,MAAM,KAAK;AAAA,IAChC;AAAA,IACA,OAAO,MAAM;AACX,eAAS,OAAO,IAAI;AAAA,IACtB;AAAA,IACA,IAAI,MAAM;AACR,aAAO,SAAS,IAAI,IAAI;AAAA,IAC1B;AAAA,IACA,OAAO;AACL,aAAO,MAAM,KAAK,SAAS,OAAO,CAAC;AAAA,IACrC;AAAA,IACA,QAAQ;AACN,iBAAW,oBAAI,IAAI;AAAA,IACrB;AAAA,EACF;AACF;AAeO,SAAS,yBAA2C;AACzD,MAAI,iBAA4C;AAChD,SAAO;AAAA,IACL,SAAS,SAAS;AAChB,uBAAiB;AAAA,IACnB;AAAA,IACA,IAAI,KAAK;AACP,UAAI,mBAAmB,KAAM,QAAO;AACpC,aAAO,eAAe,GAAG;AAAA,IAC3B;AAAA,IACA,QAAQ;AACN,uBAAiB;AAAA,IACnB;AAAA,EACF;AACF;AAeO,SAAS,wBAAyC;AACvD,MAAI,iBAA2C;AAC/C,SAAO;AAAA,IACL,SAAS,SAAS;AAChB,uBAAiB;AAAA,IACnB;AAAA,IACA,IAAI,KAAK;AACP,UAAI,mBAAmB,KAAM,QAAO;AACpC,aAAO,eAAe,GAAG;AAAA,IAC3B;AAAA,IACA,QAAQ;AACN,uBAAiB;AAAA,IACnB;AAAA,EACF;AACF;AA0EO,SAAS,sBAAqC;AACnD,QAAM,OAAO,mBAAmB;AAChC,QAAM,QAAQ,oBAAoB,IAAI;AACtC,QAAM,QAAQ,oBAAoB;AAClC,QAAM,OAAO,mBAAmB;AAChC,QAAM,cAAc,uBAAuB;AAC3C,QAAM,aAAa,sBAAsB;AACzC,SAAO,EAAE,MAAM,OAAO,OAAO,MAAM,aAAa,WAAW;AAC7D;AAQO,IAAM,oBAAmC,oBAAoB;;;ACjR7D,SAAS,aAA0B;AACxC,SAAO,kBAAkB,MAAM,WAAW;AAC5C;;;ACxBO,SAAS,eAAe,KAA4B;AACzD,SAAO,kBAAkB,YAAY,IAAI,GAAG;AAC9C;;;AC9BA,SAAS,yBAAyB,MAAqB,KAAmB,MAAyB;AACjG,UAAQ,MAAM;AAAA,IACZ,KAAK;AAIH,aAAO,IAAI;AAAA,IACb,KAAK;AAGH,aAAO,IAAI;AAAA;AAAA,IAEb,KAAK;AACH,aAAO,IAAI;AAAA,IACb,KAAK;AACH,aAAO,IAAI;AAAA,IACb,KAAK;AACH,aAAO,IAAI;AAAA,IACb,KAAK;AACH,aAAO,IAAI;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,IAAI;AAAA,IACb,KAAK;AACH,aAAO,IAAI;AAAA,IACb,KAAK;AACH,aAAO,IAAI;AAAA,IACb,KAAK;AACH,aAAO;AAAA;AAAA;AAAA,IAGT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,UAAI,QAAQ,OAAO,SAAS,YAAY,WAAW,MAAM;AACvD,eAAQ,KAAyB;AAAA,MACnC;AACA,aAAO,CAAC;AAAA,IACV,KAAK;AACH,UAAI,QAAQ,OAAO,SAAS,YAAY,YAAY,MAAM;AACxD,eAAQ,KAAyB;AAAA,MACnC;AACA,aAAO,CAAC;AAAA;AAAA;AAAA,IAGV,KAAK;AACH,aAAO,IAAI,aAAa,IAAI,WAAW,MAAM,WAAW,IAAI,WAAW;AAAA;AAAA,IAEzE,KAAK;AACH,aAAO,IAAI,aAAa,IAAI,WAAW,YAAY,IAAI,GAAG,IAAI,eAAe,GAAG;AAAA;AAAA,IAElF,KAAK;AACH,aAAO,IAAI,aAAa,IAAI,WAAW,WAAW,IAAI,GAAG,IAAI;AAAA;AAAA,IAE/D,KAAK;AACH,aAAO,IAAI;AAAA,IACb;AACE,aAAO;AAAA,EACX;AACF;AAQA,eAAsB,kBACpB,SACA,KACA,MACA,WACkB;AAClB,QAAM,aAAa,iBAAiB,OAAO;AAE3C,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,MAAM,QAAQ;AAAA,EACvB;AAEA,QAAM,OAAO,MAAM,QAAQ;AAAA,IACzB,WAAW,IAAI,OAAO,cAAc;AAElC,UAAI,UAAU,SAAS,WAAW;AAChC,eAAO,yBAAyB,UAAU,MAAM,KAAK,IAAI;AAAA,MAC3D;AAEA,UAAI,aAAa,UAAU,QAAQ,WAAW;AAE5C,eAAO,MAAM,UAAU,UAAU,IAAI,EAAG,GAAG;AAAA,MAC7C;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO,MAAM,QAAQ,GAAG,IAAI;AAC9B;;;ACtFA,SAAS,WAAW,QAAiB,KAA4B;AAC/D,SAAO,aAAa,QAAQ,IAAI,MAAM;AACxC;AAQO,SAAS,UAAU,UAAoB,MAA8B;AAC1E,QAAM,UACJ,KAAK,WAAW,UAAa,OAAO,KAAK,KAAK,OAAO,EAAE,SAAS,KAAK,KAAK,WAAW,SAAS;AAChG,MAAI,CAAC,QAAS,QAAO;AAGrB,MAAI,kBAAkB,IAAI,GAAG;AAC3B,qBAAiB,UAAU,KAAK,OAAO;AACvC,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;AAC5C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,OAAO,GAAG;AACvD,YAAQ,IAAI,KAAK,KAAK;AAAA,EACxB;AACA,aAAW,UAAU,KAAK,YAAY;AACpC,YAAQ,OAAO,cAAc,MAAM;AAAA,EACrC;AACA,SAAO,IAAI,SAAS,SAAS,MAAM;AAAA,IACjC,QAAQ,KAAK,UAAU,SAAS;AAAA,IAChC;AAAA,EACF,CAAC;AACH;AAmBA,eAAsB,QACpB,aACA,KACA,cACmB;AACnB,QAAM,OAAQ,IAA8C;AAC5D,MAAI,QAAQ;AAEZ,iBAAe,SAAS,GAA8B;AACpD,QAAI,KAAK,OAAO;AACd,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AACA,YAAQ;AAGR,QAAI,KAAK,YAAY,QAAQ;AAC3B,aAAO,MAAM,aAAa;AAAA,IAC5B;AAGA,UAAM,KAAK,YAAY,CAAC;AAExB,QAAI;AACJ,UAAM,OAAO,YAA+B;AAC1C,sBAAgB,MAAM,SAAS,IAAI,CAAC;AACpC,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,MAAM,GAAG,KAAK,IAAI;AAGjC,QAAI,kBAAkB,UAAU;AAC9B,aAAO,UAAU,QAAQ,IAAI;AAAA,IAC/B;AAEA,QAAI,kBAAkB,QAAW;AAC/B,aAAO;AAAA,IACT;AAEA,UAAM,IAAI,MAAM,yEAAiC;AAAA,EACnD;AAEA,SAAO,MAAM,SAAS,CAAC;AACzB;AAeA,eAAsB,cACpB,SACA,KACA,MACA,aACA,WACmB;AACnB,QAAM,OAAQ,IAA8C;AAa5D,QAAM,sBAAsB,MAAuB;AACjD,UAAM,YAAa,IAAmD;AACtE,QAAI,CAAC,UAAW,QAAO;AAEvB,QAAI,CAAC,UAAU,YAAY,CAAC,UAAU,UAAU,CAAC,UAAU,SAAS;AAClE,gBAAU,MAAM;AAAA,IAClB;AACA,WAAO,UAAU,UAAU,UAAU,IAAI;AAAA,EAC3C;AAGA,QAAM,sBAAsB,MAAY;AACtC,UAAM,YAAa,IAAmD;AACtE,QAAI,aAAa,CAAC,UAAU,UAAU,CAAC,UAAU,SAAS;AACxD,gBAAU,MAAM;AAAA,IAClB;AAAA,EACF;AAIA,QAAM,mBAAmB,YAA+B;AACtD,QAAI;AACF,YAAM,SAAS,MAAM,kBAAkB,SAAS,KAAK,MAAM,SAAS;AACpE,YAAM,cAAc,oBAAoB;AACxC,UAAI,YAAa,QAAO;AACxB,aAAO,WAAW,WAAW,QAAQ,GAAG,GAAG,IAAI;AAAA,IACjD,SAAS,KAAK;AAEZ,0BAAoB;AACpB,YAAM;AAAA,IACR;AAAA,EACF;AAGA,MAAI,CAAC,eAAe,YAAY,WAAW,GAAG;AAC5C,WAAO,iBAAiB;AAAA,EAC1B;AAGA,QAAM,eAAe;AAErB,SAAO,MAAM,QAAQ,aAAa,KAAK,YAAY;AACrD;;;ACrMA,OAAOE,YAAU;AACjB,OAAO,QAAQ;AACf,OAAOC,UAAQ;;;ACHf,OAAO,QAAQ;AACf,OAAOC,WAAU;AACjB,OAAOC,SAAQ;;;ACFR,IAAM,eAAe,CAAC,OAAO,QAAQ,OAAO,SAAS,UAAU,QAAQ,SAAS;AAIvF,IAAM,kBAAuC,IAAI,IAAI,YAAY;;;ACG1D,SAAS,cAAcC,QAAsB;AAClD,MAAI,CAACA,OAAM,QAAO;AAGlB,MAAI,SAASA,OAAK,QAAQ,OAAO,GAAG;AAGpC,WAAS,OAAO,QAAQ,QAAQ,GAAG;AAGnC,WAAS,OAAO,QAAQ,QAAQ,EAAE;AAGlC,MAAI,UAAU,CAAC,OAAO,WAAW,GAAG,GAAG;AACrC,aAAS,MAAM;AAAA,EACjB;AAEA,SAAO;AACT;;;ACjBO,SAAS,sBAAsB,SAAyB;AAC7D,QAAM,QAAQ,QAAQ,MAAM,YAAY;AACxC,MAAI,OAAO;AACT,WAAO,MAAM,MAAM,CAAC;AAAA,EACtB;AACA,SAAO;AACT;AASO,SAAS,kBAAkB,SAA2B;AAC3D,QAAM,SAAmB,CAAC;AAC1B,QAAM,WAAW,QAAQ,MAAM,GAAG;AAClC,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,WAAW,MAAM,GAAG;AAE9B,aAAO,KAAK,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC9B,WAAW,QAAQ,WAAW,GAAG,GAAG;AAClC,aAAO,KAAK,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC9B;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,kBAAkB,SAA0B;AAC1D,SAAO,iBAAiB,KAAK,OAAO;AACtC;AAOO,SAAS,aAAa,SAA0B;AACrD,SAAO,WAAW,KAAK,OAAO;AAChC;AAiBO,SAAS,kBAAkB,UAA0B;AAE1D,QAAM,gBAAgB,SAAS,WAAW,MAAM,IAAI,SAAS,MAAM,CAAC,IAAI;AAGxE,QAAM,iBAAiB,cAAc,YAAY,GAAG;AACpD,QAAM,UAAU,mBAAmB,KAAK,KAAK,cAAc,MAAM,GAAG,cAAc;AAElF,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,QACd,MAAM,GAAG,EACT,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,EAC9B,IAAI,qBAAqB;AAG5B,SAAO,cAAc,SAAS,KAAK,GAAG,CAAC;AACzC;;;AC1FA,OAAOC,SAAQ;AACf,SAAS,qBAAqB;AAQ9B,IAAI;AAuBJ,SAAS,wBAAoD;AAC3D,QAAM,KAAM,WAA0D;AACtE,MAAI,OAAO,IAAI,iBAAiB,WAAY,QAAO;AACnD,SAAO,GAAG,aAAa,KAAK,EAAE;AAChC;AAUA,IAAM,YAAY,oBAAI,IAAkE;AAGxF,IAAI,cAAc;AAsBlB,eAAsB,oBACpB,UACA,gBAAgB,OACkB;AAElC,QAAM,eAAe,sBAAsB;AAC3C,MAAI,cAAc;AAChB,QAAI,eAAe;AAIjB,UAAI,UAAU;AACd,UAAI;AACF,kBAAUC,IAAG,SAAS,QAAQ,EAAE;AAAA,MAClC,QAAQ;AAAA,MAER;AACA,YAAM,SAAS,UAAU,IAAI,QAAQ;AACrC,UAAI,UAAU,OAAO,YAAY,SAAS;AACxC,eAAO,OAAO;AAAA,MAChB;AACA,UAAIC,OAAM,cAAc,QAAQ,EAAE;AAClC,MAAAA,QAAO,MAAM,KAAK,IAAI,CAAC,IAAI,EAAE,WAAW;AACxC,YAAM,SAAU,MAAM,OAAOA;AAC7B,gBAAU,IAAI,UAAU,EAAE,SAAS,OAAO,CAAC;AAC3C,aAAO;AAAA,IACT;AACA,WAAQ,MAAM,aAAa,QAAQ;AAAA,EACrC;AAGA,MAAI,MAAM,cAAc,QAAQ,EAAE;AAClC,MAAI,WAAW,QAAW;AACxB,WAAO,MAAM,MAAM;AAAA,EACrB;AACA,SAAQ,MAAM,OAAO;AACvB;;;AC5FA,IAAM,kBAAkB,oBAAI,IAAoC;AAMhE,IAAM,WAAW,oBAAI,IAA6C;AAa3D,SAAS,qBAAqB,SAAqD;AACxF,SAAO,gBAAgB,IAAI,OAAO;AACpC;AAKO,SAAS,qBAAqB,SAAiB,QAAsC;AAC1F,kBAAgB,IAAI,SAAS,MAAM;AACrC;AAgBA,eAAsB,oBAAoB,UAAmD;AAC3F,MAAI;AACJ,MAAI;AACF,aAAU,MAAM,oBAAoB,QAAQ;AAAA,EAC9C,SAAS,KAAK;AAEZ,YAAQ;AAAA,MACN,2CAA2C,QAAQ;AAAA,MACnD,eAAe,QAAS,IAAI,SAAS,IAAI,UAAW;AAAA,IACtD;AACA,WAAO,EAAE,aAAa,CAAC,GAAG,WAAW,CAAC,EAAE;AAAA,EAC1C;AAGA,QAAM,cAAe,OAAO,WAAW,OAAO,eAAe,CAAC;AAC9D,MAAI,CAAC,MAAM,QAAQ,WAAW,GAAG;AAC/B,YAAQ,KAAK,kFAAqC,QAAQ,EAAE;AAC5D,WAAO,EAAE,aAAa,CAAC,GAAG,WAAW,CAAC,EAAE;AAAA,EAC1C;AAEA,QAAM,mBAAmB,YAAY,OAAO,CAAC,MAAe;AAC1D,QAAI,OAAO,MAAM,YAAY;AAC3B,cAAQ,KAAK,mHAA8B,OAAO,CAAC,EAAE;AACrD,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,CAAC;AAGD,QAAM,YAAa,OAAO,aAAa,CAAC;AACxC,MAAI,OAAO,cAAc,YAAY,cAAc,MAAM;AACvD,YAAQ,KAAK,6EAAgC,QAAQ,EAAE;AACvD,WAAO,EAAE,aAAa,kBAAkB,WAAW,CAAC,EAAE;AAAA,EACxD;AAGA,QAAM,iBAA8B,CAAC;AACrC,aAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,SAAS,GAAG;AACxD,QAAI,OAAO,aAAa,YAAY;AAClC,cAAQ,KAAK,8BAAe,IAAI,mDAAW;AAC3C;AAAA,IACF;AACA,mBAAe,IAAI,IAAI;AAAA,EACzB;AAEA,SAAO,EAAE,aAAa,kBAAkB,WAAW,eAAe;AACpE;AAcA,eAAsB,sBACpB,iBAC6C;AAC7C,MAAI,gBAAgB,WAAW,EAAG,QAAO;AAEzC,QAAM,oBAAuC,CAAC;AAC9C,QAAM,kBAA+B,CAAC;AAEtC,aAAW,aAAa,iBAAiB;AACvC,QAAI,SAA6C,qBAAqB,SAAS;AAC/E,QAAI,WAAW,QAAW;AAExB,UAAI,UAAU,SAAS,IAAI,SAAS;AACpC,UAAI,CAAC,SAAS;AACZ,kBAAU,oBAAoB,SAAS,EAAE,KAAK,CAAC,WAAW;AACxD,+BAAqB,WAAW,MAAM;AACtC,iBAAO;AAAA,QACT,CAAC;AACD,iBAAS,IAAI,WAAW,OAAO;AAAA,MACjC;AACA,eAAS,MAAM;AAAA,IACjB;AACA,sBAAkB,KAAK,GAAG,OAAO,WAAW;AAC5C,eAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,OAAO,SAAS,GAAG;AAC/D,sBAAgB,IAAI,IAAI;AAAA,IAC1B;AAAA,EACF;AAEA,MAAI,kBAAkB,WAAW,KAAK,OAAO,KAAK,eAAe,EAAE,WAAW,GAAG;AAC/E,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,aAAa,mBAAmB,WAAW,gBAAgB;AACtE;;;ALjIA,IAAM,uBAAuB,IAAI;AAAA,EAC/B,OAAO;AAAA,EACP;AACF;AAKA,SAAS,yBAAyB,QAA6B;AAC7D,QAAM,QAAQ,oBAAI,IAAY;AAC9B,MAAI;AACJ,uBAAqB,YAAY;AACjC,UAAQ,QAAQ,qBAAqB,KAAK,MAAM,OAAO,MAAM;AAC3D,UAAM,IAAI,MAAM,CAAC,CAAE;AAAA,EACrB;AACA,SAAO;AACT;AAmBA,SAAS,uBAAuB,eAAuB,SAAiB,MAAyB;AAC/F,QAAM,WAAWC,MAAK,QAAQ,aAAa;AAC3C,QAAM,eAAeA,MAAK,QAAQ,OAAO;AAEzC,QAAM,QAAkB,CAAC;AACzB,MAAI,aAAaA,MAAK,QAAQ,SAAS,QAAQ;AAC/C,SAAO,MAAM;AACX,QAAI,MAAM;AAER,YAAM,WAAWA,MAAK,KAAK,YAAY,gBAAgB;AACvD,YAAM,WAAWA,MAAK,KAAK,YAAY,gBAAgB;AACvD,YAAM,YAAYA,MAAK,QAAQ,SAAS,QAAQ;AAChD,YAAM,YAAYA,MAAK,QAAQ,SAAS,QAAQ;AAChD,YAAM,YAAYC,IAAG,WAAW,SAAS,IACrC,YACAA,IAAG,WAAW,SAAS,IACrB,YACA;AACN,UAAI,WAAW;AAEb,cAAM,YAAYD,MAAK,SAAS,SAAS,SAAS;AAClD,cAAM,cAAcA,MAAK,QAAQ,SAAS,eAAe,WAAW,IAAI,CAAC;AACzE,cAAM,KAAK,WAAW;AAAA,MACxB;AAAA,IACF,OAAO;AAEL,iBAAW,OAAO,CAAC,OAAO,KAAK,GAAG;AAChC,cAAM,SAASA,MAAK,KAAK,YAAY,cAAc,GAAG,EAAE;AACxD,cAAM,YAAYA,MAAK,QAAQ,SAAS,MAAM;AAC9C,YAAIC,IAAG,WAAW,SAAS,GAAG;AAC5B,gBAAM,KAAK,SAAS;AACpB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,QAAI,eAAe,aAAc;AACjC,UAAM,YAAYD,MAAK,QAAQ,UAAU;AACzC,QAAI,cAAc,WAAY;AAC9B,iBAAa;AAAA,EACf;AAGA,QAAM,QAAQ;AACd,SAAO;AACT;AAwCA,eAAsB,WACpB,SACA,UACA,MAC+D;AAC/D,QAAM,QAAQ,MAAM,GAAG,UAAU;AAAA,IAC/B,KAAK;AAAA,IACL,WAAW;AAAA,IACX,UAAU;AAAA,EACZ,CAAC;AAED,QAAM,SAAwB,CAAC;AAC/B,QAAM,WAA4B,CAAC;AAEnC,aAAW,QAAQ,OAAO;AACxB,UAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;AAC9C,UAAM,WAAW,eAAe,MAAM,GAAG,EAAE,IAAI;AAI/C,QAAI,aAAa,gBAAgB,aAAa,cAAc;AAC1D,YAAM,UAAUA,MAAK,QAAQ,SAAS,cAAc;AACpD,YAAM,UAAU,kBAAkB,cAAc;AAChD,YAAM,aAAa,kBAAkB,OAAO;AAC5C,YAAM,YAAY,WAAW,SAAS;AACtC,YAAM,aAAa,eAAe,MAAM,GAAG,EAAE,KAAK,iBAAiB;AAGnE,UAAI;AACJ,UAAI;AACJ,UAAI,MAAM;AAER,0BAAkB,uBAAuB,gBAAgB,SAAS,IAAI;AAAA,MACxE,OAAO;AAEL,cAAM,UAAU,uBAAuB,gBAAgB,OAAO;AAC9D,2BAAmB,MAAM,sBAAsB,OAAO;AAAA,MACxD;AAGA,YAAM,SAAS,MAAMC,IAAG,SAAS,SAAS,SAAS,MAAM,EAAE,MAAM,MAAM,EAAE;AACzE,YAAM,cAAc,yBAAyB,MAAM;AACnD,YAAM,UAAU,aAAa,OAAO,CAAC,MAAM,YAAY,IAAI,CAAC,CAAC;AAE7D,iBAAW,UAAU,SAAS;AAC5B,eAAO,KAAK;AAAA,UACV;AAAA,UACA;AAAA,UACA,UAAU;AAAA,UACV;AAAA,UACA;AAAA,UACA,YAAY,cAAc;AAAA,UAC1B;AAAA,UACA,aAAa,kBAAkB;AAAA,UAC/B,WAAW,kBAAkB;AAAA,QAC/B,CAAC;AAAA,MACH;AAGA,UAAI,YAAY,IAAI,IAAI,GAAG;AACzB,iBAAS,KAAK;AAAA,UACZ;AAAA,UACA,UAAU;AAAA,UACV;AAAA,UACA;AAAA,UACA,YAAY,cAAc;AAAA,UAC1B;AAAA,UACA,aAAa,kBAAkB;AAAA,UAC/B,WAAW,kBAAkB;AAAA,QAC/B,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,SAAS;AAC5B;;;AMjNO,SAAS,WAAW,QAAsC;AAC/D,SAAO,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM;AAEhC,QAAI,EAAE,cAAc,EAAE,WAAW;AAC/B,aAAO,EAAE,YAAY,IAAI;AAAA,IAC3B;AAGA,QAAI,EAAE,eAAe,EAAE,YAAY;AACjC,aAAO,EAAE,aAAa,IAAI;AAAA,IAC5B;AAGA,UAAM,YAAY,EAAE,QAAQ,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE;AACvD,UAAM,YAAY,EAAE,QAAQ,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE;AACvD,QAAI,cAAc,WAAW;AAC3B,aAAO,YAAY;AAAA,IACrB;AAGA,WAAO,EAAE,QAAQ,cAAc,EAAE,OAAO;AAAA,EAC1C,CAAC;AACH;;;AC/BA,OAAOC,WAAU;;;ACAjB,OAAOC,SAAQ;AACf,OAAOC,SAAQ;AACf,OAAOC,WAAU;AAQjB,IAAM,eAAe,oBAAI,IAAwB;AAgBjD,IAAM,gBAAgB,oBAAI,IAA4B;AAsBtD,SAAS,aAAa,UAAiC;AACrD,MAAI,MAAMC,MAAK,QAAQ,QAAQ;AAC/B,QAAM,OAAOA,MAAK,MAAM,GAAG,EAAE;AAC7B,SAAO,MAAM;AACX,UAAM,YAAYA,MAAK,KAAK,KAAK,eAAe;AAChD,QAAIC,IAAG,WAAW,SAAS,GAAG;AAC5B,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,KAAM,QAAO;AACzB,UAAM,SAASD,MAAK,QAAQ,GAAG;AAC/B,QAAI,WAAW,IAAK,QAAO;AAC3B,UAAM;AAAA,EACR;AACF;AAgBA,SAAS,cAAc,cAAsC;AAC3D,QAAM,SAAS,cAAc,IAAI,YAAY;AAC7C,MAAI,OAAQ,QAAO;AAEnB,QAAM,SAAyB,EAAE,WAAW,CAAC,EAAE;AAE/C,MAAI;AACF,UAAM,aAAaE,IAAG,eAAe,cAAc,CAAC,MAAMD,IAAG,aAAa,GAAG,OAAO,CAAC;AACrF,QAAI,WAAW,OAAO;AACpB,oBAAc,IAAI,cAAc,MAAM;AACtC,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,WAAW,UAAU,CAAC;AACrC,UAAM,WAAWD,MAAK,QAAQ,YAAY;AAI1C,UAAM,SAASE,IAAG;AAAA,MAChB;AAAA,MACAA,IAAG;AAAA,MACH;AAAA;AAAA,MACsB;AAAA,MACtB;AAAA,IACF;AAEA,WAAO,YAAY,OAAO;AAC1B,QAAI,OAAO,QAAQ,WAAW,QAAW;AACvC,aAAO,SAAS,OAAO,QAAQ;AAAA,IACjC;AACA,QAAI,OAAO,QAAQ,qBAAqB,QAAW;AACjD,aAAO,mBAAmB,OAAO,QAAQ;AAAA,IAC3C;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,gBAAc,IAAI,cAAc,MAAM;AACtC,SAAO;AACT;AAoBO,SAAS,cAAc,UAA8B;AAC1D,QAAM,SAAS,aAAa,IAAI,QAAQ;AACxC,MAAI,QAAQ;AACV,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,aAAa,CAAC,QAAQ,GAAG,aAAa,QAAQ,CAAC;AAE/D,eAAa,IAAI,UAAU,OAAO;AAClC,SAAO;AACT;AAuBO,SAAS,eAAe,WAA8C;AAC3E,QAAM,SAAS,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;AACrC,QAAM,SAAS,oBAAI,IAAwB;AAG3C,QAAM,SAAS,oBAAI,IAAuD;AAC1E,QAAM,kBAA4B,CAAC;AACnC,aAAW,YAAY,QAAQ;AAC7B,UAAM,eAAe,aAAa,QAAQ;AAC1C,QAAI,CAAC,cAAc;AACjB,sBAAgB,KAAK,QAAQ;AAC7B;AAAA,IACF;AACA,UAAM,QAAQ,OAAO,IAAI,YAAY;AACrC,QAAI,OAAO;AACT,YAAM,MAAM,KAAK,QAAQ;AAAA,IAC3B,OAAO;AACL,aAAO,IAAI,cAAc,EAAE,cAAc,OAAO,CAAC,QAAQ,EAAE,CAAC;AAAA,IAC9D;AAAA,EACF;AAEA,aAAW,EAAE,cAAc,MAAM,KAAK,OAAO,OAAO,GAAG;AAOrD,UAAM,WAAW,WAAW,YAAY;AACxC,QAAI,UAAU,aAAa,IAAI,QAAQ;AACvC,UAAM,YACJ,YAAY,UAAa,MAAM,MAAM,CAAC,MAAM,QAAS,cAAc,CAAC,MAAM,MAAS;AACrF,QAAI,CAAC,WAAW,CAAC,WAAW;AAC1B,gBAAU,aAAa,OAAO,YAAY;AAC1C,mBAAa,IAAI,UAAU,OAAO;AAAA,IACpC;AACA,eAAW,YAAY,OAAO;AAC5B,aAAO,IAAI,UAAU,OAAO;AAAA,IAC9B;AAAA,EACF;AAGA,aAAW,YAAY,iBAAiB;AACtC,WAAO,IAAI,UAAU,cAAc,QAAQ,CAAC;AAAA,EAC9C;AAEA,SAAO;AACT;AASA,SAAS,aAAa,YAAsB,cAAyC;AAEnF,QAAM,UAA8B;AAAA,IAClC,QAAQ;AAAA,IACR,QAAQA,IAAG,aAAa;AAAA,IACxB,QAAQA,IAAG,WAAW;AAAA,IACtB,kBAAkBA,IAAG,qBAAqB;AAAA,IAC1C,cAAc;AAAA,IACd,QAAQ;AAAA,EACV;AAGA,QAAM,YAAY,CAAC,GAAG,UAAU;AAEhC,MAAI,cAAc;AAChB,UAAM,YAAY,cAAc,YAAY;AAC5C,QAAI,UAAU,WAAW,QAAW;AAClC,cAAQ,SAAS,UAAU;AAAA,IAC7B;AACA,QAAI,UAAU,qBAAqB,QAAW;AAC5C,cAAQ,mBAAmB,UAAU;AAAA,IACvC;AACA,QAAI,UAAU,UAAU,SAAS,GAAG;AAElC,iBAAW,YAAY,UAAU,WAAW;AAC1C,YAAI,CAAC,UAAU,SAAS,QAAQ,GAAG;AACjC,oBAAU,KAAK,QAAQ;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAOA,IAAG,cAAc,WAAW,OAAO;AAC5C;;;AClQA,OAAOC,SAAQ;;;ACAf,OAAOC,SAAQ;AAcf,IAAI,iBAAoC;AAOjC,SAAS,kBAAkB,SAAkC;AAClE,mBAAiB;AACnB;AAeO,IAAM,wBAAN,MAAM,+BAA8B,MAAM;AAAA,EAC/C,YACkB,UACA,QAChB,SACgB,UAChB;AACA;AAAA,MACE,yCAAW,QAAQ,MAAM,MAAM,MAC5B,WAAW,KAAK,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,SAAS,MAAM,MAAM;AAAA,MAC1E;AAAA,IACF;AATgB;AACA;AAEA;AAOhB,SAAK,OAAO;AAAA,EACd;AAAA,EAXkB;AAAA,EACA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBlB,OAAO,GACL,MACA,UACA,QACA,YACuB;AACvB,UAAM,KAAK,cAAc,KAAK,cAAc;AAC5C,QAAI,CAAC,IAAI;AACP,aAAO,IAAI,uBAAsB,UAAU,MAAM;AAAA,IACnD;AACA,UAAM,EAAE,MAAM,UAAU,IAAI,GAAG,8BAA8B,KAAK,SAAS,EAAE,CAAC;AAC9E,WAAO,IAAI,uBAAsB,UAAU,QAAQ,QAAW;AAAA,MAC5D,MAAM,GAAG;AAAA,MACT,MAAM,OAAO;AAAA,MACb,QAAQ,YAAY;AAAA,IACtB,CAAC;AAAA,EACH;AACF;AAuGO,SAAS,gBACd,UACA,SACA,UAAuB,oBAAI,IAAI,GAC/B,WAAqC,oBAAI,IAAI,GAChC;AACb,QAAM,OAAO,SAAS;AAGtB,UAAQ,MAAM;AAAA,IACZ,KAAKA,IAAG,WAAW;AACjB,aAAO,EAAE,MAAM,SAAS;AAAA,IAC1B,KAAKA,IAAG,WAAW;AACjB,aAAO,EAAE,MAAM,SAAS;AAAA,IAC1B,KAAKA,IAAG,WAAW;AACjB,aAAO,EAAE,MAAM,UAAU;AAAA,IAC3B,KAAKA,IAAG,WAAW;AAEjB,YAAM,IAAI;AAAA,QACR,SAAS,QAAQ;AAAA,QACjB;AAAA,MACF;AAAA,IACF,KAAKA,IAAG,WAAW;AAEjB,YAAM,IAAI,sBAAsB,SAAS,QAAQ,GAAG,wDAA0B;AAAA,IAChF,KAAKA,IAAG,WAAW;AACjB,aAAO,EAAE,MAAM,OAAO;AAAA,IACxB,KAAKA,IAAG,WAAW;AACjB,aAAO,EAAE,MAAM,YAAY;AAAA,IAC7B,KAAKA,IAAG,WAAW;AAEjB,aAAO,EAAE,MAAM,MAAM;AAAA,IACvB,KAAKA,IAAG,WAAW;AACjB,YAAM,IAAI,sBAAsB,SAAS,QAAQ,GAAG,uFAA2B;AAAA,IACjF,KAAKA,IAAG,WAAW;AACjB,YAAM,IAAI,sBAAsB,SAAS,QAAQ,GAAG,uDAAe;AAAA,IACrE,KAAKA,IAAG,WAAW;AACjB,YAAM,IAAI,sBAAsB,SAAS,QAAQ,GAAG,wDAAgB;AAAA,IACtE,KAAKA,IAAG,WAAW;AACjB,YAAM,IAAI;AAAA,QACR,SAAS,QAAQ;AAAA,QACjB;AAAA,MACF;AAAA,EACJ;AAGA,MAAIA,IAAG,kBAAkB,QAAQ,GAAG;AAClC,UAAM,UAAU,SAAS;AACzB,QAAIA,IAAG,gBAAgB,OAAO,GAAG;AAC/B,aAAO,EAAE,MAAM,WAAW,OAAO,QAAQ,KAAK;AAAA,IAChD;AACA,QAAIA,IAAG,iBAAiB,OAAO,GAAG;AAChC,aAAO,EAAE,MAAM,WAAW,OAAO,OAAO,QAAQ,IAAI,EAAE;AAAA,IACxD;AACA,QAAI,QAAQ,SAASA,IAAG,WAAW,aAAa;AAC9C,aAAO,EAAE,MAAM,WAAW,OAAO,KAAK;AAAA,IACxC;AACA,QAAI,QAAQ,SAASA,IAAG,WAAW,cAAc;AAC/C,aAAO,EAAE,MAAM,WAAW,OAAO,MAAM;AAAA,IACzC;AACA,QAAI,QAAQ,SAASA,IAAG,WAAW,aAAa;AAC9C,aAAO,EAAE,MAAM,OAAO;AAAA,IACxB;AACA,UAAM,IAAI,sBAAsB,SAAS,QAAQ,GAAG,wDAAW;AAAA,EACjE;AAGA,MAAIA,IAAG,gBAAgB,QAAQ,GAAG;AAChC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS,gBAAgB,SAAS,aAAa,SAAS,SAAS,QAAQ;AAAA,IAC3E;AAAA,EACF;AAGA,MAAIA,IAAG,gBAAgB,QAAQ,GAAG;AAChC,UAAM,WAA2B,SAAS,SAAS,IAAI,CAAC,MAAM;AAE5D,UAAIA,IAAG,eAAe,CAAC,GAAG;AACxB,cAAM,QAAQ,gBAAgB,EAAE,MAAM,SAAS,SAAS,QAAQ;AAEhE,YAAI,MAAM,SAAS,SAAS;AAC1B,iBAAO,EAAE,MAAM,MAAM,SAAS,UAAU,OAAO,MAAM,KAAK;AAAA,QAC5D;AAEA,eAAO,EAAE,MAAM,OAAO,UAAU,OAAO,MAAM,KAAK;AAAA,MACpD;AAEA,UAAIA,IAAG,mBAAmB,CAAC,GAAG;AAC5B,eAAO;AAAA,UACL,MAAM,gBAAgB,EAAE,MAAM,SAAS,SAAS,QAAQ;AAAA,UACxD,UAAU,CAAC,CAAC,EAAE;AAAA,UACd,MAAM;AAAA,QACR;AAAA,MACF;AAEA,UAAIA,IAAG,mBAAmB,CAAC,GAAG;AAC5B,eAAO;AAAA,UACL,MAAM,gBAAgB,EAAE,MAAM,SAAS,SAAS,QAAQ;AAAA,UACxD,UAAU;AAAA,UACV,MAAM;AAAA,QACR;AAAA,MACF;AAEA,aAAO;AAAA,QACL,MAAM,gBAAgB,GAAG,SAAS,SAAS,QAAQ;AAAA,QACnD,UAAU;AAAA,QACV,MAAM;AAAA,MACR;AAAA,IACF,CAAC;AACD,WAAO,EAAE,MAAM,SAAS,SAAS;AAAA,EACnC;AAGA,MAAIA,IAAG,gBAAgB,QAAQ,GAAG;AAChC,UAAM,UAAU,SAAS,MAAM,IAAI,CAAC,MAAM,gBAAgB,GAAG,SAAS,SAAS,QAAQ,CAAC;AACxF,WAAO,EAAE,MAAM,SAAS,QAAQ;AAAA,EAClC;AAGA,MAAIA,IAAG,uBAAuB,QAAQ,GAAG;AACvC,UAAM,UAAU,oBAAI,IAA0B;AAC9C,eAAW,KAAK,SAAS,OAAO;AAC9B,YAAM,WAAW,gBAAgB,GAAG,SAAS,SAAS,QAAQ;AAC9D,UAAI,SAAS,SAAS,UAAU;AAG9B,cAAM,IAAI;AAAA,UACR,SAAS,QAAQ;AAAA,UACjB,uEAAqB,SAAS,IAAI;AAAA,QACpC;AAAA,MACF;AACA,iBAAW,QAAQ,SAAS,YAAY;AACtC,cAAM,WAAW,QAAQ,IAAI,KAAK,IAAI;AACtC,YAAI,CAAC,UAAU;AACb,kBAAQ,IAAI,KAAK,MAAM,IAAI;AAC3B;AAAA,QACF;AAGA,YACE,KAAK,UAAU,SAAS,IAAI,MAAM,KAAK,UAAU,KAAK,IAAI,KAC1D,SAAS,aAAa,KAAK,UAC3B;AACA,gBAAM,sBAAsB;AAAA,YAC1B;AAAA,YACA,SAAS,QAAQ;AAAA,YACjB,uEAAgB,KAAK,IAAI;AAAA,UAC3B;AAAA,QACF;AACA,YAAI,CAAC,SAAS,aAAa,UAAU,KAAK,aAAa,QAAQ;AAC7D,kBAAQ,IAAI,KAAK,MAAM,IAAI;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AACA,WAAO,EAAE,MAAM,UAAU,YAAY,CAAC,GAAG,QAAQ,OAAO,CAAC,EAAE;AAAA,EAC7D;AAGA,MAAIA,IAAG,kBAAkB,QAAQ,GAAG;AAClC,WAAO,mBAAmB,UAAU,SAAS,SAAS,QAAQ;AAAA,EAChE;AAGA,MAAIA,IAAG,mBAAmB,QAAQ,KAAK,SAAS,aAAaA,IAAG,WAAW,cAAc;AACvF,WAAO,aAAa,UAAU,OAAO;AAAA,EACvC;AAIA,MAAIA,IAAG,mBAAmB,QAAQ,KAAK,SAAS,aAAaA,IAAG,WAAW,iBAAiB;AAC1F,WAAO,gBAAgB,SAAS,MAAM,SAAS,SAAS,QAAQ;AAAA,EAClE;AAGA,MAAIA,IAAG,oBAAoB,QAAQ,GAAG;AACpC,WAAO,qBAAqB,UAAU,SAAS,SAAS,QAAQ;AAAA,EAClE;AAKA,MAAIA,IAAG,8BAA8B,QAAQ,GAAG;AAC9C,WAAO;AAAA,MACL;AAAA,QACE,SAAS,MAAM,SAAS,QAAQ;AAAA,QAChC,UAAU,SAAS;AAAA,QACnB,eAAe,SAAS;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,QAAM,sBAAsB,GAAG,UAAU,SAAS,QAAQ,GAAG,kDAAU;AACzE;AAKA,SAAS,mBACP,UACA,SACA,UAAuB,oBAAI,IAAI,GAC/B,WAAqC,oBAAI,IAAI,GAChC;AACb,QAAM,aAA6B,CAAC;AAEpC,MAAI;AAEJ,aAAW,UAAU,SAAS,SAAS;AAErC,QACEA,IAAG,kBAAkB,MAAM,KAC3BA,IAAG,yBAAyB,MAAM,KAClCA,IAAG,yBAAyB,MAAM,GAClC;AACA,YAAM,sBAAsB;AAAA,QAC1B;AAAA,QACA,OAAO,QAAQ;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAEA,QAAIA,IAAG,oBAAoB,MAAM,KAAK,OAAO,MAAM;AACjD,YAAM,OAAO,OAAO,KAAK,QAAQ;AACjC,YAAM,WAAW,CAAC,CAAC,OAAO;AAC1B,YAAM,OAAO,OAAO,OAChB,gBAAgB,OAAO,MAAM,SAAS,SAAS,QAAQ,IACvD,EAAE,MAAM,MAAe;AAC3B,YAAM,cAAc,4BAA4B,QAAQ,IAAI;AAC5D,0BAAoB,aAAa,MAAM,IAAI;AAC3C,iBAAW;AAAA,QACT,YAAY,SAAS,IAAI,EAAE,MAAM,MAAM,UAAU,YAAY,IAAI,EAAE,MAAM,MAAM,SAAS;AAAA,MAC1F;AAAA,IACF;AAEA,QAAIA,IAAG,4BAA4B,MAAM,GAAG;AAC1C,iBAAW,OAAO,OACd,gBAAgB,OAAO,MAAM,SAAS,SAAS,QAAQ,IACvD,EAAE,MAAM,MAAe;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO,aAAa,SAChB,EAAE,MAAM,UAAU,YAAY,SAAS,IACvC,EAAE,MAAM,UAAU,WAAW;AACnC;AAQA,SAAS,mBAAmB,MAAoC;AAC9D,MAAI,KAAK,SAAS,aAAa,OAAO,KAAK,UAAU,UAAU;AAC7D,WAAO,CAAC,KAAK,KAAK;AAAA,EACpB;AACA,MAAI,KAAK,SAAS,SAAS;AACzB,UAAM,OAAiB,CAAC;AACxB,eAAW,UAAU,KAAK,SAAS;AACjC,UAAI,OAAO,SAAS,aAAa,OAAO,OAAO,UAAU,UAAU;AACjE,aAAK,KAAK,OAAO,KAAK;AAAA,MACxB,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAWA,SAAS,uBAAuB,UAAuB,SAA2C;AAChG,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,OAAO,QAAQ,oBAAoB,QAAQ;AAEjD,MAAI,KAAK,QAAQ,GAAG;AAClB,UAAM,OAAiB,CAAC;AACxB,eAAW,UAAU,KAAK,OAAO;AAC/B,UAAI,OAAO,gBAAgB,GAAG;AAC5B,aAAK,KAAK,OAAO,KAAK;AAAA,MACxB,WAAW,OAAO,gBAAgB,GAAG;AACnC,aAAK,KAAK,OAAO,OAAO,KAAK,CAAC;AAAA,MAChC,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,MAAI,KAAK,gBAAgB,GAAG;AAC1B,WAAO,CAAC,KAAK,KAAK;AAAA,EACpB;AACA,MAAI,KAAK,gBAAgB,GAAG;AAC1B,WAAO,CAAC,OAAO,KAAK,KAAK,CAAC;AAAA,EAC5B;AAEA,SAAO;AACT;AAOA,SAAS,aAAa,UAA+B,SAAuC;AAC1F,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,sBAAsB,SAAS,QAAQ,GAAG,uDAAyB;AAAA,EAC/E;AACA,QAAM,OAAO,QAAQ,oBAAoB,QAAQ;AAEjD,MAAI,KAAK,QAAQ,GAAG;AAClB,UAAM,UAAyB,CAAC;AAChC,eAAW,UAAU,KAAK,OAAO;AAC/B,UAAI,OAAO,gBAAgB,GAAG;AAC5B,gBAAQ,KAAK,EAAE,MAAM,WAAW,OAAO,OAAO,MAAM,CAAC;AAAA,MACvD,WAAW,OAAO,gBAAgB,GAAG;AACnC,gBAAQ,KAAK,EAAE,MAAM,WAAW,OAAO,OAAO,MAAM,CAAC;AAAA,MACvD,OAAO;AACL,cAAM,IAAI,sBAAsB,SAAS,QAAQ,GAAG,4EAAqB;AAAA,MAC3E;AAAA,IACF;AACA,WAAO,EAAE,MAAM,SAAS,QAAQ;AAAA,EAClC;AAEA,MAAI,KAAK,gBAAgB,GAAG;AAC1B,WAAO,EAAE,MAAM,WAAW,OAAO,KAAK,MAAM;AAAA,EAC9C;AACA,MAAI,KAAK,gBAAgB,GAAG;AAC1B,WAAO,EAAE,MAAM,WAAW,OAAO,KAAK,MAAM;AAAA,EAC9C;AAEA,QAAM,IAAI,sBAAsB,SAAS,QAAQ,GAAG,wFAAuB;AAC7E;AAoBA,SAAS,qBACP,UACA,SACA,UAAuB,oBAAI,IAAI,GAC/B,WAAqC,oBAAI,IAAI,GAChC;AACb,QAAM,WAAW,SAAS,SAAS,QAAQ;AAG3C,QAAM,QAAQ,SAAS,IAAI,QAAQ;AACnC,MAAI,OAAO;AACT,WAAO;AAAA,EACT;AAGA,MAAI,aAAa,QAAQ;AACvB,WAAO,EAAE,MAAM,OAAO;AAAA,EACxB;AAGA,OACG,aAAa,WAAW,aAAa,oBACtC,SAAS,eAAe,WAAW,GACnC;AACA,UAAM,CAAC,GAAG,IAAI,SAAS;AACvB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS,gBAAgB,KAAM,SAAS,SAAS,QAAQ;AAAA,IAC3D;AAAA,EACF;AAGA,MAAI,aAAa,YAAY,SAAS,eAAe,WAAW,GAAG;AACjE,UAAM,CAAC,QAAQ,QAAQ,IAAI,SAAS;AACpC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,KAAK,gBAAgB,QAAS,SAAS,SAAS,QAAQ;AAAA,MACxD,OAAO,gBAAgB,UAAW,SAAS,SAAS,QAAQ;AAAA,IAC9D;AAAA,EACF;AAGA,OACG,aAAa,aAAa,aAAa,cAAc,aAAa,eACnE,SAAS,eAAe,WAAW,GACnC;AACA,UAAM,QAAQ,gBAAgB,SAAS,cAAc,CAAC,GAAI,SAAS,SAAS,QAAQ;AACpF,QAAI,MAAM,SAAS,YAAY,aAAa,WAAW;AAErD,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,MAAM,WAAW,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,UAAU,KAAK,EAAE;AAAA,MACpE;AAAA,IACF;AACA,QAAI,MAAM,SAAS,YAAY,aAAa,YAAY;AAEtD,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,MAAM,WAAW,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,UAAU,MAAM,EAAE;AAAA,MACrE;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAGA,OAAK,aAAa,UAAU,aAAa,WAAW,SAAS,eAAe,WAAW,GAAG;AACxF,UAAM,YAAY,gBAAgB,SAAS,cAAc,CAAC,GAAI,SAAS,SAAS,QAAQ;AACxF,QAAI,UAAU,SAAS,UAAU;AAC/B,YAAM,IAAI;AAAA,QACR,SAAS,QAAQ;AAAA,QACjB,GAAG,QAAQ,gFAAoB,UAAU,IAAI;AAAA,MAC/C;AAAA,IACF;AAGA,UAAM,cAAc,SAAS,cAAc,CAAC;AAC5C,QAAI,OAAO,mBAAmB,gBAAgB,aAAa,SAAS,SAAS,QAAQ,CAAC;AACtF,QAAI,SAAS,MAAM;AACjB,aAAO,uBAAuB,aAAa,OAAO;AAAA,IACpD;AACA,QAAI,SAAS,MAAM;AACjB,YAAM,IAAI,sBAAsB,SAAS,QAAQ,GAAG,GAAG,QAAQ,wEAAiB;AAAA,IAClF;AAEA,UAAM,SAAS,IAAI,IAAI,IAAI;AAC3B,UAAM,aACJ,aAAa,SACT,UAAU,WAAW,OAAO,CAAC,MAAM,OAAO,IAAI,EAAE,IAAI,CAAC,IACrD,UAAU,WAAW,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,IAAI,CAAC;AAE5D,WAAO,EAAE,MAAM,UAAU,WAAW;AAAA,EACtC;AAGA,MAAI,aAAa,OAAO;AACtB,QAAI,CAAC,SAAS,iBAAiB,SAAS,cAAc,WAAW,GAAG;AAClE,YAAM,IAAI;AAAA,QACR,SAAS,QAAQ;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AACA,UAAM,CAAC,QAAQ,QAAQ,IAAI,SAAS;AACpC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,KAAK,gBAAgB,QAAS,SAAS,SAAS,QAAQ;AAAA,MACxD,OAAO,gBAAgB,UAAW,SAAS,SAAS,QAAQ;AAAA,IAC9D;AAAA,EACF;AAGA,MAAI,aAAa,OAAO;AACtB,QAAI,CAAC,SAAS,iBAAiB,SAAS,cAAc,WAAW,GAAG;AAClE,YAAM,IAAI;AAAA,QACR,SAAS,QAAQ;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AACA,UAAM,CAAC,MAAM,IAAI,SAAS;AAC1B,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS,gBAAgB,QAAS,SAAS,SAAS,QAAQ;AAAA,IAC9D;AAAA,EACF;AAGA,MAAI,aAAa,aAAa,aAAa,WAAW;AACpD,UAAM,IAAI;AAAA,MACR,SAAS,QAAQ;AAAA,MACjB,GAAG,QAAQ;AAAA,IACb;AAAA,EACF;AAGA,MAAI,aAAa,WAAW;AAC1B,UAAM,IAAI;AAAA,MACR,SAAS,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAGA,MAAI,aAAa,YAAY;AAC3B,UAAM,IAAI,sBAAsB,SAAS,QAAQ,GAAG,0DAA4B;AAAA,EAClF;AAGA,MAAI,QAAQ,IAAI,QAAQ,GAAG;AACzB,WAAO,EAAE,MAAM,OAAO,MAAM,SAAS;AAAA,EACvC;AACA,UAAQ,IAAI,QAAQ;AAGpB,MAAI,SAAS;AACX,UAAM,SACJA,IAAG,aAAa,SAAS,QAAQ,KAAKA,IAAG,gBAAgB,SAAS,QAAQ,IACtE,QAAQ,oBAAoB,SAAS,QAAQ,IAC7C;AAEN,QAAI,QAAQ;AACV,YAAM,cAAc,OAAO,eAAe,CAAC;AAC3C,UAAI,aAAa;AAEf,YAAIA,IAAG,uBAAuB,WAAW,GAAG;AAC1C,iBAAO;AAAA,YACL;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAEA,YAAIA,IAAG,uBAAuB,WAAW,GAAG;AAC1C,gBAAM,eAAe;AAAA,YACnB,YAAY;AAAA,YACZ,SAAS;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA,iBAAO,gBAAgB,YAAY,MAAM,SAAS,SAAS,YAAY;AAAA,QACzE;AAEA,YAAIA,IAAG,kBAAkB,WAAW,GAAG;AACrC,iBAAO,uBAAuB,WAAW;AAAA,QAC3C;AAIA,YAAIA,IAAG,kBAAkB,WAAW,KAAKA,IAAG,eAAe,WAAW,GAAG;AACvE,gBAAM,WAAW;AAAA,YACf;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,SAAS;AAAA,UACX;AACA,cAAI,SAAU,QAAO;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,IAAI,sBAAsB,SAAS,QAAQ,GAAG,2DAAc,QAAQ,GAAG;AAC/E;AAUA,SAAS,mBACP,gBACA,eACA,eACA,SACA,SACA,WAC0B;AAC1B,MAAI,CAAC,kBAAkB,eAAe,WAAW,EAAG,QAAO;AAE3D,QAAM,WAAW,IAAI,IAAI,aAAa;AACtC,WAAS,IAAI,GAAG,IAAI,eAAe,QAAQ,KAAK;AAC9C,UAAM,QAAQ,eAAe,CAAC;AAC9B,QAAI,CAAC,MAAO;AACZ,UAAM,MAAM,gBAAgB,CAAC;AAC7B,QAAI,KAAK;AACP,eAAS,IAAI,MAAM,KAAK,MAAM,gBAAgB,KAAK,SAAS,SAAS,aAAa,CAAC;AAAA,IACrF,WAAW,MAAM,SAAS;AACxB,eAAS,IAAI,MAAM,KAAK,MAAM,gBAAgB,MAAM,SAAS,SAAS,SAAS,QAAQ,CAAC;AAAA,IAC1F,OAAO;AACL,YAAM,IAAI;AAAA,QACR,UAAU,QAAQ;AAAA,QAClB,6BAAS,MAAM,KAAK,IAAI;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAoBA,SAAS,mBACP,UACA,QACA,SACA,SACA,WAAqC,oBAAI,IAAI,GAC7C,eACoB;AACpB,QAAM,WAAW,SAAS,SAAS,QAAQ;AAG3C,MAAI;AACF,UAAM,UAAU,QAAQ,iBAAiB,MAAM;AAC/C,QAAI,WAAW,QAAQ,gBAAgB,QAAQ,aAAa,SAAS,GAAG;AACtE,YAAM,OAAO,QAAQ,aAAa,CAAC;AACnC,UAAIA,IAAG,uBAAuB,IAAI,GAAG;AACnC,eAAO,4BAA4B,MAAM,SAAS,SAAS,UAAU,aAAa;AAAA,MACpF;AACA,UAAIA,IAAG,uBAAuB,IAAI,GAAG;AACnC,cAAM,eAAe;AAAA,UACnB,KAAK;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,eAAO,gBAAgB,KAAK,MAAM,SAAS,SAAS,YAAY;AAAA,MAClE;AACA,UAAIA,IAAG,kBAAkB,IAAI,GAAG;AAC9B,eAAO,uBAAuB,IAAI;AAAA,MACpC;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAKA,QAAM,UAAU;AAChB,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,SAAS,QAAQ,eAAe;AACtC,aAAW,cAAc,QAAQ;AAE/B,QACE,WAAW,SAAS,SAAS,gBAAgB,KAC7C,WAAW,SAAS,SAAS,iBAAiB,GAC9C;AACA;AAAA,IACF;AAEA,UAAM,QAAQ,iBAAiB,YAAY,QAAQ;AACnD,QAAI,OAAO;AACT,UAAI,MAAM,SAAS,aAAa;AAC9B,eAAO,4BAA4B,MAAM,MAAM,SAAS,SAAS,UAAU,aAAa;AAAA,MAC1F;AACA,UAAI,MAAM,SAAS,aAAa;AAC9B,cAAM,eAAe;AAAA,UACnB,MAAM,KAAK;AAAA,UACX;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,eAAO,gBAAgB,MAAM,KAAK,MAAM,SAAS,SAAS,YAAY;AAAA,MACxE;AACA,UAAI,MAAM,SAAS,QAAQ;AACzB,eAAO,uBAAuB,MAAM,IAAI;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAeO,SAAS,iBAAiB,YAA2B,UAAuC;AACjG,MAAI,QAA6B;AACjC,EAAAA,IAAG,aAAa,YAAY,CAAC,SAAS;AACpC,QAAI,MAAO;AACX,QAAIA,IAAG,uBAAuB,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AAClE,cAAQ,EAAE,MAAM,aAAa,KAAK;AAAA,IACpC,WAAWA,IAAG,uBAAuB,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AACzE,cAAQ,EAAE,MAAM,aAAa,KAAK;AAAA,IACpC,WAAWA,IAAG,kBAAkB,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AACpE,cAAQ,EAAE,MAAM,QAAQ,KAAK;AAAA,IAC/B;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAWO,SAAS,uBAAuB,MAAuC;AAC5E,QAAM,UAAyB,CAAC;AAChC,MAAI,mBAAmB;AAEvB,aAAW,UAAU,KAAK,SAAS;AACjC,QAAI,OAAO,aAAa;AACtB,UAAIA,IAAG,gBAAgB,OAAO,WAAW,GAAG;AAC1C,gBAAQ,KAAK,EAAE,MAAM,WAAW,OAAO,OAAO,YAAY,KAAK,CAAC;AAAA,MAClE,WAAWA,IAAG,iBAAiB,OAAO,WAAW,GAAG;AAClD,cAAM,MAAM,OAAO,OAAO,YAAY,IAAI;AAC1C,gBAAQ,KAAK,EAAE,MAAM,WAAW,OAAO,IAAI,CAAC;AAC5C,2BAAmB,MAAM;AAAA,MAC3B,OAAO;AACL,cAAM,IAAI;AAAA,UACR,KAAK,KAAK;AAAA,UACV,sBAAY,OAAO,KAAK,QAAQ,CAAC;AAAA,QACnC;AAAA,MACF;AAAA,IACF,OAAO;AAEL,cAAQ,KAAK,EAAE,MAAM,WAAW,OAAO,iBAAiB,CAAC;AACzD;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,SAAS,QAAQ;AAClC;AAYO,SAAS,4BACd,MACA,SACA,UAAuB,oBAAI,IAAI,GAC/B,gBAA0C,oBAAI,IAAI,GAClD,eACa;AACb,QAAM,aAA6B,CAAC;AACpC,QAAM,UAAU,oBAAI,IAA0B;AAE9C,MAAI;AAGJ,QAAM,WAAW;AAAA,IACf,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,aAAW,kBAAkB,KAAK,mBAAmB,CAAC,GAAG;AACvD,QAAI,eAAe,UAAUA,IAAG,WAAW,gBAAgB;AACzD,iBAAW,QAAQ,eAAe,OAAO;AACvC,cAAM,aAAa,gBAAgB,MAAM,SAAS,SAAS,QAAQ;AACnE,YAAI,WAAW,SAAS,UAAU;AAChC,qBAAW,QAAQ,WAAW,YAAY;AACxC,oBAAQ,IAAI,KAAK,MAAM,IAAI;AAAA,UAC7B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,aAAW,UAAU,KAAK,SAAS;AAEjC,QACEA,IAAG,kBAAkB,MAAM,KAC3BA,IAAG,yBAAyB,MAAM,KAClCA,IAAG,yBAAyB,MAAM,GAClC;AACA,YAAM,sBAAsB;AAAA,QAC1B;AAAA,QACA,OAAO,QAAQ;AAAA,QACf;AAAA,MACF;AAAA,IACF;AACA,QAAIA,IAAG,oBAAoB,MAAM,KAAK,OAAO,MAAM;AACjD,YAAM,OAAO,OAAO,KAAK,QAAQ;AACjC,YAAM,WAAW,CAAC,CAAC,OAAO;AAC1B,YAAM,OAAO,OAAO,OAChB,gBAAgB,OAAO,MAAM,SAAS,SAAS,QAAQ,IACvD,EAAE,MAAM,MAAe;AAC3B,YAAM,cAAc,4BAA4B,QAAQ,IAAI;AAC5D,0BAAoB,aAAa,MAAM,IAAI;AAC3C,cAAQ;AAAA,QACN;AAAA,QACA,YAAY,SAAS,IAAI,EAAE,MAAM,MAAM,UAAU,YAAY,IAAI,EAAE,MAAM,MAAM,SAAS;AAAA,MAC1F;AAAA,IACF;AAEA,QAAIA,IAAG,4BAA4B,MAAM,GAAG;AAC1C,iBAAW,OAAO,OACd,gBAAgB,OAAO,MAAM,SAAS,SAAS,QAAQ,IACvD,EAAE,MAAM,MAAe;AAAA,IAC7B;AAAA,EACF;AAEA,aAAW,QAAQ,QAAQ,OAAO,GAAG;AACnC,eAAW,KAAK,IAAI;AAAA,EACtB;AAEA,SAAO,aAAa,SAChB,EAAE,MAAM,UAAU,YAAY,SAAS,IACvC,EAAE,MAAM,UAAU,WAAW;AACnC;AAKA,IAAM,0BAA0B,oBAAI,IAA4B;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKD,IAAM,0BAA0B,oBAAI,IAA4B;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKD,IAAM,iCAAiC,oBAAI,IAA4B;AAAA,EACrE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAYD,SAAS,4BACP,MACA,WACkB;AAElB,QAAM,SAASA,IACZ,wBAAwB,IAAI,EAC5B,OAAO,CAAC,UAA6BA,IAAG,QAAQ,KAAK,CAAC;AAEzD,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AAEjC,QAAM,cAAgC,CAAC;AAEvC,aAAW,SAAS,QAAQ;AAC1B,QAAI,CAAC,MAAM,KAAM;AACjB,eAAW,OAAO,MAAM,MAAM;AAC5B,YAAM,aAAa,cAAc,KAAK,SAAS;AAC/C,UAAI,WAAY,aAAY,KAAK,UAAU;AAAA,IAC7C;AAAA,EACF;AAEA,SAAO;AACT;AAUA,SAAS,kBAAkB,KAAsC;AAC/D,QAAM,UAAU,IAAI;AACpB,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,SAAO;AACT;AASA,SAAS,cAAc,KAAkB,WAA0C;AACjF,QAAM,UAAU,IAAI,QAAQ;AAE5B,UAAQ,SAAS;AAAA;AAAA,IAEf,KAAK;AAAA,IACL,KAAK,OAAO;AACV,YAAM,QAAQ,iBAAiB,KAAK,WAAW,OAAO;AACtD,aAAO,EAAE,MAAM,SAAS,MAAM;AAAA,IAChC;AAAA;AAAA,IAGA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,UAAU;AACb,YAAM,QAAQ,iBAAiB,KAAK,WAAW,OAAO;AACtD,aAAO,EAAE,MAAM,SAAS,MAAM;AAAA,IAChC;AAAA;AAAA,IAGA,KAAK;AAAA,IACL,KAAK,WAAW;AACd,YAAM,OAAO,kBAAkB,GAAG;AAClC,UAAI,CAAC,MAAM;AACT,cAAM,IAAI,sBAAsB,WAAW,IAAI,OAAO,mEAA2B;AAAA,MACnF;AACA,YAAM,QAAQ,kBAAkB,KAAK,KAAK,GAAG,SAAS;AACtD,aAAO,EAAE,MAAM,SAAS,SAAS,MAAM,SAAS,OAAO,MAAM,MAAM;AAAA,IACrE;AAAA;AAAA,IAGA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ;AAAA;AAAA,IAGzB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ;AAAA,IAEzB;AACE,aAAO;AAAA,EACX;AACF;AASA,SAAS,iBAAiB,KAAkB,WAAmB,SAAyB;AACtF,QAAM,OAAO,kBAAkB,GAAG;AAClC,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,sBAAsB,WAAW,IAAI,OAAO,yDAAY;AAAA,EACpE;AACA,QAAM,UAAU,KAAK,KAAK;AAC1B,QAAM,MAAM,OAAO,OAAO;AAC1B,MAAI,CAAC,OAAO,SAAS,GAAG,GAAG;AACzB,UAAM,IAAI,sBAAsB,WAAW,IAAI,OAAO,8BAAU,OAAO,wCAAU;AAAA,EACnF;AACA,SAAO;AACT;AAQA,SAAS,kBAAkB,MAAc,WAAwD;AAE/F,QAAM,QAAQ,wBAAwB,KAAK,IAAI;AAC/C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,sBAAsB,WAAW,uBAAQ,IAAI,4CAAwB;AAAA,EACjF;AACA,QAAM,CAAC,EAAE,SAAS,KAAK,IAAI;AAC3B,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,sBAAsB,WAAW,uBAAQ,IAAI,2CAAkB;AAAA,EAC3E;AACA,SAAO,QAAQ,EAAE,SAAS,MAAM,IAAI,EAAE,QAAQ;AAChD;AAWA,SAAS,oBACP,aACA,MACA,WACM;AACN,MAAI,YAAY,WAAW,EAAG;AAE9B,aAAW,cAAc,aAAa;AACpC,UAAM,OAAO,WAAW;AAExB,QAAI,wBAAwB,IAAI,IAAI,GAAG;AACrC,UAAI,KAAK,SAAS,UAAU;AAC1B,cAAM,IAAI;AAAA,UACR;AAAA,UACA,IAAI,IAAI,qFAAyB,KAAK,IAAI;AAAA,QAC5C;AAAA,MACF;AACA;AAAA,IACF;AAEA,QAAI,wBAAwB,IAAI,IAAI,GAAG;AACrC,UAAI,KAAK,SAAS,YAAY,KAAK,SAAS,SAAS;AACnD,cAAM,IAAI;AAAA,UACR;AAAA,UACA,IAAI,IAAI,kGAAiC,KAAK,IAAI;AAAA,QACpD;AAAA,MACF;AACA;AAAA,IACF;AAEA,QAAI,+BAA+B,IAAI,IAAI,GAAG;AAC5C,UAAI,KAAK,SAAS,UAAU;AAC1B,cAAM,IAAI;AAAA,UACR;AAAA,UACA,IAAI,IAAI,qFAAyB,KAAK,IAAI;AAAA,QAC5C;AAAA,MACF;AACA;AAAA,IACF;AAAA,EACF;AACF;;;AD7sCO,SAAS,gBACd,SACA,UACA,UACwB;AACxB,QAAM,aAAa,QAAQ,cAAc,QAAQ;AACjD,MAAI,CAAC,WAAY,QAAO;AAExB,QAAM,UAAU,QAAQ,eAAe;AAIvC,oBAAkB,OAAO;AACzB,MAAI;AAEF,QAAI,SAAiC;AAErC,IAAAC,IAAG,aAAa,YAAY,CAAC,SAAS;AACpC,UAAI,OAAQ;AAEZ,UAAIA,IAAG,uBAAuB,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AAClE,cAAM,UAAU,oBAAI,IAAY;AAChC,gBAAQ,IAAI,QAAQ;AACpB,cAAM,cAAc;AAAA,UAAgB;AAAA,UAAU;AAAA,UAAU,MACtD,4BAA4B,MAAM,SAAS,OAAO;AAAA,QACpD;AACA,iBAAS;AAAA,UACP,MAAM;AAAA,UACN,YAAY,YAAY,SAAS,WAAW,YAAY,aAAa,CAAC;AAAA,UACtE;AAAA,QACF;AACA;AAAA,MACF;AAGA,UAAIA,IAAG,uBAAuB,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AAClE,cAAM,UAAU,oBAAI,IAAY;AAChC,gBAAQ,IAAI,QAAQ;AACpB,cAAM,cAAc;AAAA,UAAgB;AAAA,UAAU;AAAA,UAAU,MACtD,gBAAgB,KAAK,MAAM,SAAS,OAAO;AAAA,QAC7C;AACA,iBAAS;AAAA,UACP,MAAM;AAAA,UACN,YAAY,YAAY,SAAS,WAAW,YAAY,aAAa,CAAC;AAAA,UACtE;AAAA,QACF;AACA;AAAA,MACF;AAAA,IACF,CAAC;AAED,QAAI,OAAQ,QAAO;AAGnB,eAAW,MAAM,QAAQ,eAAe,GAAG;AACzC,UAAI,OAAO,WAAY;AAEvB,UAAI,GAAG,SAAS,SAAS,gBAAgB,KAAK,GAAG,SAAS,SAAS,iBAAiB,GAAG;AACrF;AAAA,MACF;AACA,YAAM,QAAQ,iBAAiB,IAAI,QAAQ;AAC3C,UAAI,CAAC,MAAO;AAEZ,YAAM,UAAU,oBAAI,IAAY;AAChC,cAAQ,IAAI,QAAQ;AACpB,YAAM,cAAc,gBAAgB,UAAU,UAAU,MAAM;AAC5D,YAAI,MAAM,SAAS,aAAa;AAC9B,iBAAO,4BAA4B,MAAM,MAAM,SAAS,OAAO;AAAA,QACjE;AACA,YAAI,MAAM,SAAS,aAAa;AAC9B,iBAAO,gBAAgB,MAAM,KAAK,MAAM,SAAS,OAAO;AAAA,QAC1D;AACA,eAAO,uBAAuB,MAAM,IAAI;AAAA,MAC1C,CAAC;AACD,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,YAAY,SAAS,WAAW,YAAY,aAAa,CAAC;AAAA,QACtE;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT,UAAE;AACA,sBAAkB,IAAI;AAAA,EACxB;AACF;AAwFO,SAAS,uBAAuB,SAAqB,UAAoC;AAC9F,QAAM,QAAQ,oBAAI,IAAoC;AACtD,SAAO;AAAA,IACL,QAAQ,MAAsC;AAC5C,UAAI,MAAM,IAAI,IAAI,EAAG,QAAO,MAAM,IAAI,IAAI;AAC1C,YAAM,OAAO,gBAAgB,SAAS,UAAU,IAAI;AACpD,YAAM,IAAI,MAAM,IAAI;AACpB,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAOA,SAAS,gBAAmB,UAAkB,UAAkB,IAAgB;AAC9E,MAAI;AACF,WAAO,GAAG;AAAA,EACZ,SAAS,KAAK;AACZ,QAAI,eAAe,uBAAuB;AAExC,YAAM,WAAW,SAAS,MAAM,GAAG,EAAE,IAAI,KAAK;AAC9C,YAAM,WAAW,IAAI;AAAA,QACnB,IAAI;AAAA,QACJ,GAAG,IAAI,MAAM,uBAAQ,QAAQ,mBAAS,QAAQ;AAAA,QAC9C,EAAE,OAAO,IAAI;AAAA,QACb,IAAI;AAAA,MACN;AACA,YAAM;AAAA,IACR;AACA,UAAM;AAAA,EACR;AACF;;;AE5OO,SAAS,sBAAsB,QAAkC;AACtE,QAAM,QAAQ,OAAO,YAAY;AACjC,MAAI,UAAU,SAAS,UAAU,YAAY,UAAU,QAAQ;AAC7D,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAQO,SAAS,QAAQ,QAAyB;AAC/C,QAAM,QAAQ,OAAO,YAAY;AACjC,SAAO,UAAU,UAAU,UAAU,SAAS,UAAU,WAAW,UAAU;AAC/E;;;ACrBO,SAAS,cAAc,QAAgB,WAAgD;AAC5F,SAAO,GAAG,OAAO,YAAY,CAAC,GAAG,UAAU,OAAO,CAAC,EAAE,YAAY,IAAI,UAAU,MAAM,CAAC,CAAC;AACzF;;;ACRA,OAAOC,SAAQ;AAmDR,SAAS,6BACd,YACA,cACe;AACf,QAAM,SAAsB,CAAC;AAG7B,EAAAC,IAAG,aAAa,YAAY,CAAC,SAAS;AACpC,QAAIA,IAAG,sBAAsB,IAAI,KAAK,KAAK,MAAM,SAAS,cAAc;AAEtE,iBAAW,SAAS,KAAK,YAAY;AACnC,cAAM,YAAY,aAAa,OAAO,UAAU;AAChD,eAAO,KAAK,SAAS;AAAA,MACvB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,EAAE,OAAO;AAClB;AAKA,SAAS,aAAa,OAAgC,YAAsC;AAC1F,QAAM,OAAO,MAAM,KAAK,QAAQ,UAAU;AAC1C,QAAM,OAAO,eAAe,IAAI,KAAK;AAErC,QAAM,SAAoB,EAAE,MAAM,KAAK;AAGvC,MAAI,MAAM,MAAM;AACd,QAAIA,IAAG,oBAAoB,MAAM,IAAI,GAAG;AAEtC,aAAO,WAAW,MAAM,KAAK,SAAS,QAAQ,UAAU;AAAA,IAC1D,WAAWA,IAAG,kBAAkB,MAAM,IAAI,GAAG;AAE3C,aAAO,SAAS,cAAc,MAAM,MAAM,UAAU;AAAA,IACtD;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,cAAc,UAA8B,YAA2C;AAC9F,QAAM,SAAyB,CAAC;AAEhC,aAAW,UAAU,SAAS,SAAS;AACrC,QAAIA,IAAG,oBAAoB,MAAM,KAAK,OAAO,QAAQA,IAAG,aAAa,OAAO,IAAI,GAAG;AACjF,YAAM,WAAW,OAAO,KAAK;AAC7B,YAAM,WAAW,CAAC,CAAC,OAAO;AAC1B,YAAM,WAAW,OAAO,MAAM,QAAQ,UAAU,KAAK;AAErD,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;;;ACzGA,OAAOC,WAAU;AACjB,OAAOC,SAAQ;AAmDf,SAAS,qBAAqB,YAA2B,MAAuB;AAC9E,aAAW,aAAa,WAAW,YAAY;AAC7C,UAAM,YAAYA,IAAG,iBAAiB,SAAS,IAAIA,IAAG,aAAa,SAAS,IAAI;AAChF,UAAM,aAAa,WAAW,KAAK,CAAC,MAAM,EAAE,SAASA,IAAG,WAAW,aAAa;AAChF,QAAI,CAAC,WAAY;AACjB,SACGA,IAAG,uBAAuB,SAAS,KAAKA,IAAG,uBAAuB,SAAS,MAC5E,UAAU,MAAM,SAAS,MACzB;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,0BACd,QACA,SACA,UAKA;AAGA,QAAM,gBAAgB,oBAAI,IAAuD;AACjF,aAAW,SAAS,QAAQ;AAC1B,UAAM,WAAW,UAAUD,MAAK,QAAQ,SAAS,MAAM,QAAQ,IAAI,MAAM;AACzE,QAAI,QAAQ,cAAc,IAAI,QAAQ;AACtC,QAAI,CAAC,OAAO;AACV,cAAQ,EAAE,SAAS,MAAM,SAAS,SAAS,oBAAI,IAAI,EAAE;AACrD,oBAAc,IAAI,UAAU,KAAK;AAAA,IACnC;AACA,UAAM,QAAQ,IAAI,MAAM,MAAM;AAAA,EAChC;AAIA,QAAM,UAAwD,CAAC;AAC/D,aAAW,WAAW,YAAY,CAAC,GAAG;AACpC,YAAQ,KAAK;AAAA,MACX,UAAU,UAAUA,MAAK,QAAQ,SAAS,QAAQ,QAAQ,IAAI,QAAQ;AAAA,MACtE,SAAS,QAAQ;AAAA,IACnB,CAAC;AAAA,EACH;AAGA,QAAM,WAAW,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,cAAc,KAAK,GAAG,GAAG,QAAQ,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC1F,QAAM,gBAAgB,eAAe,QAAQ;AAG7C,QAAM,kBAAkB,oBAAI,IAA8B;AAC1D,aAAW,YAAY,UAAU;AAC/B,oBAAgB,IAAI,UAAU,uBAAuB,cAAc,IAAI,QAAQ,GAAI,QAAQ,CAAC;AAAA,EAC9F;AAGA,QAAM,UAA+B,CAAC;AACtC,aAAW,CAAC,UAAU,KAAK,KAAK,eAAe;AAC7C,UAAM,UAAU,cAAc,IAAI,QAAQ;AAC1C,UAAM,aAAa,QAAQ,cAAc,QAAQ;AACjD,QAAI,CAAC,WAAY;AACjB,UAAM,WAAW,gBAAgB,IAAI,QAAQ;AAC7C,eAAW,UAAU,MAAM,SAAS;AAClC,YAAM,YAAY,sBAAsB,MAAM;AAC9C,YAAM,aAAa,cAAc,QAAQ,SAAS;AAElD,YAAM,OAAO,6BAA6B,YAAY,MAAM;AAK5D,YAAM,QACJ,KAAK,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,SAAS,MAC3C,cAAc,SAAS,KAAK,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,IAAI;AACvE,YAAM,SAAS,OAAO,SAAS;AAE/B,YAAM,WAAW,OAAO,WAAW,SAAS,QAAQ,MAAM,QAAQ,IAAI;AACtE,cAAQ,KAAK;AAAA,QACX,SAAS,MAAM;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ,UAAU;AAAA,MACpB,CAAC;AAOD,YAAM,YAAY,KAAK,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ;AAC7D,UAAI,WAAW,UAAU;AACvB,gBAAQ,KAAK;AAAA,UACX,SAAS,MAAM;AAAA,UACf;AAAA,UACA,YAAY,cAAc,QAAQ,QAAQ;AAAA,UAC1C,UAAU,SAAS,QAAQ,UAAU,QAAQ,KAAK;AAAA,QACpD,CAAC;AAAA,MACH;AASA,UAAI,cAAc,QAAQ;AACxB,cAAM,WAAW,KAAK,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO;AAC3D,YAAI,UAAU,UAAU;AACtB,kBAAQ,KAAK;AAAA,YACX,SAAS,MAAM;AAAA,YACf;AAAA,YACA,YAAY,cAAc,QAAQ,OAAO;AAAA,YACzC,UAAU,SAAS,QAAQ,SAAS,QAAQ,KAAK;AAAA,UACnD,CAAC;AAAA,QACH;AAAA,MACF,WAAW,QAAQ,MAAM,GAAG;AAC1B,cAAM,UACJ,KAAK,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,KAAK,KAAK,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AACzF,YAAI,SAAS,UAAU;AACrB,kBAAQ,KAAK;AAAA,YACX,SAAS,MAAM;AAAA,YACf;AAAA,YACA,YAAY,cAAc,QAAQ,MAAM;AAAA,YACxC,UAAU,SAAS,QAAQ,QAAQ,QAAQ,KAAK;AAAA,YAChD,QAAQ,QAAQ,SAAS,UAAU;AAAA,UACrC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAMA,aAAW,UAAU,SAAS;AAC5B,UAAM,UAAU,cAAc,IAAI,OAAO,QAAQ;AACjD,UAAM,aAAa,SAAS,cAAc,OAAO,QAAQ;AACzD,QAAI,CAAC,WAAY;AACjB,UAAM,WAAW,gBAAgB,IAAI,OAAO,QAAQ;AACpD,QAAI,qBAAqB,YAAY,OAAO,GAAG;AAC7C,cAAQ,KAAK;AAAA,QACX,SAAS,OAAO;AAAA,QAChB,UAAU,OAAO;AAAA,QACjB,YAAY,cAAc,MAAM,OAAO;AAAA,QACvC,UAAU,SAAS,QAAQ,OAAO,KAAK;AAAA,MACzC,CAAC;AAAA,IACH;AACA,QAAI,qBAAqB,YAAY,QAAQ,GAAG;AAC9C,cAAQ,KAAK;AAAA,QACX,SAAS,OAAO;AAAA,QAChB,UAAU,OAAO;AAAA,QACjB,YAAY,cAAc,MAAM,QAAQ;AAAA,QACxC,UAAU,SAAS,QAAQ,QAAQ,KAAK;AAAA,MAC1C,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,gBAAgB;AACpC;;;AC7MA,IAAM,iBAAN,MAAqB;AAAA;AAAA,EAEV,aAAa,oBAAI,IAAyB;AAAA;AAAA,EAE1C;AAAA;AAAA,EAET,gBAAgB;AAAA;AAAA,EAEhB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOlB,SAAS;AAAA,EAET,YAAY,aAA2B;AACrC,SAAK,cAAc;AAAA,EACrB;AACF;AAQO,SAAS,kBAAkB,MAAmB,KAA2B;AAC9E,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH;AAAA,IAEF,KAAK;AACH,wBAAkB,KAAK,SAAS,GAAG;AACnC;AAAA,IAEF,KAAK;AACH,iBAAW,MAAM,KAAK,UAAU;AAC9B,0BAAkB,GAAG,MAAM,GAAG;AAAA,MAChC;AACA;AAAA,IAEF,KAAK;AACH,iBAAW,QAAQ,KAAK,YAAY;AAClC,0BAAkB,KAAK,MAAM,GAAG;AAAA,MAClC;AACA;AAAA,IAEF,KAAK;AACH,iBAAW,UAAU,KAAK,SAAS;AACjC,0BAAkB,QAAQ,GAAG;AAAA,MAC/B;AACA;AAAA,IAEF,KAAK;AACH,wBAAkB,KAAK,KAAK,GAAG;AAC/B,wBAAkB,KAAK,OAAO,GAAG;AACjC;AAAA,IAEF,KAAK;AACH,wBAAkB,KAAK,KAAK,GAAG;AAC/B,wBAAkB,KAAK,OAAO,GAAG;AACjC;AAAA,IAEF,KAAK;AACH,wBAAkB,KAAK,SAAS,GAAG;AACnC;AAAA,IAEF,KAAK,OAAO;AACV,UAAI,IAAI,WAAW,IAAI,KAAK,IAAI,EAAG;AAEnC,UAAI,WAAW,IAAI,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC;AAC7C,YAAM,WAAW,IAAI,YAAY,KAAK,IAAI;AAC1C,UAAI,UAAU;AACZ,YAAI,WAAW,IAAI,KAAK,MAAM,QAAQ;AACtC,0BAAkB,UAAU,GAAG;AAC/B;AAAA,MACF;AAIA,YAAM,IAAI;AAAA,QACR,KAAK;AAAA,QACL,iEAAe,KAAK,IAAI;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AACF;AASO,SAAS,2BACd,MACA,KACA,aACQ;AACR,QAAM,OAAO,eAAe,MAAM,GAAG;AAGrC,QAAM,kBACJ,eAAe,YAAY,SAAS,IAAI,iBAAiB,MAAM,aAAa,KAAK,IAAI,IAAI;AAG3F,MAAI,IAAI,WAAW,KAAK,SAAS,YAAY,KAAK,SAAS,YAAY;AACrE,WAAO,qBAAqB,KAAK,MAAM,eAAe;AAAA,EACxD;AAIA,MACE,IAAI,UACJ,KAAK,SAAS,cACb,OAAO,KAAK,UAAU,YAAY,OAAO,KAAK,UAAU,YACzD;AACA,WAAO;AAAA,MACL,OAAO,KAAK,UAAU,WAAW,WAAW;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAcA,SAAS,iBACP,UACA,aACA,UACQ;AACR,QAAM,SAAS,YAAY,IAAI,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAC,EAAE,KAAK,EAAE;AAChF,SAAO,GAAG,QAAQ,GAAG,MAAM;AAC7B;AAQA,SAAS,qBAAqB,YAA4B,WAAwC;AAChG,UAAQ,WAAW,MAAM;AAAA,IACvB,KAAK;AACH,aAAO,QAAQ,WAAW,KAAK;AAAA,IACjC,KAAK;AACH,aAAO,QAAQ,WAAW,KAAK;AAAA,IACjC,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,QAAQ,WAAW,KAAK;AAAA,IACjC,KAAK;AACH,aAAO,QAAQ,WAAW,KAAK;AAAA,IACjC,KAAK;AACH,aAAO,WAAW,WAAW,KAAK;AAAA,IACpC,KAAK,SAAS;AACZ,YAAM,QAAQ,WAAW,SAAS;AAElC,aAAO,qBAAqB,KAAK,UAAU,WAAW,OAAO,CAAC,GAAG,QAAQ,KAAK,KAAK,UAAU,KAAK,CAAC,KAAK,EAAE;AAAA,IAC5G;AAAA,IACA,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EACX;AACF;AAKA,SAAS,eAAe,MAAmB,KAA6B;AACtE,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAEH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,aAAa,KAAK,UAAU,KAAK,KAAK,CAAC;AAAA,IAChD,KAAK;AACH,aAAO,WAAW,2BAA2B,KAAK,SAAS,GAAG,CAAC;AAAA,IACjE,KAAK;AACH,aAAO,wBAAwB,KAAK,UAAU,GAAG;AAAA,IACnD,KAAK;AACH,aAAO,yBAAyB,MAAM,GAAG;AAAA,IAC3C,KAAK;AACH,aAAO,wBAAwB,KAAK,SAAS,GAAG;AAAA,IAClD,KAAK;AAKH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,YAAY,2BAA2B,KAAK,KAAK,GAAG,CAAC,KAAK,2BAA2B,KAAK,OAAO,GAAG,CAAC;AAAA,IAC9G,KAAK;AAGH,aAAO,iCAAiC,2BAA2B,KAAK,KAAK,GAAG,CAAC,KAAK,2BAA2B,KAAK,OAAO,GAAG,CAAC;AAAA,IACnI,KAAK;AAGH,aAAO,iCAAiC,2BAA2B,KAAK,SAAS,GAAG,CAAC;AAAA,IACvF,KAAK;AAGH,UAAI,KAAK,SAAS,IAAI,eAAe;AACnC,eAAO,GAAG,IAAI,eAAe;AAAA,MAC/B;AACA,aAAO,GAAG,KAAK,IAAI;AAAA,EACvB;AACF;AAWO,IAAM,uBACX;AAYK,IAAM,wBACX;AAiBK,IAAM,oBACX;AAaK,IAAM,oBACX;AAKK,IAAM,mBAAmB;AASzB,SAAS,4BAAoC;AAClD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AASO,SAAS,kBAAkB,MAAuB;AACvD,SACE,KAAK,SAAS,cAAc,KAC5B,KAAK,SAAS,eAAe,KAC7B,KAAK,SAAS,WAAW,KACzB,KAAK,SAAS,WAAW;AAE7B;AAQA,SAAS,qBAAqB,MAA4B,OAAuB;AAC/E,MAAI,SAAS,UAAU;AACrB,WAAO,8BAA8B,KAAK;AAAA,EAC5C;AACA,SAAO,+BAA+B,KAAK;AAC7C;AAUA,SAAS,wBAAwB,UAA0B,KAA6B;AACtF,QAAM,aAAuB,CAAC;AAC9B,QAAM,gBAA2B,CAAC;AAClC,MAAI,iBAAiB;AACrB,MAAI,cAAc;AAElB,aAAW,MAAM,UAAU;AACzB,QAAI,GAAG,MAAM;AACX,uBAAiB,2BAA2B,GAAG,MAAM,GAAG;AACxD,oBAAc;AAAA,IAChB,WAAW,CAAC,aAAa;AACvB,iBAAW,KAAK,2BAA2B,GAAG,MAAM,GAAG,CAAC;AACxD,oBAAc,KAAK,GAAG,QAAQ;AAAA,IAChC;AAAA,EACF;AAKA,MAAI,gBAAgB;AAClB,UAAM,oBAAoB,WAAW;AAAA,MAAI,CAAC,MAAM,MAC9C,cAAc,CAAC,IAAI,GAAG,IAAI,gBAAgB;AAAA,IAC5C;AACA,WAAO,YAAY,kBAAkB,KAAK,IAAI,CAAC,WAAW,cAAc;AAAA,EAC1E;AAGA,QAAM,cAAc,cAAc,KAAK,CAAC,MAAM,CAAC;AAC/C,MAAI,CAAC,aAAa;AAChB,WAAO,YAAY,WAAW,KAAK,IAAI,CAAC;AAAA,EAC1C;AAIA,QAAM,WAAqB,CAAC;AAC5B,WAAS,MAAM,WAAW,QAAQ,OAAO,GAAG,OAAO;AAIjD,UAAM,UAAU,cAAc,MAAM,GAAG;AACvC,QAAI,QAAQ,SAAS,KAAK,QAAQ,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG;AAEjD;AAAA,IACF;AACA,UAAM,SAAS,WAAW,MAAM,GAAG,GAAG;AACtC,aAAS,KAAK,YAAY,OAAO,KAAK,IAAI,CAAC,IAAI;AAAA,EACjD;AAGA,WAAS,QAAQ;AACjB,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,SAAS,CAAC;AAAA,EACnB;AACA,SAAO,YAAY,SAAS,KAAK,IAAI,CAAC;AACxC;AASA,SAAS,yBACP,MACA,KACQ;AACR,QAAM,SAAS,KAAK,WAAW,IAAI,CAAC,SAAS;AAC3C,UAAM,OAAO,2BAA2B,KAAK,MAAM,KAAK,KAAK,WAAW;AACxE,UAAM,YAAY,KAAK,WAAW,GAAG,IAAI,gBAAgB;AACzD,WAAO,GAAG,KAAK,UAAU,KAAK,IAAI,CAAC,KAAK,SAAS;AAAA,EACnD,CAAC;AACD,QAAM,aAAa,cAAc,OAAO,KAAK,IAAI,CAAC;AAClD,SAAO,KAAK,aAAa,SACrB,GAAG,UAAU,aAAa,2BAA2B,KAAK,UAAU,GAAG,CAAC,MACxE;AACN;AAQA,SAAS,wBAAwB,SAAwB,KAA6B;AACpF,QAAM,UAAU,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AACrD,QAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM;AAGvD,MAAI,WAAW,QAAQ,WAAW,GAAG;AACnC,WAAO,GAAG,2BAA2B,QAAQ,CAAC,GAAI,GAAG,CAAC;AAAA,EACxD;AAGA,MAAI,SAAS;AACX,UAAME,cAAa,QAAQ,IAAI,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC,EAAE,KAAK,IAAI;AACnF,WAAO,YAAYA,WAAU;AAAA,EAC/B;AAGA,QAAM,aAAa,QAAQ,IAAI,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC,EAAE,KAAK,IAAI;AACnF,SAAO,YAAY,UAAU;AAC/B;AAQA,SAAS,6BACP,MACA,MACA,KACQ;AACR,QAAM,OAAO,2BAA2B,MAAM,GAAG;AACjD,QAAM,SAAS,YAAY,MAAM,oBAAI,IAAI,CAAC,IAAI,CAAC,CAAC;AAChD,MAAI,QAAQ;AAEV,WAAO,SAAS,IAAI,yBAAyB,IAAI;AAAA,EACnD;AACA,SAAO,SAAS,IAAI,YAAY,IAAI;AACtC;AAKA,SAAS,YAAY,MAAmB,SAA+B;AACrE,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,aAAO,QAAQ,IAAI,KAAK,IAAI;AAAA,IAC9B,KAAK;AACH,aAAO,YAAY,KAAK,SAAS,OAAO;AAAA,IAC1C,KAAK;AACH,aAAO,KAAK,SAAS,KAAK,CAAC,OAAO,YAAY,GAAG,MAAM,OAAO,CAAC;AAAA,IACjE,KAAK;AACH,aAAO,KAAK,WAAW,KAAK,CAAC,SAAS,YAAY,KAAK,MAAM,OAAO,CAAC;AAAA,IACvE,KAAK;AACH,aAAO,KAAK,QAAQ,KAAK,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC;AAAA,IACzD,KAAK;AACH,aAAO,YAAY,KAAK,KAAK,OAAO,KAAK,YAAY,KAAK,OAAO,OAAO;AAAA,IAC1E,KAAK;AACH,aAAO,YAAY,KAAK,KAAK,OAAO,KAAK,YAAY,KAAK,OAAO,OAAO;AAAA,IAC1E,KAAK;AACH,aAAO,YAAY,KAAK,SAAS,OAAO;AAAA,IAC1C;AACE,aAAO;AAAA,EACX;AACF;AA4DO,SAAS,6BACd,UACA,aACA,YACA,SAAS,OACa;AACtB,QAAM,MAAM,IAAI,eAAe,WAAW;AAC1C,QAAM,OAAO,cAAc,SAAS;AACpC,MAAI,gBAAgB,SAAS;AAC7B,MAAI,kBAAkB;AACtB,MAAI,SAAS;AAGb,oBAAkB,SAAS,aAAa,GAAG;AAC3C,MAAI,WAAW,OAAO,SAAS,IAAI;AAEnC,QAAM,wBAAwB,CAAC,GAAG,IAAI,UAAU,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI,OAAO;AAAA,IACpE,MAAM;AAAA,IACN,aAAa,6BAA6B,GAAG,MAAM,GAAG;AAAA,EACxD,EAAE;AAGF,QAAM,YAAY,2BAA2B,SAAS,aAAa,GAAG;AACtE,QAAM,aAAa,YAAY,SAAS,aAAa,oBAAI,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;AAC7E,QAAM,mBAAmB,aACrB,gBAAgB,IAAI,yBAAyB,SAAS,OACtD,gBAAgB,IAAI,YAAY,SAAS;AAE7C,SAAO,EAAE,uBAAuB,iBAAiB;AACnD;;;ACvnBA,OAAOC,WAAU;AACjB,SAAS,kBAAkB;;;ACD3B,OAAOC,WAAU;AACjB,OAAOC,SAAQ;AAef,eAAsB,gBAAgB,YAAoB,SAAgC;AACxF,QAAMA,IAAG,SAAS,MAAMD,MAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AACrE,QAAM,MAAM,GAAG,UAAU,QAAQ,QAAQ,GAAG,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AACnF,QAAMC,IAAG,SAAS,UAAU,KAAK,SAAS,OAAO;AACjD,QAAMA,IAAG,SAAS,OAAO,KAAK,UAAU;AAC1C;;;AD4BA,eAAsB,qBACpB,SACA,SACe;AACf,QAAM,EAAE,oBAAoB,iBAAiB,SAAS,KAAK,IAAI;AAC/D,MAAI,QAAQ,WAAW,EAAG;AAG1B,QAAM,gBAAgB,oBAAI,IAAiB;AAC3C,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,cAAc,IAAI,OAAO,QAAQ;AAC5C,QAAI,CAAC,MAAM;AACT,aAAO,CAAC;AACR,oBAAc,IAAI,OAAO,UAAU,IAAI;AAAA,IACzC;AACA,SAAK,KAAK,MAAM;AAAA,EAClB;AAGA,QAAM,cAAwD,CAAC;AAC/D,aAAW,CAAC,UAAU,WAAW,KAAK,eAAe;AACnD,UAAM,UAAUC,MAAK,SAAS,SAAS,QAAQ,EAAE,QAAQ,OAAO,GAAG;AACnE,UAAM,aAAa,oBAAoB,SAAS,MAAM,OAAO;AAC7D,UAAM,WAAW,gBAAgB,IAAI,QAAQ;AAI7C,QAAI,YAAY;AAChB,QAAI,UAAU,WAAW,MAAM,GAAG;AAChC,kBAAY,UAAU,MAAM,CAAC;AAAA,IAC/B;AACA,UAAM,SAAS,UAAU,YAAY,GAAG;AACxC,UAAM,YAAY,UAAU,IAAI,UAAU,MAAM,GAAG,MAAM,IAAI;AAE7D,UAAM,SAAS;AAAA,MACb;AAAA,MACA,CAAC,SAAS,UAAU,QAAQ,IAAI,GAAG;AAAA,MACnC,qBAAqB,SAAS;AAAA,IAChC;AACA,gBAAY,KAAK,EAAE,YAAY,OAAO,CAAC;AAAA,EACzC;AAGA,QAAM,gBAAgB,YAAY,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI;AAChE,MAAI,kBAAkB,aAAa,GAAG;AACpC,UAAM,cAAcA,MAAK,QAAQ,SAAS,MAAM,gBAAgB;AAChE,QAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,YAAM,gBAAgB,aAAa,0BAA0B,CAAC;AAAA,IAChE;AAAA,EACF;AAGA,QAAM,QAAQ;AAAA,IACZ,YAAY,IAAI,CAAC,EAAE,YAAY,OAAO,MAAM,gBAAgB,YAAY,MAAM,CAAC;AAAA,EACjF;AACF;;;AThFO,SAAS,oBAAoB,YAAoB,MAAc,SAAyB;AAC7F,MAAI,MAAM,WAAW,QAAQ,OAAO,GAAG;AAEvC,MAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,UAAM,IAAI,MAAM,CAAC;AAAA,EACnB;AAEA,QAAM,MAAM,IAAI,YAAY,GAAG;AAC/B,QAAM,SAAS,OAAO,IAAI,IAAI,MAAM,GAAG,GAAG,IAAI;AAC9C,SAAOC,MAAK,QAAQ,SAAS,MAAM,QAAQ,QAAQ;AACrD;AAmBO,SAAS,qBAAqB,UAAkB,MAAc,SAAyB;AAC5F,MAAI,MAAM,SAAS,QAAQ,OAAO,GAAG;AAErC,MAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,UAAM,IAAI,MAAM,CAAC;AAAA,EACnB,WAES,IAAI,WAAW,GAAG,IAAI,GAAG,GAAG;AACnC,UAAM,IAAI,MAAM,KAAK,SAAS,CAAC;AAAA,EACjC;AAEA,QAAM,MAAM,IAAI,YAAY,GAAG;AAC/B,QAAM,SAAS,OAAO,IAAI,IAAI,MAAM,GAAG,GAAG,IAAI;AAC9C,SAAOA,MAAK,QAAQ,SAAS,MAAM,QAAQ,QAAQ;AACrD;AAcO,SAAS,qBAAqB,QAAwB;AAC3D,MAAI,CAAC,OAAQ,QAAO,KAAK,gBAAgB;AACzC,QAAM,QAAQ,OAAO,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE;AAChD,SAAO,GAAG,MAAM,OAAO,KAAK,CAAC,GAAG,gBAAgB;AAClD;AAgCO,SAAS,yBACd,SACA,aACA,mBACQ;AACR,QAAM,QAAkB,CAAC,0BAA0B;AAKnD,QAAM,wBAAkC,CAAC;AACzC,QAAM,iBAAiB,oBAAI,IAAY;AAGvC,QAAM,eAAyB,CAAC;AAChC,aAAW,UAAU,SAAS;AAC5B,UAAM,EAAE,YAAY,SAAS,IAAI;AACjC,QAAI,CAAC,UAAU;AAEb;AAAA,IACF;AAKA,UAAM,SAAS,OAAO,UAAU,oBAAoB,KAAK,UAAU;AAGnE,UAAM,EAAE,uBAAuB,OAAO,iBAAiB,IAAI;AAAA,MACzD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,eAAW,EAAE,MAAM,YAAY,KAAK,OAAO;AACzC,UAAI,eAAe,IAAI,IAAI,EAAG;AAC9B,qBAAe,IAAI,IAAI;AACvB,4BAAsB,KAAK,WAAW;AAAA,IACxC;AAEA,iBAAa,KAAK,CAAC,MAAM,UAAU,IAAI,kBAAkB,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,EACzE;AAEA,MAAI,sBAAsB,SAAS,GAAG;AACpC,UAAM,KAAK,GAAG,qBAAqB;AACnC,UAAM,KAAK,EAAE;AAAA,EACf;AAKA,QAAM,gBAAgB,CAAC,GAAG,uBAAuB,GAAG,YAAY,EAAE,KAAK,IAAI;AAC3E,MAAI,qBAAqB,kBAAkB,aAAa,GAAG;AACzD,UAAM;AAAA,MACJ,sEAAsE,iBAAiB;AAAA,IACzF;AAAA,EACF;AACA,QAAM,KAAK,EAAE;AAEb,QAAM,KAAK,GAAG,YAAY;AAE1B,SAAO,MAAM,KAAK,IAAI,EAAE,QAAQ,QAAQ,IAAI;AAC9C;AA0BA,eAAsB,oBACpB,QACA,SACA,MACA,UACe;AACf,MAAI,OAAO,WAAW,MAAM,UAAU,UAAU,OAAO,EAAG;AAE1D,QAAM,EAAE,SAAS,gBAAgB,IAAI,0BAA0B,QAAQ,SAAS,QAAQ;AAExF,QAAM,qBAAqB,SAAS;AAAA,IAClC,oBAAoB;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;;;AW7NA,OAAOC,YAAU;AACjB,OAAOC,UAAQ;;;ACDf,OAAOC,YAAU;AACjB,OAAOC,SAAQ;AACf,OAAOC,SAAQ;;;ACDf,OAAOC,SAAQ;AACf,OAAOC,YAAU;AACjB,OAAOC,SAAQ;;;ACKR,SAAS,aAAa,WAAmB,QAAuC;AACrF,QAAM,aAAuB,CAAC;AAE9B,aAAW,CAAC,SAAS,OAAO,KAAK,OAAO,QAAQ,OAAO,KAAK,GAAG;AAC7D,UAAM,gBAAgB,QAAQ,QAAQ,GAAG;AAEzC,QAAI,kBAAkB,IAAI;AAExB,UAAI,cAAc,SAAS;AACzB,mBAAW,KAAK,GAAG,OAAO;AAAA,MAC5B;AACA;AAAA,IACF;AAGA,UAAM,SAAS,QAAQ,MAAM,GAAG,aAAa;AAC7C,UAAM,SAAS,QAAQ,MAAM,gBAAgB,CAAC;AAE9C,QACE,UAAU,WAAW,MAAM,KAC3B,UAAU,SAAS,MAAM,KACzB,UAAU,UAAU,OAAO,SAAS,OAAO,QAC3C;AACA,YAAM,WAAW,UAAU,MAAM,OAAO,QAAQ,UAAU,SAAS,OAAO,MAAM;AAChF,iBAAW,UAAU,SAAS;AAC5B,mBAAW,KAAK,OAAO,QAAQ,KAAK,QAAQ,CAAC;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;;;ACvCA,OAAOC,SAAQ;AACf,OAAOC,WAAU;AACjB,OAAOC,SAAQ;AA4Bf,IAAM,gBAAgB,oBAAI,IAAgC;AAEnD,SAAS,aAAa,SAA6C;AACxE,QAAM,eAAeD,MAAK,QAAQ,SAAS,eAAe;AAC1D,MAAI,CAACC,IAAG,WAAW,YAAY,EAAG,QAAO;AAGzC,MAAI;AACJ,MAAI;AACF,cAAUA,IAAG,SAAS,YAAY,EAAE;AAAA,EACtC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,SAAS,cAAc,IAAI,YAAY;AAC7C,MAAI,UAAU,OAAO,YAAY,SAAS;AACxC,WAAO,OAAO;AAAA,EAChB;AAEA,QAAM,aAAaF,IAAG,eAAe,cAAcA,IAAG,IAAI,QAAQ;AAClE,MAAI,WAAW,SAAS,CAAC,WAAW,OAAQ,QAAO;AAGnD,QAAM,SAASA,IAAG,2BAA2B,WAAW,QAAQA,IAAG,KAAK,OAAO;AAG/E,QAAM,UAAU,OAAO,QAAQ,WAAW;AAE1C,QAAM,WAAW,OAAO,QAAQ;AAChC,QAAM,SAAqC,YACtC,MAAM;AAEL,UAAM,QAAkC,CAAC;AACzC,eAAW,CAAC,SAAS,OAAO,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACzD,YAAM,OAAO,IAAI,QAAQ,IAAI,CAAC,MAAMC,MAAK,QAAQ,SAAS,CAAC,CAAC;AAAA,IAC9D;AACA,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B,GAAG,IACH;AAEJ,gBAAc,IAAI,cAAc,EAAE,SAAS,OAAO,CAAC;AACnD,SAAO;AACT;;;AFzDA,SAAS,iBAAiB,YAAoB,UAA0B;AACtE,QAAM,cAAcE,OAAK,QAAQ,QAAQ;AACzC,MAAI,MAAMA,OAAK,SAAS,aAAa,UAAU;AAC/C,QAAM,IAAI,MAAMA,OAAK,GAAG,EAAE,KAAK,GAAG;AAClC,MAAI,CAAC,IAAI,WAAW,GAAG,EAAG,OAAM,OAAO;AACvC,SAAO,gBAAgB,GAAG;AAC5B;AAeA,SAAS,yBAAyB,YAAoB,SAAyB;AAC7E,QAAM,YAAY,WAAWA,OAAK,QAAQ,SAAS,OAAO,CAAC;AAC3D,QAAM,aAAa,WAAW,UAAU;AACxC,MAAI,MAAMA,OAAK,SAAS,WAAW,UAAU;AAC7C,QAAM,IAAI,MAAMA,OAAK,GAAG,EAAE,KAAK,GAAG;AAClC,MAAI,CAAC,IAAI,WAAW,GAAG,EAAG,OAAM,OAAO;AACvC,SAAO,gBAAgB,GAAG;AAC5B;AAcA,SAAS,yBAAyB,UAAkB,SAAiB,aAA6B;AAGhG,QAAM,cAAcA,OACjB,SAAS,WAAWA,OAAK,QAAQ,OAAO,CAAC,GAAG,WAAW,QAAQ,CAAC,EAChE,MAAMA,OAAK,GAAG,EACd,KAAK,GAAG;AACX,QAAM,kBAAkBA,OAAK,MAAM,QAAQ,gBAAgB,WAAW,CAAC;AACvE,MAAI,oBAAoB,IAAK,QAAO;AACpC,QAAM,SAAS,YAAY,QAAQ,SAAS,EAAE;AAC9C,MAAI,MAAMA,OAAK,MAAM,SAAS,iBAAiB,MAAM;AACrD,MAAI,CAAC,IAAI,WAAW,GAAG,EAAG,OAAM,OAAO;AACvC,SAAO;AACT;AAiBA,IAAM,YAAY,CAAC,OAAO,QAAQ,MAAM;AAKxC,IAAM,cAAc,CAAC,OAAO,QAAQ,OAAO,QAAQ,QAAQ,MAAM;AAKjE,IAAM,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAgBO,SAAS,yBAAyB,UAAkB,WAAkC;AAC3F,QAAM,cAAcA,OAAK,QAAQ,QAAQ;AACzC,QAAM,OAAOA,OAAK,QAAQ,aAAa,SAAS;AAGhD,MAAI,UAAU,KAAK,CAAC,QAAQ,UAAU,SAAS,GAAG,CAAC,GAAG;AACpD,QAAIC,IAAG,WAAW,IAAI,EAAG,QAAO;AAEhC,QAAI,KAAK,SAAS,KAAK,GAAG;AACxB,iBAAW,OAAO,CAAC,OAAO,QAAQ,MAAM,GAAG;AACzC,cAAM,OAAO,KAAK,MAAM,GAAG,EAAE,IAAI;AACjC,YAAIA,IAAG,WAAW,IAAI,EAAG,QAAO;AAAA,MAClC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAGA,MAAI,kBAAkB,KAAK,SAAS,GAAG;AACrC,WAAOA,IAAG,WAAW,IAAI,IAAI,OAAO;AAAA,EACtC;AAGA,aAAW,OAAO,aAAa;AAC7B,UAAM,OAAO,OAAO;AACpB,QAAIA,IAAG,WAAW,IAAI,EAAG,QAAO;AAAA,EAClC;AAGA,aAAW,YAAY,YAAY;AACjC,UAAM,OAAO,OAAO;AACpB,QAAIA,IAAG,WAAW,IAAI,EAAG,QAAO;AAAA,EAClC;AAEA,SAAO;AACT;AAuCO,SAAS,kBACd,QACA,SACQ;AAIR,QAAM,YAAY,SAAS,UAAU,WAAWD,OAAK,QAAQ,QAAQ,SAAS,OAAO,CAAC,IAAI;AAG1F,QAAM,qBAAqB,CAAC,cAAqC;AAC/D,eAAW,OAAO,aAAa;AAC7B,YAAM,OAAO,YAAY;AACzB,UAAIC,IAAG,WAAW,IAAI,EAAG,QAAO;AAAA,IAClC;AACA,eAAW,YAAY,YAAY;AACjC,YAAM,OAAO,YAAY;AACzB,UAAIA,IAAG,WAAW,IAAI,EAAG,QAAO;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,OAAO;AACX,YAAM,OAAO,EAAE,QAAQ,6BAA6B,GAAG,CAAC,SAAS;AAC/D,YAAI;AACJ,YAAI;AACF,mBAASA,IAAG,aAAa,KAAK,MAAM,MAAM;AAAA,QAC5C,QAAQ;AACN,iBAAO;AAAA,QACT;AACA,cAAM,WAAW,KAAK;AAEtB,cAAM,wBAAwB,YAAY,CAAC,YAAY,UAAU,SAAS,IAAI;AAI9E,cAAM,aAAaC,IAAG,iBAAiB,UAAU,QAAQA,IAAG,aAAa,QAAQ,KAAK;AACtF,cAAM,WAA+B,CAAC;AACtC,cAAM,QAAQ,CAAC,SAAwB;AAErC,cAAIA,IAAG,oBAAoB,IAAI,GAAG;AAChC,gBAAI,CAAC,KAAK,cAAc,cAAc,KAAK,iBAAiB;AAC1D,kBAAIA,IAAG,gBAAgB,KAAK,eAAe,EAAG,UAAS,KAAK,KAAK,eAAe;AAAA,YAClF;AAAA,UACF,WAAWA,IAAG,oBAAoB,IAAI,GAAG;AACvC,gBAAI,CAAC,KAAK,cAAc,KAAK,iBAAiB;AAC5C,kBAAIA,IAAG,gBAAgB,KAAK,eAAe,EAAG,UAAS,KAAK,KAAK,eAAe;AAAA,YAClF;AAAA,UACF,WACEA,IAAG,iBAAiB,IAAI,KACxB,KAAK,WAAW,SAASA,IAAG,WAAW,iBACvC,KAAK,UAAU,CAAC,KAChBA,IAAG,gBAAgB,KAAK,UAAU,CAAC,CAAC,GACpC;AACA,qBAAS,KAAK,KAAK,UAAU,CAAC,CAAC;AAAA,UACjC;AACA,UAAAA,IAAG,aAAa,MAAM,KAAK;AAAA,QAC7B;AACA,QAAAA,IAAG,aAAa,YAAY,KAAK;AAEjC,cAAM,eAA+D,CAAC;AACtE,cAAM,eAAmC,CAAC;AAE1C,mBAAW,OAAO,UAAU;AAC1B,gBAAM,YAAY,IAAI;AAEtB,cACE,UAAU,WAAW,GAAG,KACxB,UAAU,WAAW,OAAO,KAC5B,UAAU,WAAW,OAAO,GAC5B;AACA;AAAA,UACF;AAGA,cAAI,UAAU,WAAW,IAAI,KAAK,UAAU,WAAW,KAAK,GAAG;AAC7D,kBAAM,WAAW,yBAAyB,UAAU,SAAS;AAC7D,gBAAI,CAAC,UAAU;AACb,2BAAa,KAAK,GAAG;AACrB;AAAA,YACF;AAEA,gBAAI,UAAU,KAAK,CAAC,QAAQ,UAAU,SAAS,GAAG,CAAC,EAAG;AAEtD,gBAAI;AAGJ,gBAAI,aAAa,yBAAyB,YAAY,UAAU,SAAS,GAAG;AAC1E,yBAAW;AAAA,gBACT;AAAA,gBACA,QAAS;AAAA,gBACT,yBAAyB,UAAU,QAAS,OAAQ;AAAA,cACtD;AAAA,YACF,OAAO;AACL,yBAAW,iBAAiB,UAAU,QAAQ;AAAA,YAChD;AACA,kBAAM,QAAQ,OAAO,IAAI,SAAS,UAAU,CAAC;AAC7C,yBAAa,KAAK;AAAA,cAChB,OAAO,IAAI,SAAS,UAAU;AAAA,cAC9B,KAAK,IAAI,OAAO;AAAA,cAChB,MAAM,QAAQ,WAAW;AAAA,YAC3B,CAAC;AACD;AAAA,UACF;AAGA,qBAAW,aAAa,aAAa,WAAW,MAAM,GAAG;AACvD,kBAAM,OAAO,mBAAmB,SAAS;AACzC,gBAAI,CAAC,KAAM;AAEX,gBAAI;AACJ,gBAAI,aAAa,yBAAyB,YAAY,MAAM,SAAS,GAAG;AACtE,yBAAW;AAAA,gBACT;AAAA,gBACA,QAAS;AAAA,gBACT,yBAAyB,MAAM,QAAS,OAAQ;AAAA,cAClD;AAAA,YACF,OAAO;AACL,yBAAW,iBAAiB,MAAM,QAAQ;AAAA,YAC5C;AACA,kBAAM,QAAQ,OAAO,IAAI,SAAS,UAAU,CAAC;AAC7C,yBAAa,KAAK;AAAA,cAChB,OAAO,IAAI,SAAS,UAAU;AAAA,cAC9B,KAAK,IAAI,OAAO;AAAA,cAChB,MAAM,QAAQ,WAAW;AAAA,YAC3B,CAAC;AACD;AAAA,UACF;AAAA,QACF;AAEA,YAAI,aAAa,SAAS,GAAG;AAC3B,gBAAM,aAAa,WAAW,cAAc;AAC5C,iBAAO;AAAA,YACL,QAAQ,aAAa,IAAI,CAAC,QAAQ;AAChC,oBAAM,EAAE,MAAM,UAAU,IAAI,WAAW;AAAA,gBACrC,IAAI,SAAS,UAAU;AAAA,cACzB;AACA,oBAAM,UACJ,OAAO,IAAI,WAAW,SAAS,WAAW,OAAO,CAAC,IAAI,WAAW,OAAO;AAC1E,qBAAO;AAAA,gBACL,MAAM,2DAAc,IAAI,IAAI;AAAA,gBAC5B,UAAU;AAAA,kBACR,MAAM;AAAA,kBACN,MAAM,OAAO;AAAA,kBACb,QAAQ;AAAA,kBACR,UAAU,OAAO,MAAM,WAAW,IAAI,GAAG,OAAO,EAAE,QAAQ,UAAU,EAAE;AAAA,gBACxE;AAAA,cACF;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF;AAEA,YAAI,aAAa,WAAW,EAAG,QAAO;AAEtC,qBAAa,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC7C,YAAI,YAAY;AAChB,mBAAW,KAAK,cAAc;AAC5B,sBAAY,UAAU,MAAM,GAAG,EAAE,KAAK,IAAI,EAAE,OAAO,UAAU,MAAM,EAAE,GAAG;AAAA,QAC1E;AACA,eAAO,EAAE,UAAU,WAAW,QAAQ,UAAU;AAAA,MAClD,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAYO,SAAS,kBAAkB,SAA2B;AAC3D,QAAM,WAAW,aAAa,OAAO;AACrC,SAAO,CAAC,kBAAkB,YAAY,EAAE,SAAS,KAAK,OAAO,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;AACjF;;;AD9WA,IAAM,gBAAgB,CAAC,gBAAgB,oBAAoB,aAAa,GAAG,OAAO,eAAe;AA8CjG,eAAsB,mBACpB,SACwB;AACxB,QAAM,EAAE,SAAS,MAAM,OAAO,WAAW,UAAU,YAAY,YAAY,IAAI;AAG/E,QAAM,cACJ,SACC,MAAMC,IAAG,CAAC,GAAG,OAAO,UAAU,GAAG;AAAA,IAChC,KAAK;AAAA,IACL,WAAW;AAAA,IACX,UAAU;AAAA,IACV,QAAQ;AAAA,EACV,CAAC;AAEH,MAAI,YAAY,WAAW,GAAG;AAC5B,WAAO,EAAE,eAAe,CAAC,EAAE;AAAA,EAC7B;AAGA,QAAM,UAAUC,OAAK,QAAQ,SAAS,IAAI;AAC1C,QAAMC,IAAG,SAAS,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AAGpD,QAAM,UAAU,kBAAkB,OAAO;AAIzC,QAAM,UAAU,MAAM,OAAO,SAAS;AACtC,QAAM,UAAUD,OAAK,QAAQ,SAAS,OAAO;AAC7C,QAAM,SAAS,MAAM,QAAQ,MAAM;AAAA,IACjC;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA;AAAA,IAEA,GAAI,aACA,EAAE,QAAQ,EAAE,wBAAwB,eAAe,GAAG,cAAc,KAAK,IACzE,CAAC;AAAA,IACL;AAAA;AAAA,IAEA,GAAI,cAAc,EAAE,OAAO,MAAe,IAAI,CAAC;AAAA,EACjD,CAAC;AAID,MAAI,eAAe,OAAO,aAAa;AACrC,UAAM,QAAQ;AAAA,MACZ,OAAO,YAAY,IAAI,OAAO,SAAS;AACrC,cAAMC,IAAG,SAAS,MAAMD,OAAK,QAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACpE,cAAM,MAAM,GAAG,KAAK,IAAI,QAAQ,QAAQ,GAAG,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAClF,cAAMC,IAAG,SAAS,UAAU,KAAK,KAAK,QAAQ;AAC9C,cAAMA,IAAG,SAAS,OAAO,KAAK,KAAK,IAAI;AAAA,MACzC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,EAAE,eAAe,YAAY;AACtC;;;AIhFA,eAAsB,iBAAiB,SAAoD;AACzF,SAAO,mBAAmB,EAAE,GAAG,SAAS,aAAa,KAAK,CAAC;AAC7D;;;ACtCA,OAAOC,YAAU;AACjB,OAAOC,UAAQ;AASf,IAAM,UAAU;AAQhB,SAAS,wBAAwB,QAA0B;AACzD,QAAM,aAAuB,CAAC;AAC9B,MAAI;AACJ,UAAQ,YAAY;AACpB,UAAQ,QAAQ,QAAQ,KAAK,MAAM,OAAO,MAAM;AAC9C,eAAW,KAAK,MAAM,CAAC,CAAE;AAAA,EAC3B;AACA,SAAO;AACT;AAQA,SAAS,wBAAwB,KAAa,WAAkC;AAC9E,SAAO,yBAAyBC,OAAK,KAAK,KAAK,oBAAoB,GAAG,SAAS;AACjF;AAwBA,eAAsB,uBACpB,YACA,SAC4D;AAE5D,QAAM,YAAY,WAAWA,OAAK,QAAQ,SAAS,KAAK,CAAC;AACzD,QAAM,WAAW,aAAa,OAAO;AACrC,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,cAAc,oBAAI,IAAY;AACpC,QAAM,eAAe,oBAAI,IAAY;AAErC,iBAAe,QAAQ,UAAiC;AACtD,QAAI,QAAQ,IAAI,QAAQ,EAAG;AAC3B,YAAQ,IAAI,QAAQ;AAEpB,QAAI;AACJ,QAAI;AACF,eAAS,MAAMC,KAAG,SAAS,SAAS,UAAU,MAAM;AAAA,IACtD,QAAQ;AACN;AAAA,IACF;AAEA,eAAW,aAAa,wBAAwB,MAAM,GAAG;AAEvD,UAAI,kBAAkB,KAAK,SAAS,EAAG;AAEvC,UAAI;AACJ,UAAI,UAAU,WAAW,IAAI,KAAK,UAAU,WAAW,KAAK,GAAG;AAC7D,mBAAW,yBAAyB,UAAU,SAAS;AAAA,MACzD,WAAW,UAAU;AAEnB,mBAAW;AACX,mBAAW,aAAa,aAAa,WAAW,QAAQ,GAAG;AACzD,gBAAM,SAAS,wBAAwB,SAAS,SAAS;AACzD,cAAI,QAAQ;AACV,uBAAW;AACX;AAAA,UACF;AAAA,QACF;AAAA,MACF,OAAO;AACL,mBAAW;AAAA,MACb;AAEA,UAAI,CAAC,SAAU;AAKf,UAAI,CAAC,YAAY,WAAW,QAAQ,GAAG,WAAWD,OAAK,QAAQ,OAAO,CAAC,CAAC,EAAG;AAE3E,UAAI,YAAY,WAAW,QAAQ,GAAG,SAAS,GAAG;AAChD,oBAAY,IAAI,QAAQ;AAAA,MAC1B,OAAO;AACL,qBAAa,IAAI,QAAQ;AAAA,MAC3B;AACA,YAAM,QAAQ,QAAQ;AAAA,IACxB;AAAA,EACF;AAEA,aAAW,SAAS,YAAY;AAC9B,UAAM,QAAQ,KAAK;AAAA,EACrB;AAEA,SAAO;AAAA,IACL,aAAa,MAAM,KAAK,WAAW;AAAA,IACnC,cAAc,MAAM,KAAK,YAAY;AAAA,EACvC;AACF;;;ANjFO,SAAS,eAAe,eAAuB,gBAAiC;AACrF,MAAI;AACF,UAAM,UAAUE,KAAG,SAAS,aAAa;AACzC,UAAM,WAAWA,KAAG,SAAS,cAAc;AAC3C,WAAO,SAAS,WAAW,QAAQ;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AA4BA,SAAS,yBAA2C;AAClD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS;AAAA,IACT,eAAe,oBAAI,IAAI;AAAA,IACvB,kBAAkB,oBAAI,IAAI;AAAA,IAC1B,sBAAsB,oBAAI,IAAI;AAAA,IAC9B,2BAA2B,oBAAI,IAAI;AAAA,EACrC;AACF;AAEA,IAAM,QAA0B,uBAAuB;AAwDvD,eAAsB,eACpB,eACA,SACA,MACkB;AAGlB,QAAMC,YAAW,MAAM,qBAAqB,IAAI,aAAa;AAC7D,MAAIA,WAAU;AACZ,UAAMA;AACN,WAAO;AAAA,EACT;AAGA,MAAI,MAAM,cAAc,IAAI,aAAa,GAAG;AAC1C,WAAO;AAAA,EACT;AAGA,MAAI,CAACC,KAAG,WAAW,aAAa,GAAG;AACjC,WAAO;AAAA,EACT;AAGA,QAAM,cAAc,4BAA4B,eAAe,SAAS,IAAI;AAC5E,MAAI,eAAe,eAAe,eAAe,WAAW,GAAG;AAC7D,UAAM,cAAc,IAAI,aAAa;AACrC,WAAO;AAAA,EACT;AAGA,QAAM,kBAAkB,YAAY;AAElC,UAAM,EAAE,YAAY,IAAI,MAAM,uBAAuB,CAAC,aAAa,GAAG,OAAO;AAC7E,UAAM,QAAQ,CAAC,aAAa;AAC5B,eAAW,OAAO,aAAa;AAC7B,UAAI,MAAM,cAAc,IAAI,GAAG,EAAG;AAClC,YAAM,aAAa,4BAA4B,KAAK,SAAS,IAAI;AACjE,UAAI,cAAc,eAAe,KAAK,UAAU,EAAG;AACnD,YAAM,KAAK,GAAG;AAAA,IAChB;AAEA,UAAM,iBAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,IACZ,CAAC;AACD,eAAW,QAAQ,OAAO;AACxB,YAAM,cAAc,IAAI,IAAI;AAAA,IAC9B;AACA,UAAM,cAAc,IAAI,aAAa;AAAA,EACvC,GAAG;AAEH,QAAM,qBAAqB,IAAI,eAAe,cAAc;AAC5D,MAAI;AACF,UAAM;AACN,WAAO;AAAA,EACT,UAAE;AACA,UAAM,qBAAqB,OAAO,aAAa;AAAA,EACjD;AACF;AAaA,eAAsB,0BACpB,iBACA,SACe;AACf,MAAI,CAAC,qBAAqB,KAAK,gBAAgB,WAAW,EAAG;AAC7D,QAAM,OAAO,WAAW;AACxB,MAAI,CAAC,KAAM;AAEX,aAAW,UAAU,iBAAiB;AACpC,UAAM,aAAa,qBAAqB,QAAQ,SAAS,IAAI;AAC7D,QAAI;AACF,YAAM,eAAe,YAAY,SAAS,IAAI;AAAA,IAChD,SAAS,KAAK;AAGZ,cAAQ,MAAM,+CAA+C,UAAU,KAAK,GAAG;AAAA,IACjF;AAAA,EACF;AACF;AAOA,SAAS,4BACP,eACA,SACA,MACe;AACf,QAAM,MAAMC,OAAK,SAAS,SAAS,aAAa,EAAE,QAAQ,OAAO,GAAG;AACpE,MAAI,CAAC,IAAI,WAAW,MAAM,EAAG,QAAO;AACpC,QAAM,gBAAgB,IAAI,MAAM,CAAC;AACjC,QAAM,QAAQ,cAAc,QAAQ,SAAS,KAAK;AAClD,SAAOA,OAAK,QAAQ,SAAS,MAAM,KAAK;AAC1C;AA0CA,eAAsB,sBACpB,YACA,eACA,QACA,SACA,MACA,UACkB;AAGlB,QAAMC,YAAW,MAAM,0BAA0B,IAAI,UAAU;AAC/D,MAAIA,WAAU;AACZ,UAAMA;AACN,WAAO;AAAA,EACT;AAGA,MAAI,MAAM,iBAAiB,IAAI,UAAU,GAAG;AAC1C,WAAO;AAAA,EACT;AAGA,QAAM,cAAcC,OAAK,QAAQ,SAAS,aAAa;AACvD,QAAM,gBAAgB,qBAAqB,aAAa,SAAS,IAAI;AACrE,MAAI,CAACC,KAAG,WAAW,aAAa,GAAG;AACjC,WAAO;AAAA,EACT;AAGA,MAAI,eAAe,eAAe,UAAU,GAAG;AAC7C,UAAM,iBAAiB,IAAI,UAAU;AACrC,WAAO;AAAA,EACT;AAIA,QAAM,aAAa,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,aAAa;AACpE,QAAM,gBAAgB,YAAY,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,aAAa,aAAa;AAChF,MAAI,WAAW,WAAW,KAAK,aAAa,WAAW,GAAG;AACxD,WAAO;AAAA,EACT;AAIA,QAAM,gBAAgBD,OAAK,SAAS,SAAS,aAAa,EAAE,QAAQ,OAAO,GAAG;AAC9E,QAAM,eAAe,WAAW,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,UAAU,cAAc,EAAE;AAC9E,QAAM,iBAAiB,aAAa,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,UAAU,cAAc,EAAE;AAGlF,QAAM,mBAAmB,YAAY;AAEnC,UAAM,oBAAoB,cAAc,SAAS,MAAM,cAAc;AACrE,UAAM,iBAAiB,IAAI,UAAU;AAAA,EACvC,GAAG;AAEH,QAAM,0BAA0B,IAAI,YAAY,eAAe;AAC/D,MAAI;AACF,UAAM;AACN,WAAO;AAAA,EACT,UAAE;AACA,UAAM,0BAA0B,OAAO,UAAU;AAAA,EACnD;AACF;AAuCA,IAAM,kBAAkB,oBAAI,IAAoB;AAEzC,SAAS,qBAAqB,aAAqB,SAAiB,MAAsB;AAC/F,QAAM,SAAS,gBAAgB,IAAI,WAAW;AAC9C,MAAI,OAAQ,QAAO;AAEnB,QAAM,MAAME,OAAK,SAAS,SAAS,WAAW,EAAE,QAAQ,OAAO,GAAG;AAElE,MAAI,iBAAiB;AACrB,MAAI,eAAe,WAAW,GAAG,IAAI,GAAG,GAAG;AACzC,qBAAiB,eAAe,MAAM,KAAK,SAAS,CAAC;AAAA,EACvD;AAEA,QAAM,SAAS,OAAO,cAAc;AAEpC,QAAM,QAAQ,OAAO,QAAQ,SAAS,KAAK;AAC3C,QAAM,QAAQA,OAAK,QAAQ,SAAS,KAAK;AACzC,MAAI;AACJ,MAAIC,KAAG,WAAW,KAAK,GAAG;AACxB,aAAS;AAAA,EACX,OAAO;AAEL,aAASD,OAAK,QAAQ,SAAS,MAAM;AAAA,EACvC;AACA,kBAAgB,IAAI,aAAa,MAAM;AACvC,SAAO;AACT;AAgBO,SAAS,uBAAgC;AAC9C,SAAO,MAAM;AACf;AAWO,SAAS,aAAiC;AAC/C,SAAO,MAAM;AACf;;;AOnaA,IAAM,cAAc,oBAAI,IAA0B;AAK3C,SAAS,wBAA8B;AAC5C,cAAY,MAAM;AACpB;AAQA,eAAe,iBAAiB,YAA2C;AACzE,MAAI,MAAM,YAAY,IAAI,UAAU;AACpC,MAAI,CAAC,KAAK;AAGR,UAAO,MAAM,oBAAoB,YAAY,qBAAqB,CAAC;AACnE,gBAAY,IAAI,YAAY,GAAG;AAAA,EACjC;AACA,SAAO;AACT;AA0BA,eAAsB,cACpB,YACA,QACA,WACA,OAC2B;AAC3B,QAAM,aAAa,cAAc,QAAQ,SAAS;AAClD,QAAM,YAAY,GAAG,UAAU;AAE/B,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,iBAAiB,UAAU;AAAA,EACzC,SAAS,KAAK;AACZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI,cAAc,gDAAkB,UAAU,KAAK,MAAM,EAAE;AAAA,EACnE;AAEA,QAAM,SAAS,IAAI,SAAS;AAG5B,MAAI,WAAW,UAAa,WAAW,MAAM;AAC3C,WAAO,EAAE,OAAO,MAAM,QAAQ,CAAC,GAAG,MAAM,MAAM;AAAA,EAChD;AAGA,MACE,OAAO,WAAW,YAClB,OAAQ,OAAmC,cAAc,YACzD;AACA,UAAM,IAAI,cAAc,qDAA4B,UAAU,IAAI,UAAU,EAAE;AAAA,EAChF;AAKA,QAAM,YAAY;AAClB,QAAM,SAAS,UAAU,UAAU,KAAK;AAExC,MAAI,OAAO,SAAS;AAElB,WAAO,EAAE,OAAO,MAAM,QAAQ,CAAC,GAAG,MAAM,OAAO,KAAK;AAAA,EACtD;AAGA,QAAM,SAAS,aAAa,OAAO,KAAK;AACxC,SAAO,EAAE,OAAO,OAAO,QAAQ,MAAM,MAAM;AAC7C;AA0CA,SAAS,aAAa,OAAsD;AAC1E,SAAO,MAAM,OAAO,IAAI,CAAC,UAAU;AACjC,UAAM,OAAO,WAAW,KAAK;AAC7B,UAAME,SAAO,MAAM,KAAK,IAAI,MAAM,EAAE,KAAK,GAAG,KAAK;AACjD,WAAO;AAAA,MACL,MAAAA;AAAA,MACA;AAAA,MACA,UAAU,MAAM,YAAY,uBAAuB,MAAM,OAAO;AAAA,MAChE,UAAU,MAAM,YAAY,uBAAuB,MAAM,OAAO;AAAA,MAChE,SAAS,MAAM;AAAA,IACjB;AAAA,EACF,CAAC;AACH;AAeA,SAAS,WAAW,OAA0C;AAC5D,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AAGH,UAAI,MAAM,aAAa,eAAe,sBAAsB,KAAK,MAAM,OAAO,GAAG;AAC/E,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAKA,SAAS,uBAAuB,SAAyB;AAEvD,QAAM,QAAQ,QAAQ,MAAM,mBAAmB;AAC/C,SAAO,QAAQ,MAAM,CAAC,EAAG,YAAY,IAAI;AAC3C;AAKA,SAAS,uBAAuB,SAAyB;AACvD,QAAM,QAAQ,QAAQ,MAAM,mBAAmB;AAC/C,SAAO,QAAQ,MAAM,CAAC,EAAG,YAAY,IAAI;AAC3C;;;ACxPA;AAAA,EACE,gBAAgB;AAAA,OAIX;AACP,SAAS,sBAAsB,+BAA+B;AAC9D,SAAS,oBAAoB;AAC7B,SAAS,YAAAC,iBAAgB;AACzB,OAAOC,YAAU;;;ACqCjB,IAAM,iBAAiB,oBAAI,QAAwC;AACnE,IAAM,eAAe,oBAAI,QAAwC;AAGjE,SAAS,aAAa,QAAwC;AAC5D,MAAI,QAAQ,eAAe,IAAI,MAAM;AACrC,MAAI,MAAO,QAAO;AAElB,UAAQ,EAAE,QAAQ,oBAAI,IAAI,GAAG,qBAAqB,oBAAI,IAAI,GAAG,UAAU,CAAC,EAAE;AAC1E,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,WAAW;AACnB,YAAM,SAAS,KAAK;AAAA,QAClB;AAAA,QACA,UAAU,MAAM,QAAQ,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA,MACnD,CAAC;AAAA,IACH,OAAO;AACL,YAAM,OAAO,IAAI,GAAG,MAAM,MAAM,IAAI,MAAM,OAAO,IAAI,KAAK;AAC1D,UAAI,UAAU,MAAM,oBAAoB,IAAI,MAAM,OAAO;AACzD,UAAI,CAAC,SAAS;AACZ,kBAAU,oBAAI,IAAI;AAClB,cAAM,oBAAoB,IAAI,MAAM,SAAS,OAAO;AAAA,MACtD;AACA,cAAQ,IAAI,MAAM,MAAM;AAAA,IAC1B;AAAA,EACF;AAEA,iBAAe,IAAI,QAAQ,KAAK;AAChC,SAAO;AACT;AAGA,SAAS,WAAW,QAAwC;AAC1D,MAAI,QAAQ,aAAa,IAAI,MAAM;AACnC,MAAI,MAAO,QAAO;AAElB,UAAQ,EAAE,QAAQ,oBAAI,IAAI,GAAG,UAAU,CAAC,EAAE;AAC1C,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,WAAW;AACnB,YAAM,SAAS,KAAK,KAAK;AAAA,IAC3B,OAAO;AACL,YAAM,OAAO,IAAI,MAAM,SAAS,KAAK;AAAA,IACvC;AAAA,EACF;AAEA,eAAa,IAAI,QAAQ,KAAK;AAC9B,SAAO;AACT;AAcO,SAAS,WAAW,QAAuB,QAAgBC,QAAiC;AACjG,QAAM,QAAQ,aAAa,MAAM;AACjC,QAAM,QAAQ,OAAO,YAAY;AAEjC,QAAM,MAAM,cAAc,OAAO,OAAOA,MAAI;AAC5C,MAAI,IAAK,QAAO;AAEhB,MAAI,UAAU,QAAQ;AACpB,WAAO,cAAc,OAAO,OAAOA,MAAI;AAAA,EACzC;AAEA,SAAO;AACT;AAGA,SAAS,cAAc,OAAwB,QAAgBA,QAAiC;AAE9F,QAAM,YAAY,MAAM,OAAO,IAAI,GAAG,MAAM,IAAIA,MAAI,EAAE;AACtD,MAAI,WAAW;AACb,WAAO,EAAE,OAAO,WAAW,QAAQ,CAAC,EAAE;AAAA,EACxC;AAGA,MAAI,MAAM,SAAS,WAAW,GAAG;AAC/B,WAAO;AAAA,EACT;AAGA,QAAM,eAAeA,OAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AACnD,aAAW,SAAS,MAAM,UAAU;AAClC,UAAM,QAAQ,MAAM;AACpB,QAAI,MAAM,WAAW,QAAQ;AAC3B;AAAA,IACF;AACA,UAAM,SAAS,cAAc,MAAM,UAAU,cAAc,MAAM,YAAY,MAAM,UAAU;AAC7F,QAAI,WAAW,MAAM;AACnB,aAAO,EAAE,OAAO,OAAO;AAAA,IACzB;AAAA,EACF;AAEA,SAAO;AACT;AASO,SAAS,aAAa,UAA2BA,QAAmC;AACzF,QAAM,QAAQ,WAAW,QAAQ;AAEjC,QAAM,YAAY,MAAM,OAAO,IAAIA,MAAI;AACvC,MAAI,WAAW;AACb,WAAO,EAAE,OAAO,WAAW,QAAQ,CAAC,EAAE;AAAA,EACxC;AAEA,aAAW,SAAS,MAAM,UAAU;AAClC,UAAM,SAAS,iBAAiB,MAAM,SAASA,QAAM,MAAM,YAAY,MAAM,UAAU;AACvF,QAAI,WAAW,MAAM;AACnB,aAAO,EAAE,OAAO,OAAO;AAAA,IACzB;AAAA,EACF;AAEA,SAAO;AACT;AAQO,SAAS,mBAAmB,QAAuBA,QAAwB;AAChF,QAAM,QAAQ,aAAa,MAAM;AACjC,QAAM,UAAU,oBAAI,IAAY;AAGhC,QAAM,gBAAgB,MAAM,oBAAoB,IAAIA,MAAI;AACxD,MAAI,eAAe;AACjB,eAAW,UAAU,eAAe;AAClC,cAAQ,IAAI,MAAM;AAAA,IACpB;AAAA,EACF;AAGA,MAAI,MAAM,SAAS,SAAS,GAAG;AAC7B,UAAM,eAAeA,OAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AACnD,eAAW,SAAS,MAAM,UAAU;AAClC,YAAM,SAAS;AAAA,QACb,MAAM;AAAA,QACN;AAAA,QACA,MAAM,MAAM;AAAA,QACZ,MAAM,MAAM;AAAA,MACd;AACA,UAAI,WAAW,MAAM;AACnB,gBAAQ,IAAI,MAAM,MAAM,MAAM;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAIA,MAAI,QAAQ,IAAI,KAAK,GAAG;AACtB,YAAQ,IAAI,MAAM;AAAA,EACpB;AAEA,SAAO,MAAM,KAAK,OAAO;AAC3B;AAQO,SAAS,iBACd,SACAA,QACA,YACA,YAC+B;AAC/B,SAAO;AAAA,IACL,QAAQ,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA,IACjCA,OAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA,IAC9B;AAAA,IACA;AAAA,EACF;AACF;AAQA,SAAS,cACP,iBACA,cACA,YACA,YAC+B;AAE/B,MAAI,YAAY;AAGd,UAAM,mBAAmB,gBAAgB,SAAS;AAClD,QAAI,aAAa,UAAU,kBAAkB;AAC3C,aAAO;AAAA,IACT;AAEA,UAAMC,UAAiC,CAAC;AAExC,aAAS,IAAI,GAAG,IAAI,kBAAkB,KAAK;AAEzC,YAAM,aAAa,gBAAgB,CAAC;AACpC,YAAM,UAAU,aAAa,CAAC;AAE9B,UAAI,WAAW,WAAW,GAAG,GAAG;AAC9B,cAAM,YAAY,WAAW,MAAM,CAAC;AACpC,QAAAA,QAAO,SAAS,IAAI;AAAA,MACtB,WAAW,eAAe,SAAS;AACjC,eAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,gBAAgB,aAAa,MAAM,gBAAgB,EAAE,KAAK,GAAG;AAEnE,UAAM,oBAAoB,gBAAgB,gBAAgB,EAAG,MAAM,CAAC;AACpE,IAAAA,QAAO,iBAAiB,IAAI;AAE5B,QAAI,OAAO,KAAKA,OAAM,EAAE,WAAW,WAAW,QAAQ;AACpD,aAAO;AAAA,IACT;AAEA,WAAOA;AAAA,EACT;AAGA,MAAI,gBAAgB,WAAW,aAAa,QAAQ;AAClD,WAAO;AAAA,EACT;AAEA,QAAM,SAAiC,CAAC;AAExC,WAAS,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;AAE/C,UAAM,aAAa,gBAAgB,CAAC;AACpC,UAAM,UAAU,aAAa,CAAC;AAE9B,QAAI,WAAW,WAAW,GAAG,GAAG;AAE9B,YAAM,YAAY,WAAW,MAAM,CAAC;AACpC,aAAO,SAAS,IAAI;AAAA,IACtB,WAAW,eAAe,SAAS;AAEjC,aAAO;AAAA,IACT;AAAA,EACF;AAGA,MAAI,OAAO,KAAK,MAAM,EAAE,WAAW,WAAW,QAAQ;AACpD,WAAO;AAAA,EACT;AAEA,SAAO;AACT;;;AChTO,SAAS,cAAc,QAAiC,YAA6B;AAE1F,MAAI,cAAc,UAAU,OAAO,OAAO,UAAU,MAAM,aAAa;AACrE,WAAO,OAAO,UAAU;AAAA,EAC1B;AAGA,QAAM,gBAAgB,OAAO;AAC7B,MAAI,kBAAkB,QAAQ,OAAO,kBAAkB,UAAU;AAC/D,UAAM,QAAS,cAA0C,UAAU;AACnE,QAAI,UAAU,QAAW;AACvB,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;;;ACvBO,SAAS,oBACd,OACA,QACA,UACkD;AAClD,MAAI,OAAO,UAAU,YAAY;AAC/B,UAAM,IAAI;AAAA,MACR,iBAAiB,QAAQ,iDAAiD,MAAM,+BAClD,OAAO,KAAK;AAAA,IAC5C;AAAA,EACF;AACF;;;ACgBA,eAAsB,gBACpB,UACA,QACA,SACsB;AAOtB,MAAI,qBAAqB,KAAK,SAAS;AACrC,UAAM,OAAO,WAAW;AACxB,QAAI,MAAM;AACR,YAAM,aAAa,qBAAqB,UAAU,SAAS,IAAI;AAC/D,UAAI,YAAY;AACd,YAAI;AACF,gBAAM,eAAe,YAAY,SAAS,IAAI;AAAA,QAChD,SAAS,YAAY;AACnB,gBAAM,SAAS,sBAAsB,QAAQ,WAAW,UAAU,OAAO,UAAU;AACnF,gBAAM,IAAI,MAAM,mCAAmC,UAAU,MAAM,MAAM,IAAI;AAAA,YAC3E,OAAO;AAAA,UACT,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,oBAAoB,UAAU,qBAAqB,CAAC;AAAA,EACrE,SAAS,KAAc;AACrB,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI,MAAM,gCAAgC,QAAQ,MAAM,MAAM,IAAI,EAAE,OAAO,IAAI,CAAC;AAAA,EACxF;AAEA,QAAM,UAAU,cAAc,QAAQ,MAAM;AAC5C,sBAAoB,SAAS,QAAQ,QAAQ;AAE7C,SAAO,EAAE,SAAS,OAAO;AAC3B;;;ACrEO,SAAS,cAAc,MAA+B;AAC3D,MAAI;AACF,UAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,WAAO,EAAE,SAAS,MAAM,KAAK;AAAA,EAC/B,QAAQ;AACN,WAAO,EAAE,SAAS,OAAO,OAAO,oBAAoB;AAAA,EACtD;AACF;;;ACkBA,eAAsB,eAAe,SAA4C;AAC/E,QAAM,WAAW,MAAM,QAAQ,SAAS;AACxC,QAAM,SAA4C,CAAC;AACnD,QAAM,QAAwB,CAAC;AAE/B,aAAW,CAAC,KAAK,KAAK,KAAK,SAAS,QAAQ,GAAG;AAC7C,QAAI,iBAAiB,MAAM;AACzB,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,UAAU,MAAM;AAAA,QAChB,MAAM,MAAM;AAAA,QACZ,MAAM,MAAM;AAAA,QACZ,aAAa,MAAM,MAAM,YAAY;AAAA,MACvC,CAAC;AAAA,IACH,OAAO;AAEL,UAAI,OAAO,QAAQ;AACjB,cAAM,WAAW,OAAO,GAAG;AAC3B,YAAI,MAAM,QAAQ,QAAQ,GAAG;AAC3B,mBAAS,KAAK,KAAK;AAAA,QACrB,OAAO;AAEL,iBAAO,GAAG,IAAI,CAAC,UAAW,KAAK;AAAA,QACjC;AAAA,MACF,OAAO;AACL,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,MAAM;AACzB;;;ACdA,eAAsB,oBACpB,QACA,SACA,KACwB;AACxB,QAAM,YAAY,sBAAsB,MAAM;AAG9C,MAAI,cAAc,QAAQ;AACxB,WAAO,uBAAuB,OAAO;AAAA,EACvC;AAGA,SAAO,EAAE,OAAO,cAAc,IAAI,YAAY,EAAE;AAClD;AAWA,eAAe,uBAAuB,SAA0C;AAC9E,QAAM,cAAc,QAAQ,QAAQ,IAAI,cAAc,KAAK;AAG3D,MAAI,YAAY,SAAS,qBAAqB,GAAG;AAC/C,WAAO,EAAE,OAAO,MAAM,eAAe,OAAO,EAAE;AAAA,EAChD;AAGA,MAAI,YAAY,SAAS,mCAAmC,GAAG;AAC7D,UAAMC,QAAO,MAAM,QAAQ,KAAK;AAChC,QAAI,YAAYA,KAAI,EAAG,QAAO,EAAE,OAAO,KAAK;AAC5C,UAAM,SAAS,IAAI,gBAAgBA,KAAI;AACvC,UAAM,MAA8B,CAAC;AACrC,eAAW,CAAC,KAAK,KAAK,KAAK,QAAQ;AACjC,UAAI,GAAG,IAAI;AAAA,IACb;AACA,WAAO,EAAE,OAAO,KAAK,SAASA,MAAK;AAAA,EACrC;AAGA,QAAM,OAAO,MAAM,QAAQ,KAAK;AAGhC,MAAI,YAAY,IAAI,GAAG;AACrB,WAAO,EAAE,OAAO,KAAK;AAAA,EACvB;AAGA,QAAM,SAAS,cAAc,IAAI;AACjC,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI,gBAAgB,yDAAiB;AAAA,MACzC;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,UAAU;AAAA,QACV,UAAU;AAAA,QACV,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO,EAAE,OAAO,OAAO,MAAM,SAAS,KAAK;AAC7C;AAGA,SAAS,YAAY,MAAuB;AAC1C,SAAO,KAAK,WAAW,KAAK,CAAC,KAAK,KAAK,IAAI;AAC7C;AAkBA,eAAsB,0BAA0B,SAA0C;AACxF,SAAO,uBAAuB,OAAO;AACvC;;;ACvIA,SAAS,gBAAgB;AAezB,eAAsB,iBAAiB,UAAoB,KAAoC;AAE7F,MAAI,aAAa,SAAS;AAG1B,aAAW,CAAC,KAAK,KAAK,KAAK,SAAS,SAAS;AAC3C,QAAI,IAAI,YAAY,MAAM,cAAc;AAEtC,UAAI,aAAa,KAAK,KAAK;AAAA,IAC7B,OAAO;AACL,UAAI,UAAU,KAAK,KAAK;AAAA,IAC1B;AAAA,EACF;AAGA,QAAM,WAAW,0BAA0B,QAAQ;AACnD,MAAI,UAAU;AACZ,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACnD,UAAI,UAAU,KAAK,KAAK;AAAA,IAC1B;AAAA,EACF;AAGA,MAAI,SAAS,MAAM;AAIjB,QAAI,gBAAgB,QAAQ,GAAG;AAC7B,YAAM,SAAS,MAAM,iBAAiB,SAAS,IAAI;AACnD,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,YAAI,GAAG,SAAS,MAAM;AAEpB,cAAI,CAAC,IAAI,cAAe,KAAI,QAAQ;AAAA,QACtC,CAAC;AACD,YAAI,IAAI,WAAW;AACjB,kBAAQ;AACR;AAAA,QACF;AACA,YAAI,IAAI,QAAQ,MAAM,QAAQ,CAAC;AAAA,MACjC,CAAC;AACD;AAAA,IACF;AAEA,UAAM,aAAa,SAAS,QAAQ,SAAS,IAAa;AAC1D,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAE3C,iBAAW,GAAG,SAAS,MAAM;AAG7B,YAAM,QAAQ,MAAM;AAClB,mBAAW,QAAQ;AACnB,gBAAQ;AAAA,MACV;AACA,UAAI,GAAG,SAAS,KAAK;AACrB,UAAI,GAAG,SAAS,MAAM;AAEpB,YAAI,CAAC,IAAI,cAAe,OAAM;AAAA,MAChC,CAAC;AAED,UAAI,GAAG,UAAU,OAAO;AACxB,iBAAW,KAAK,GAAG;AAAA,IACrB,CAAC;AACD;AAAA,EACF;AAEA,MAAI,IAAI;AACV;AAQA,eAAe,iBAAiB,MAA2C;AACzE,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,SAAuB,CAAC;AAC9B,MAAI;AACF,eAAS;AACP,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,KAAM;AACV,aAAO,KAAK,KAAK;AAAA,IACnB;AAAA,EACF,UAAE;AACA,QAAI;AACF,aAAO,YAAY;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,MAAI,OAAO,WAAW,EAAG,QAAO,IAAI,WAAW,CAAC;AAChD,MAAI,OAAO,WAAW,EAAG,QAAO,OAAO,CAAC;AACxC,SAAO,OAAO,OAAO,MAAM;AAC7B;;;AC3FO,SAAS,YAAY,KAAsB,eAAe,OAAe;AAE9E,MAAI,cAAc;AAChB,UAAM,MAAM,IAAI,QAAQ,iBAAiB;AACzC,QAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,GAAG;AAC7C,YAAM,QAAQ,IAAI,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AACtC,UAAI,MAAO,QAAO;AAAA,IACpB;AAAA,EACF;AAGA,QAAM,SAAS,IAAI,QAAQ;AAC3B,MAAI,QAAQ;AAEV,QAAI,OAAO,WAAW,SAAS,GAAG;AAChC,aAAO,OAAO,MAAM,CAAC;AAAA,IACvB;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;;;AC3BO,SAAS,UAAU,MAAoB,OAAqB;AACjE,QAAM,WAAW,KAAK,QAAQ,MAAM,KAAK,KAAK,QAAQ,MAAM;AAC5D,MAAI,CAAC,UAAU;AACb,SAAK,QAAQ,MAAM,IAAI;AACvB;AAAA,EACF;AAEA,QAAM,WAAW,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC;AACtE,MAAI,CAAC,SAAS,SAAS,MAAM,YAAY,CAAC,GAAG;AAC3C,UAAM,MAAM,KAAK,QAAQ,MAAM,MAAM,SAAY,SAAS;AAC1D,SAAK,QAAQ,GAAG,IAAI,GAAG,QAAQ,KAAK,KAAK;AAAA,EAC3C;AACF;;;ACXA,IAAM,kBAAkB,CAAC,OAAO,QAAQ,OAAO,SAAS,UAAU,QAAQ,SAAS;AAW5E,SAAS,KAAK,UAAuB,CAAC,GAAoB;AAC/D,QAAM;AAAA,IACJ,SAAS;AAAA,IACT,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,EACF,IAAI;AAEJ,SAAO,OAAO,KAAK,SAAS;AAC1B,UAAM,OAAQ,IAA8C;AAC5D,UAAM,YAAY,IAAI,QAAQ,IAAI,QAAQ;AAC1C,QAAI,CAAC,WAAW;AACd,YAAM,KAAK;AACX;AAAA,IACF;AAGA,QAAI,cAA6B;AACjC,QAAI,WAAW,MAAM;AACnB,oBAAc;AAAA,IAChB,WAAW,OAAO,WAAW,UAAU;AACrC,oBAAc,cAAc,SAAS,SAAS;AAAA,IAChD,WAAW,MAAM,QAAQ,MAAM,GAAG;AAChC,oBAAc,OAAO,SAAS,SAAS,IAAI,YAAY;AAAA,IACzD;AAOA,QAAI,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC5C,gBAAU,MAAM,QAAQ;AAAA,IAC1B;AAEA,QAAI,CAAC,aAAa;AAChB,YAAM,KAAK;AACX;AAAA,IACF;AAGA,QAAI,UAAU,+BAA+B,WAAW;AAExD,QAAI,UAAU,gCAAgC,QAAQ,KAAK,IAAI,CAAC;AAEhE,QAAI,gBAAgB;AAClB,UAAI,UAAU,gCAAgC,eAAe,KAAK,IAAI,CAAC;AAAA,IACzE,OAAO;AAEL,YAAM,iBAAiB,IAAI,QAAQ,IAAI,gCAAgC;AACvE,UAAI,gBAAgB;AAClB,YAAI,UAAU,gCAAgC,cAAc;AAAA,MAC9D;AAAA,IACF;AAEA,QAAI,iBAAiB,cAAc,SAAS,GAAG;AAC7C,UAAI,UAAU,iCAAiC,cAAc,KAAK,IAAI,CAAC;AAAA,IACzE;AAEA,QAAI,aAAa;AACf,UAAI,UAAU,oCAAoC,MAAM;AAAA,IAC1D;AAEA,QAAI,WAAW,QAAW;AACxB,UAAI,UAAU,0BAA0B,OAAO,MAAM,CAAC;AAAA,IACxD;AAGA,QAAI,IAAI,WAAW,WAAW;AAC5B,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C;AAEA,UAAM,KAAK;AAAA,EACb;AACF;;;AClFA,IAAM,WAEF;AAAA,EACF,uBAAuB;AAAA,EACvB,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAChB,yBAAyB;AAAA,EACzB,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,+BAA+B;AAAA,EAC/B,yBAAyB;AAAA,EACzB,2BAA2B;AAAA,EAC3B,2BAA2B;AAAA,EAC3B,oBAAoB;AAAA,EACpB,YAAY;AACd;AAEO,SAAS,OAAO,UAAyB,CAAC,GAAoB;AACnE,QAAM,OAAO,EAAE,GAAG,UAAU,GAAG,QAAQ;AAEvC,SAAO,OAAO,KAAK,SAAS;AAC1B,QAAI,KAAK,0BAA0B,OAAO;AACxC,UAAI,UAAU,2BAA2B,KAAK,qBAAqB;AAAA,IACrE;AACA,QAAI,KAAK,kBAAkB,OAAO;AAChC,UAAI,UAAU,mBAAmB,KAAK,aAAa;AAAA,IACrD;AACA,QAAI,KAAK,qBAAqB;AAC5B,UAAI,UAAU,0BAA0B,SAAS;AAAA,IACnD;AACA,QAAI,KAAK,mBAAmB,OAAO;AACjC,UAAI,UAAU,mBAAmB,KAAK,cAAc;AAAA,IACtD;AACA,QAAI,KAAK,4BAA4B,OAAO;AAC1C,UAAI,UAAU,6BAA6B,KAAK,uBAAuB;AAAA,IACzE;AACA,QAAI,KAAK,qBAAqB;AAC5B,UAAI,UAAU,0BAA0B,KAAK;AAAA,IAC/C;AACA,QAAI,KAAK,kBAAkB;AACzB,UAAI,UAAU,sBAAsB,QAAQ;AAAA,IAC9C;AACA,QAAI,KAAK,kCAAkC,OAAO;AAChD,UAAI,UAAU,qCAAqC,KAAK,6BAA6B;AAAA,IACvF;AACA,QAAI,KAAK,4BAA4B,OAAO;AAC1C,UAAI,UAAU,8BAA8B,KAAK,uBAAuB;AAAA,IAC1E;AACA,QAAI,KAAK,8BAA8B,OAAO;AAC5C,UAAI,UAAU,gCAAgC,KAAK,yBAAyB;AAAA,IAC9E;AACA,QAAI,KAAK,8BAA8B,OAAO;AAC5C,UAAI,UAAU,gCAAgC,KAAK,yBAAyB;AAAA,IAC9E;AACA,QAAI,KAAK,oBAAoB;AAC3B,UAAI,UAAU,wBAAwB,IAAI;AAAA,IAC5C;AACA,QAAI,KAAK,YAAY;AACnB,UAAI,UAAU,gBAAgB,OAAO;AAAA,IACvC;AAEA,WAAO,MAAM,KAAK;AAAA,EACpB;AACF;;;AClFA,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAM1B,IAAM,YAAY,UAAU,KAAK,IAAI;AACrC,IAAM,eAAe,UAAU,KAAK,OAAO;AAC3C,IAAM,cAAc,UAAU,KAAK,cAAc;AAUjD,IAAM,oBAAoB;AAG1B,IAAM,sBAA+D;AAAA,EACnE,CAAC,OAAO,OAAO;AAAA,EACf,CAAC,OAAO,OAAO;AAAA,EACf,CAAC,OAAO,OAAO;AAAA,EACf,CAAC,OAAO,GAAG,WAAW,OAAO,KAAK,OAAO;AAC3C;AAEA,SAAS,eAAe,aAA8B;AACpD,QAAM,KAAK,YAAY,MAAM,GAAG,EAAE,CAAC,EAAG,KAAK,EAAE,YAAY;AACzD,MAAI,OAAO,GAAI,QAAO;AACtB,SAAO,oBAAoB,KAAK,CAAC,UAAU,MAAM,EAAE,CAAC;AACtD;AAYO,SAAS,oBAAoB,QAAoC;AACtE,SAAO,OACJ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,CAAC,SAAS,SAAS,EAAE,EAC5B,IAAI,CAAC,SAAS;AACb,UAAM,CAAC,UAAU,GAAG,MAAM,IAAI,KAAK,MAAM,GAAG;AAC5C,QAAI,UAAU;AACd,eAAW,SAAS,QAAQ;AAC1B,YAAM,UAAU,MAAM,KAAK;AAC3B,UAAI,QAAQ,WAAW,IAAI,GAAG;AAC5B,cAAM,IAAI,OAAO,QAAQ,MAAM,CAAC,CAAC;AACjC,YAAI,OAAO,SAAS,CAAC,EAAG,WAAU;AAAA,MACpC;AAAA,IACF;AACA,WAAO,EAAE,WAAW,YAAY,IAAI,KAAK,EAAE,YAAY,GAAG,QAAQ;AAAA,EACpE,CAAC,EACA,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE,UAAU,CAAC;AACrD;AAQO,SAAS,eAAe,UAAgE;AAC7F,QAAM,SAAS,oBAAI,IAAoB;AACvC,aAAW,EAAE,UAAU,QAAQ,KAAK,UAAU;AAE5C,WAAO,IAAI,UAAU,KAAK,IAAI,OAAO,IAAI,QAAQ,KAAK,GAAG,OAAO,CAAC;AAAA,EACnE;AAEA,aAAW,CAAC,UAAU,OAAO,KAAK,QAAQ;AACxC,QAAI,YAAY,EAAG,QAAO,OAAO,QAAQ;AAAA,EAC3C;AAEA,aAAW,aAAa,CAAC,MAAM,QAAQ,SAAS,GAAY;AAC1D,SAAK,OAAO,IAAI,SAAS,KAAK,KAAK,EAAG,QAAO;AAAA,EAC/C;AACA,OAAK,OAAO,IAAI,GAAG,KAAK,KAAK,EAAG,QAAO;AACvC,SAAO;AACT;AAEA,eAAe,aACb,UACA,MACqB;AACrB,QAAM,MAAM,OAAO,KAAK,MAAM,OAAO;AACrC,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO,YAAY,GAAG;AAAA,IACxB,KAAK;AACH,aAAO,UAAU,GAAG;AAAA,IACtB,KAAK;AACH,aAAO,aAAa,GAAG;AAAA,EAC3B;AACF;AAUO,SAAS,YAAY,UAA8B,CAAC,GAAoB;AAC7E,QAAM,YAAY,QAAQ,aAAa;AAEvC,SAAO,OAAO,KAAK,SAAS;AAC1B,UAAM,OAAQ,IAA8C;AAC5D,UAAM,WAAW,MAAM,KAAK;AAC5B,QAAI,CAAC,SAAU,QAAO;AAGtB,cAAU,MAAM,iBAAiB;AAGjC,UAAM,iBAAiB,IAAI,QAAQ,QAAQ,IAAI,iBAAiB,KAAK;AACrE,UAAM,WAAW,eAAe,oBAAoB,cAAc,CAAC;AACnE,UAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC5D,QACE,CAAC,YACD,CAAC,eAAe,WAAW,KAC3B,SAAS,QAAQ,IAAI,kBAAkB,MACtC,SAAS,QAAQ,IAAI,eAAe,KAAK,IAAI,SAAS,cAAc,KACrE,SAAS,WAAW,OACpB,SAAS,WAAW,OACpB,SAAS,SAAS,MAClB;AACA,aAAO;AAAA,IACT;AAIA,UAAM,kBAAkB,0BAA0B,QAAQ;AAE1D,UAAM,WAAW,MAAM,SAAS,KAAK;AACrC,QAAI,SAAS,SAAS,WAAW;AAE/B,YAAMC,WAAU,IAAI,QAAQ,SAAS,OAAO;AAC5C,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,mBAAmB,CAAC,CAAC,GAAG;AAChE,QAAAA,SAAQ,IAAI,KAAK,KAAK;AAAA,MACxB;AACA,aAAO,IAAI,SAAS,UAAU;AAAA,QAC5B,QAAQ,SAAS;AAAA,QACjB,YAAY,SAAS;AAAA,QACrB,SAAAA;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,aAAa,MAAM,aAAa,UAAU,QAAQ;AACxD,UAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;AAC5C,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,mBAAmB,CAAC,CAAC,GAAG;AAChE,cAAQ,IAAI,KAAK,KAAK;AAAA,IACxB;AACA,YAAQ,IAAI,oBAAoB,QAAQ;AAExC,YAAQ,OAAO,gBAAgB;AAC/B,WAAO,IAAI,SAAS,YAAmC;AAAA,MACrD,QAAQ,SAAS;AAAA,MACjB,YAAY,SAAS;AAAA,MACrB;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AC3KA,SAAS,kBAAkB;AAc3B,IAAMC,YAAkC,EAAE,MAAM,KAAK;AAGrD,SAAS,YAAY,MAAc,MAAuB;AACxD,QAAM,OAAO,WAAW,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,QAAQ;AAC5D,SAAO,OAAO,MAAM,IAAI,MAAM,IAAI,IAAI;AACxC;AAGA,SAAS,mBAAmB,aAAqBC,OAAc,MAAwB;AACrF,QAAM,YAAY,CAAC,QAAgB,IAAI,KAAK,EAAE,QAAQ,SAAS,EAAE;AACjE,MAAI,MAAM;AACR,UAAM,SAAS,UAAUA,KAAI;AAC7B,WAAO,YAAY,MAAM,GAAG,EAAE,KAAK,CAAC,QAAQ;AAC1C,YAAM,UAAU,IAAI,KAAK;AACzB,aAAO,YAAY,OAAO,UAAU,OAAO,MAAM;AAAA,IACnD,CAAC;AAAA,EACH;AAEA,SAAO,YAAY,MAAM,GAAG,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,MAAMA,SAAQ,IAAI,KAAK,MAAM,GAAG;AACvF;AAWO,SAAS,KAAK,UAAuB,CAAC,GAAoB;AAC/D,QAAM,OAAO,EAAE,GAAGD,WAAU,GAAG,QAAQ;AAEvC,SAAO,OAAO,KAAK,SAAS;AAC1B,UAAM,OAAQ,IAA8C;AAC5D,UAAM,WAAW,MAAM,KAAK;AAC5B,QAAI,CAAC,SAAU,QAAO;AAGtB,UAAM,SAAS,IAAI,QAAQ,OAAO,YAAY;AAC9C,QAAI,WAAW,SAAS,WAAW,OAAQ,QAAO;AAClD,QAAI,SAAS,SAAS,OAAO,SAAS,UAAU,IAAK,QAAO;AAG5D,QAAI,KAAK,QAAQ,MAAM,MAAM,UAAa,SAAS,QAAQ,IAAI,MAAM,GAAG;AACtE,aAAO;AAAA,IACT;AAEA,QACE,SAAS,SAAS,SACjB,SAAS,QAAQ,IAAI,cAAc,KAAK,IAAI,SAAS,mBAAmB,GACzE;AACA,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,MAAM,SAAS,KAAK;AACrC,UAAM,YAAY,YAAY,UAAU,KAAK,IAAI;AAGjD,UAAM,cAAc,IAAI,QAAQ,QAAQ,IAAI,eAAe;AAC3D,QAAI,eAAe,mBAAmB,aAAa,WAAW,KAAK,IAAI,GAAG;AACxE,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,YAAY,SAAS;AAAA,QACrB,SAAS,EAAE,MAAM,UAAU;AAAA,MAC7B,CAAC;AAAA,IACH;AAGA,SAAK,QAAQ,MAAM,IAAI;AACvB,UAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;AAE5C,UAAM,kBAAkB,0BAA0B,QAAQ;AAC1D,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,mBAAmB,CAAC,CAAC,GAAG;AAChE,cAAQ,IAAI,KAAK,KAAK;AAAA,IACxB;AACA,WAAO,IAAI,SAAS,UAAU;AAAA,MAC5B,QAAQ,SAAS;AAAA,MACjB,YAAY,SAAS;AAAA,MACrB;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACrEA,SAAS,eAAe,QAA0B;AAChD,MAAI,UAAU,IAAK,QAAO;AAC1B,MAAI,UAAU,IAAK,QAAO;AAC1B,SAAO;AACT;AAUA,SAAS,WAAW,OAAwB,MAAoB;AAC9D,MAAI,CAAC,mBAAmB,EAAG;AAC3B,gBAAc;AAAA,IACZ,OAAO,eAAe,MAAM,MAAM;AAAA,IAClC,SAAS;AAAA,IACT,OAAM,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC7B,OAAO;AAAA,IACP,QAAQ;AAAA,EACV,CAAC;AACH;AAkBO,SAAS,OAAO,UAAyB,CAAC,GAAoB;AACnE,SAAO,OAAO,KAAK,SAAS;AAC1B,UAAM,MAAM,QAAQ,OAAO;AAC3B,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI;AACF,YAAM,WAAW,MAAM,KAAK;AAC5B,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,YAAM,QAAQ;AAAA,QACZ,WAAW,IAAI;AAAA,QACf,QAAQ,IAAI;AAAA,QACZ,MAAM,IAAI;AAAA,QACV,QAAQ,SAAS;AAAA,QACjB,YAAY;AAAA,MACd;AACA,UAAI,OAAO,GAAG,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,SAAS,MAAM,IAAI,QAAQ,IAAI;AACvE,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAM,SAAU,KAAiC,cAAc;AAC/D,YAAM,QAAQ;AAAA,QACZ,WAAW,IAAI;AAAA,QACf,QAAQ,IAAI;AAAA,QACZ,MAAM,IAAI;AAAA,QACV;AAAA,QACA,YAAY;AAAA,QACZ,OAAO;AAAA,MACT;AACA,UAAI,OAAO,GAAG,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,MAAM,IAAI,QAAQ,QAAQ,OAAO,EAAE;AAC3E,YAAM;AAAA,IACR;AAAA,EACF;AACF;;;ACvFA,OAAOE,UAAQ;AACf,SAAS,iBAAiB,iBAAiB;AAE3C,OAAOC,YAAU;;;ACjBjB,OAAOC,YAAU;AACjB,OAAOC,UAAQ;AAYf,IAAM,uBAAuB;AAiBtB,SAAS,oBAAoB,SAAiB,MAAsB;AACzE,SAAOC,OAAK,QAAQ,SAAS,MAAM,oBAAoB;AACzD;;;ACvBO,SAAS,qBAAqB,KAA+B;AAClE,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACtD,QAAI,UAAU,OAAW;AACzB,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,iBAAW,KAAK,MAAO,SAAQ,OAAO,KAAK,CAAC;AAAA,IAC9C,OAAO;AACL,cAAQ,IAAI,KAAK,KAAK;AAAA,IACxB;AAAA,EACF;AACA,SAAO;AACT;AAcO,SAAS,mBACd,KACA,QACU;AACV,MAAI;AACF,WAAO,oBAAoB,KAAK,MAAM;AAAA,EACxC,QAAQ;AAEN,UAAM,WAAW,IAAI;AAAA,MACnB,KAAK,UAAU,EAAE,OAAO,EAAE,MAAM,kBAAkB,SAAS,wBAAwB,EAAE,CAAC;AAAA,MACtF;AAAA,QACE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD;AAAA,IACF;AACA,qBAAiB,QAAQ;AACzB,WAAO;AAAA,EACT;AACF;;;ACuCO,SAAS,aAAa,WAIhB;AACX,SAAO;AAAA,IACL,KAAK,MAAsC;AAEzC,YAAM,UACJ,OAAO,SAAS,YAAY,OAAO,SAAS,IAAI,IAAI,OAAO,cAAc,IAAI;AAC/E,gBAAU,KAAK,OAAO;AAAA,IACxB;AAAA,IACA,MAAM,MAAe,QAAgC;AACnD,gBAAU,MAAM,MAAM,MAAM;AAAA,IAC9B;AAAA,IACA,IAAI,aAAqB;AACvB,aAAO,UAAU;AAAA,IACnB;AAAA,EACF;AACF;;;AHxDA,SAAS,YAAY,KAA8B;AACjD,QAAM,MAAM,IAAI,OAAO;AACvB,QAAM,MAAM,IAAI,QAAQ,GAAG;AAC3B,SAAO,OAAO,IAAI,IAAI,MAAM,GAAG,GAAG,IAAI;AACxC;AAWA,eAAe,cACb,UACA,KACA,SACiC;AAEjC,MAAI,qBAAqB,KAAK,SAAS;AACrC,UAAM,OAAO,WAAW;AACxB,QAAI,MAAM;AACR,YAAM,aAAa,qBAAqB,UAAU,SAAS,IAAI;AAC/D,UAAI,cAAcC,KAAG,WAAW,UAAU,GAAG;AAC3C,cAAM,eAAe,YAAY,SAAS,IAAI;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAU,MAAM,oBAAoB,UAAU,qBAAqB,CAAC;AAK1E,QAAM,UAAU,OAAO,IAAI;AAC3B,MAAI,OAAO,YAAY,YAAY;AACjC,UAAM,IAAI,MAAM,0BAA0B,QAAQ,EAAE;AAAA,EACtD;AACA,SAAQ,QAAsB,GAAG;AACnC;AAcO,SAAS,WAAW,WAAsB,UAAwC;AACvF,MAAI,CAAC,SAAU;AACf,QAAM,KAAK,aAAa,SAAS;AAGjC,QAAM,QAAQ,CAAC,OAAe,OAAyB;AACrD,QAAI;AACF,SAAG;AAAA,IACL,SAAS,KAAK;AACZ,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,UAAI,SAAS,SAAS;AACpB,YAAI;AACF,mBAAS,QAAQ,IAAI,KAAK;AAAA,QAC5B,SAAS,SAAS;AAChB,kBAAQ,MAAM,sDAAkC,KAAK,MAAM,OAAO;AAAA,QACpE;AAAA,MACF,OAAO;AACL,gBAAQ,MAAM,cAAc,KAAK,8BAAU,KAAK;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,QAAQ;AAEnB,QAAI,UAAU,eAAe,UAAU,MAAM;AAC3C,YAAM,UAAU,MAAM,SAAS,OAAQ,EAAE,CAAC;AAAA,IAC5C,OAAO;AACL,gBAAU,KAAK,QAAQ,MAAM,MAAM,UAAU,MAAM,SAAS,OAAQ,EAAE,CAAC,CAAC;AAAA,IAC1E;AAAA,EACF;AACA,MAAI,SAAS,WAAW;AACtB,cAAU,GAAG,WAAW,CAAC,MAAe,aAAsB;AAI5D,YAAM,MAAM,WAAW,IAAI;AAC3B,YAAM,aAAa,MAAM,SAAS,UAAW,IAAI,WAAW,MAAM,IAAI,SAAS,MAAM,CAAC,CAAC;AAAA,IACzF,CAAC;AAAA,EACH;AACA,MAAI,SAAS,SAAS;AACpB,cAAU,GAAG,SAAS,CAAC,MAAc,WAAmB;AACtD,YAAM,WAAW,MAAM,SAAS,QAAS,IAAI,MAAM,OAAO,SAAS,MAAM,CAAC,CAAC;AAAA,IAC7E,CAAC;AAAA,EACH;AACA,MAAI,SAAS,SAAS;AACpB,cAAU,GAAG,SAAS,CAAC,QAAe;AAEpC,UAAI;AACF,iBAAS,QAAS,IAAI,GAAG;AAAA,MAC3B,SAAS,SAAS;AAChB,gBAAQ,MAAM,gEAA4C,OAAO;AAAA,MACnE;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAGA,SAAS,WAAW,MAAuB;AACzC,MAAI,OAAO,SAAS,IAAI,EAAG,QAAO;AAClC,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,OAAO,OAAO,IAAI;AAClD,SAAO,OAAO,KAAK,IAAmB;AACxC;AAOA,eAAe,qBAAqB,QAAgB,UAAmC;AACrF,QAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,QAAM,aAAa,YAAY,SAAS,MAAM,IAAI,SAAS,cAAc,EAAE;AAAA;AAC3E,QAAM,cAAwB,CAAC;AAC/B,MAAI,mBAAmB;AAEvB,QAAM,kBAAkB,0BAA0B,QAAQ;AAC1D,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,mBAAmB,CAAC,CAAC,GAAG;AAChE,QAAI,IAAI,YAAY,MAAM,iBAAkB,oBAAmB;AAC/D,gBAAY,KAAK,GAAG,GAAG,KAAK,KAAK,EAAE;AAAA,EACrC;AACA,aAAW,CAAC,KAAK,KAAK,KAAK,SAAS,SAAS;AAC3C,QAAI,IAAI,YAAY,MAAM,kBAAkB;AAC1C,yBAAmB;AAAA,IACrB;AACA,gBAAY,KAAK,GAAG,GAAG,KAAK,KAAK,EAAE;AAAA,EACrC;AACA,MAAI,CAAC,kBAAkB;AACrB,gBAAY,KAAK,mBAAmB,OAAO,WAAW,IAAI,CAAC,EAAE;AAAA,EAC/D;AACA,SAAO,MAAM,aAAa,YAAY,KAAK,MAAM,IAAI,aAAa,IAAI;AACtE,SAAO,QAAQ;AACjB;AAuCO,SAAS,gBAAgB,SAA2C;AACzE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf;AAAA,EACF,IAAI;AAGJ,QAAM,eAAe,oBAAoB,SAAS,IAAI;AAEtD,QAAM,MAAM,IAAI,gBAAgB,EAAE,UAAU,KAAK,CAAC;AAElD,SAAO,GAAG,WAAW,OAAO,KAAsB,QAAgB,SAAiB;AACjF,QAAI;AACF,YAAM,qBAAqB,KAAK,QAAQ,IAAI;AAAA,IAC9C,SAAS,KAAK;AAIZ,cAAQ,MAAM,gDAA4B,GAAG;AAC7C,UAAI,CAAC,OAAO,WAAW;AACrB,YAAI;AACF,cAAI,CAAC,OAAO,eAAe;AACzB,mBAAO,MAAM,4CAA4C;AAAA,UAC3D;AAAA,QACF,QAAQ;AAAA,QAER;AACA,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF,CAAC;AAED,iBAAe,qBACb,KACA,QACA,MACe;AACf,UAAM,kBAAkB,UAAU;AAClC,UAAM,WAAW,YAAY,GAAG;AAChC,UAAM,QAA6B,aAAa,iBAAiB,QAAQ;AAEzE,QAAI,CAAC,OAAO;AAEV,aAAO,MAAM,gCAAgC;AAC7C,aAAO,QAAQ;AACf;AAAA,IACF;AAEA,UAAM,EAAE,OAAO,OAAO,IAAI;AAC1B,UAAM,UAAU,qBAAqB,GAAG;AACxC,UAAM,OAAO,IAAI,QAAQ,QAAQ;AAEjC,UAAM,UAAU,IAAI,IAAI,UAAU,IAAI,GAAG,IAAI,OAAO,GAAG,EAAE;AAGzD,UAAM,UAAU,IAAI,QAAQ,QAAQ,SAAS,GAAG,EAAE,QAAQ,OAAO,QAAQ,CAAC;AAC1E,UAAM,MAAM;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,KAAK,YAAY;AAAA,MAC7B;AAAA,MACA;AAAA,IACF;AACA,UAAM,OAAQ,IAA8C;AAG5D,QAAI,WAAW;AAGf,UAAM,eAAe,YAA+B;AAQlD,YAAM,aAAa,qBAAqB,MAAM,UAAU,MAAM,OAAO;AAKrE,UAAIA,KAAG,WAAW,UAAU,GAAG;AAC7B,YAAI,qBAAqB,GAAG;AAC1B,gBAAM,UAAU,WAAW;AAC3B,cAAI,SAAS;AACX,kBAAM;AAAA,cACJ;AAAA,cACA,MAAM;AAAA,cACN,UAAU;AAAA,cACV;AAAA,cACA;AAAA,cACA,UAAU;AAAA,YACZ;AAAA,UACF;AAAA,QACF;AACA,cAAM,eAAe,QAAQ;AAC7B,cAAM,iBAAiB,cAAc,YAAY;AACjD,cAAM,eAAe,MAAM,cAAc,YAAY,MAAM,UAAU,MAAM;AAC3E,YAAI,CAAC,aAAa,OAAO;AACvB,gBAAM,IAAI,gBAAgB,wCAAU,aAAa,MAAM;AAAA,QACzD;AACA,YACE,aAAa,SAAS,UACtB,OAAO,aAAa,SAAS,YAC7B,aAAa,SAAS,MACtB;AACA,cAAI,SAAS;AAAA,YACX,GAAG;AAAA,YACH,GAAI,aAAa;AAAA,UACnB;AAAA,QACF;AACA,cAAM,cAAc,MAAM,cAAc,YAAY,MAAM,SAAS,cAAc;AACjF,YAAI,CAAC,YAAY,OAAO;AACtB,gBAAM,IAAI,gBAAgB,wCAAU,YAAY,MAAM;AAAA,QACxD;AACA,YACE,YAAY,SAAS,kBACrB,OAAO,YAAY,SAAS,YAC5B,YAAY,SAAS,MACrB;AACA,cAAI,QAAQ;AAAA,YACV,GAAI;AAAA,YACJ,GAAI,YAAY;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAEA,UAAI;AACJ,UAAI;AACF,cAAM,mBAAmBC,OAAK,QAAQ,SAAS,MAAM,QAAQ;AAG7D,mBAAW,MAAM,cAAc,kBAAkB,KAA6B,OAAO;AAAA,MACvF,SAAS,KAAK;AACZ,cAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,gBAAQ,MAAM,+CAA2B,MAAM,QAAQ,KAAK,MAAM,EAAE;AACpE,eAAO,IAAI,SAAS,yBAAyB,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC9D;AAGA,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAI,cAAc,KAAK,QAAQ,MAAM,CAAC,cAAyB;AAC7D,cAAI;AACF,uBAAW,WAAW,QAAQ;AAC9B,gBAAI,KAAK,cAAc,WAAW,GAAG;AACrC,uBAAW;AACX,oBAAQ;AAAA,UACV,SAAS,KAAK;AACZ,mBAAO,GAAG;AAAA,UACZ;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAGD,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C;AAGA,QAAI;AACJ,QAAI;AAEF,UAAI,MAAM,gBAAgB,UAAa,MAAM,iBAAiB;AAE5D,cAAM,0BAA0B,MAAM,iBAAiB,OAAO;AAC9D,cAAM,SAAS,MAAM,sBAAsB,MAAM,eAAe;AAChE,YAAI,QAAQ;AACV,gBAAM,cAAc,OAAO;AAAA,QAC7B,OAAO;AACL,gBAAM,cAAc,CAAC;AAAA,QACvB;AAAA,MACF;AACA,YAAM,iBAAiB,MAAM,eAAe,CAAC;AAC7C,YAAM,iBACJ,qBAAqB,kBAAkB,SAAS,IAC5C,CAAC,GAAG,mBAAmB,GAAG,cAAc,IACxC;AACN,UAAI,eAAe,SAAS,GAAG;AAC7B,mBAAW,MAAM,QAAQ,gBAAgB,KAAK,YAAY;AAAA,MAC5D,OAAO;AACL,mBAAW,MAAM,aAAa;AAAA,MAChC;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,UAAU;AAEZ,gBAAQ,MAAM,gEAAwB,GAAG;AACzC;AAAA,MACF;AAGA,iBAAW,mBAAmB,GAAG;AAAA,IACnC;AAGA,QAAI,UAAU;AACZ;AAAA,IACF;AAGA,UAAM,qBAAqB,QAAQ,UAAU,UAAU,IAAI,CAAC;AAAA,EAC9D;AAEA,SAAO;AACT;;;AhBzYA,IAAM,qBAAqB,KAAK,OAAO;AAEvC,SAAS,aACP,KACA,YAAoB,oBACpB,eAKA;AAEA,QAAM,iBAAiB,IAAI,QAAQ,mBAAmB;AACtD,QAAM,WAAW,MAAM,QAAQ,cAAc,IACxC,eAAe,CAAC,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,KAAK,SAC5C,gBAAgB,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,KAAK;AAC9C,QAAM,OAAO,IAAI,QAAQ,QAAQ;AAEjC,QAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,GAAG,QAAQ,MAAM,IAAI,EAAE;AAE3D,QAAM,UAAU,qBAAqB,GAAG;AAExC,QAAM,SAAS,IAAI,UAAU;AAG7B,MAAI,WAAW,SAAS,WAAW,QAAQ;AACzC,WAAO;AAAA,MACL,SAAS,IAAI,QAAQ,IAAI,SAAS,GAAG,EAAE,QAAQ,SAAS,QAAQ,cAAc,CAAC;AAAA,MAC/E;AAAA,IACF;AAAA,EACF;AAIA,QAAM,gBAAgB,IAAI,QAAQ,gBAAgB;AAClD,MAAI,kBAAkB,QAAW;AAC/B,UAAM,WAAW,OAAO,MAAM,QAAQ,aAAa,IAAI,cAAc,CAAC,IAAI,aAAa;AACvF,QAAI,OAAO,SAAS,QAAQ,KAAK,WAAW,WAAW;AACrD,YAAM,IAAI,qBAAqB,SAAS;AAAA,IAC1C;AAAA,EACF;AAIA,QAAM,SAASC,UAAS,MAAM,GAAG;AACjC,QAAM,gBAAgB,gBAAgB,QAAQ,SAAS;AACvD,SAAO;AAAA,IACL,SAAS,IAAI,QAAQ,IAAI,SAAS,GAAG;AAAA,MACnC;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV,CAAgB;AAAA,IAChB;AAAA,EACF;AACF;AAaA,SAAS,gBACP,QACA,SAC4B;AAC5B,MAAI,YAAY;AAChB,MAAI;AACJ,MAAI,UAAU;AAEd,QAAM,gBAAgB,MAAY;AAChC,QAAI,QAAQ;AACV,UAAI;AACF,eAAO,YAAY;AAAA,MACrB,QAAQ;AAAA,MAER;AACA,eAAS;AAAA,IACX;AAAA,EACF;AAEA,QAAM,aAAa,CACjB,YACA,QACS;AACT,QAAI,QAAS;AACb,cAAU;AACV,eAAW,MAAM,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACpE,kBAAc;AAAA,EAChB;AAEA,SAAO,IAAI,eAA2B;AAAA,IACpC,MAAM,KAAK,YAAY;AACrB,UAAI,CAAC,OAAQ,UAAS,OAAO,UAAU;AACvC,UAAI;AACF,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,MAAM;AACR,qBAAW,MAAM;AACjB,wBAAc;AACd;AAAA,QACF;AACA,qBAAa,MAAM;AACnB,YAAI,YAAY,SAAS;AAEvB,qBAAW,YAAY,IAAI,qBAAqB,OAAO,CAAC;AACxD;AAAA,QACF;AACA,mBAAW,QAAQ,KAAK;AAAA,MAC1B,SAAS,KAAK;AAEZ,mBAAW,YAAY,GAAG;AAAA,MAC5B;AAAA,IACF;AAAA,IACA,OAAO,QAAQ;AACb,UAAI,QAAQ;AACV,YAAI;AACF,iBAAO,OAAO,MAAM;AAAA,QACtB,QAAQ;AAAA,QAER;AACA,sBAAc;AAAA,MAChB;AAAA,IACF;AAAA,EACF,CAAC;AACH;AA0DO,SAAS,aAAa,SAG3B;AACA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,MAAM;AAAA,IACN;AAAA,IACA,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,eAAe;AAAA,EACjB,IAAI;AAGJ,QAAM,YAAuB,EAAE,SAAS,QAAQ,WAAW,YAAY,CAAC,EAAE;AAG1E,QAAM,eAAe,oBAAoB,SAAS,IAAI;AAGtD,QAAM,oBAAuC,CAAC;AAG9C,MAAI,mBAAmB;AACrB,UAAM,WAAW,OAAO,sBAAsB,WAAW,oBAAoB,CAAC;AAC9E,sBAAkB,KAAK,YAAY,QAAQ,CAAC;AAAA,EAC9C;AAGA,QAAM,iBACJ,eAAe,QACX,OACA,eAAe,QAAQ,eAAe,SACpC,KAAK,IACL,KAAK,UAAU;AACvB,MAAI,eAAgB,mBAAkB,KAAK,cAAc;AAGzD,MAAI,cAAc;AAChB,UAAM,WAAW,OAAO,iBAAiB,WAAW,eAAe,CAAC;AACpE,sBAAkB,KAAK,OAAO,QAAQ,CAAC;AAAA,EACzC;AAIA,oBAAkB,KAAK,OAAiB,CAAC;AAIzC,MAAI,YAAY;AACd,UAAM,WAAW,OAAO,eAAe,WAAW,aAAa,CAAC;AAChE,sBAAkB,KAAK,KAAK,QAAQ,CAAC;AAAA,EACvC;AAIA,QAAM,mBAAsC,CAAC,GAAG,iBAAiB;AACjE,MAAI,qBAAqB,kBAAkB,SAAS,GAAG;AACrD,qBAAiB,KAAK,GAAG,iBAAiB;AAAA,EAC5C;AAEA,QAAM,UAAU,MAAc;AAC5B,QAAI,aAAa;AACf,YAAM,SAAS,OAAO,gBAAgB,WAAW,cAAc,CAAC;AAChE,aAAO,wBAAwB;AAAA,QAC7B,KAAK,OAAO,MAAM,aAAa,OAAO,GAAG,IAAI;AAAA,QAC7C,MAAM,OAAO,OAAO,aAAa,OAAO,IAAI,IAAI;AAAA,QAChD,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AACA,WAAO,iBAAiB;AAAA,EAC1B,GAAG;AAEH,SAAO,GAAG,WAAW,CAAC,KAAsB,QAAwB;AAElE,UAAM,gBAAgB,UAAU;AAEhC;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV;AAAA,IACF,EAAE,MAAM,CAAC,QAAQ;AAGf,cAAQ,MAAM,iEAAiE,GAAG;AAClF,UAAI;AACF,YAAI,CAAC,IAAI,aAAa;AACpB,cAAI,aAAa;AACjB,cAAI,IAAI;AAAA,QACV,WAAW,CAAC,IAAI,eAAe;AAC7B,cAAI,IAAI;AAAA,QACV;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAKD,kBAAgB;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO,EAAE,QAAQ,UAAU;AAC7B;AAgBA,SAAS,eACP,KACA,SACA,KACA,QACA,cACA,YACA,YACA,cAQA;AACA,QAAM,SAAS,QAAQ,OAAO,YAAY;AAC1C,QAAM,UAAU,IAAI;AAGpB,QAAM,MAAM;AAAA,IACV;AAAA,IACA;AAAA,IACA,YAAY,UAAU,CAAC;AAAA,IACvB;AAAA,IACA,YAAY,KAAK,YAAY;AAAA,IAC7B;AAAA,IACA;AAAA,EACF;AACA,QAAM,OAAQ,IAA8C;AAC5D,SAAO,EAAE,SAAS,KAAK,KAAK,MAAM,QAAQ,QAAQ;AACpD;AAOA,SAAS,oBAAoB,QAAuB,QAAgB,SAA6B;AAC/F,QAAM,QAAQ,WAAW,QAAQ,QAAQ,OAAO;AAChD,MAAI,MAAO,QAAO;AAGlB,QAAM,iBAAiB,mBAAmB,QAAQ,OAAO;AACzD,MAAI,eAAe,SAAS,GAAG;AAC7B,UAAM,IAAI,sBAAsB,QAAQ,SAAS,cAAc;AAAA,EACjE;AAGA,QAAM,IAAI,mBAAmB,OAAO;AACtC;AAYA,IAAM,iBAAiB,oBAAI,QAGzB;AAEF,SAAS,cACP,OACA,SACA,MAC6C;AAC7C,MAAI,SAAS,eAAe,IAAI,KAAK;AACrC,MAAI,CAAC,QAAQ;AACX,aAAS;AAAA,MACP,aAAaC,OAAK,QAAQ,SAAS,MAAM,QAAQ;AAAA,MACjD,YAAY,qBAAqB,MAAM,UAAU,MAAM,OAAO;AAAA,IAChE;AACA,mBAAe,IAAI,OAAO,MAAM;AAAA,EAClC;AACA,SAAO;AACT;AAGA,IAAM,uBAAuB,oBAAI,QAA6B;AAE9D,SAAS,oBAAoB,MAcD;AAC1B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AACJ,SAAO,YAAY;AAGjB,UAAM,QAAQ,cAAc,oBAAoB,QAAQ,QAAQ,OAAO;AACvE,UAAM,EAAE,MAAM,IAAI;AAClB,UAAM,EAAE,aAAa,WAAW,IAAI,cAAc,OAAO,SAAS,IAAI;AAGtE,UAAM,cAAc,MAAM,gBAAgB,aAAa,MAAM,QAAQ,OAAO;AAI5E,UAAM,EAAE,OAAO,QAAQ,IAAI,MAAM,oBAAoB,MAAM,QAAQ,SAAS,GAAG;AAC/E,QAAI,YAAY,QAAW;AACzB,UAAI,UAAU;AAAA,IAChB;AAGA,UAAM,YAAY,sBAAsB,MAAM,MAAM;AAIpD,QAAI,qBAAqB,GAAG;AAC1B,YAAM,UAAU,WAAW;AAC3B,UAAI,SAAS;AACX,cAAM,sBAAsB,YAAY,MAAM,UAAU,QAAQ,SAAS,MAAM,QAAQ;AAAA,MACzF;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,cAAc,YAAY,MAAM,QAAQ,WAAW,KAAK;AAC7E,QAAI,CAAC,OAAO,OAAO;AACjB,YAAM,IAAI,gBAAgB,wCAAU,OAAO,MAAM;AAAA,IACnD;AAOA,QACE,cAAc,WACd,OAAO,SAAS,SAChB,OAAO,OAAO,SAAS,YACvB,OAAO,SAAS,MAChB;AACA,UAAI,QAAQ;AAAA,QACV,GAAI;AAAA,QACJ,GAAI,OAAO;AAAA,MACb;AAAA,IACF;AAQA,UAAM,eAAe,MAAM,cAAc,YAAY,MAAM,QAAQ,UAAU,MAAM,MAAM;AACzF,QAAI,CAAC,aAAa,OAAO;AACvB,YAAM,IAAI,gBAAgB,wCAAU,aAAa,MAAM;AAAA,IACzD;AACA,QAAI,aAAa,SAAS,MAAM,QAAQ;AACtC,UAAI,SACF,OAAO,aAAa,SAAS,YAAY,aAAa,SAAS,OAC3D;AAAA,QACE,GAAG,MAAM;AAAA,QACT,GAAI,aAAa;AAAA,MACnB,IACA,MAAM;AAAA,IACd;AAOA,QAAI,cAAc,QAAQ;AACxB,YAAM,WAAW,cAAc,IAAI,YAAY;AAC/C,YAAM,cAAc,MAAM,cAAc,YAAY,MAAM,QAAQ,SAAS,QAAQ;AACnF,UAAI,CAAC,YAAY,OAAO;AACtB,cAAM,IAAI,gBAAgB,wCAAU,YAAY,MAAM;AAAA,MACxD;AACA,UACE,YAAY,SAAS,YACrB,OAAO,YAAY,SAAS,YAC5B,YAAY,SAAS,MACrB;AACA,YAAI,QAAQ;AAAA,UACV,GAAI;AAAA,UACJ,GAAI,YAAY;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAUA,QAAI;AACJ,QAAI,cAAc,WAAW,QAAQ,MAAM,MAAM,GAAG;AAClD,YAAM,SAAS,MAAM,0BAA0B,OAAO;AACtD,UAAI,OAAO,YAAY,QAAW;AAChC,YAAI,UAAU,OAAO;AAAA,MACvB;AACA,YAAM,aAAa,MAAM,cAAc,YAAY,MAAM,QAAQ,QAAQ,OAAO,KAAK;AACrF,UAAI,CAAC,WAAW,OAAO;AACrB,cAAM,IAAI,gBAAgB,wCAAU,WAAW,MAAM;AAAA,MACvD;AACA,aAAO,WAAW;AAAA,IACpB,WAAW,QAAQ,MAAM,MAAM,GAAG;AAChC,aAAO,OAAO;AAAA,IAChB;AAIA,QAAI,SAAS,UAAa,SAAS,MAAM;AACvC,UAAI,OAAO;AAAA,IACb;AAIA,QAAI,MAAM,gBAAgB,UAAa,MAAM,cAAc,UAAa,MAAM,iBAAiB;AAE7F,YAAM,0BAA0B,MAAM,iBAAiB,OAAO;AAC9D,YAAM,SAAS,MAAM,sBAAsB,MAAM,eAAe;AAChE,UAAI,QAAQ;AACV,cAAM,cAAc,OAAO;AAC3B,cAAM,YAAY,OAAO;AAAA,MAC3B,OAAO;AAEL,cAAM,cAAc,CAAC;AACrB,cAAM,YAAY,CAAC;AAAA,MACrB;AAAA,IACF;AAKA,QAAI,kBAAkB,MAAM;AAC5B,QAAI,iBAAiB;AACnB,UAAI,SAAS,qBAAqB,IAAI,KAAK;AAC3C,UAAI,CAAC,QAAQ;AACX,iBAAS,EAAE,GAAG,iBAAiB,GAAG,MAAM,UAAU;AAClD,6BAAqB,IAAI,OAAO,MAAM;AAAA,MACxC;AACA,wBAAkB;AAAA,IACpB;AAGA,WAAO,MAAM,cAAc,YAAY,SAAS,KAAK,MAAM,MAAM,aAAa,eAAe;AAAA,EAC/F;AACF;AAKA,eAAe,oBAAoB,UAAoB,KAAoC;AACzF,QAAM,iBAAiB,UAAU,GAAG;AACtC;AAeA,eAAe,kBACb,KACA,MACA,KACA,SACA,KACe;AACf,QAAM,iBAAiB,UAAU,mBAAmB,KAAK,KAAK,MAAM,GAAG,IAAI,GAAG,GAAG;AAGjF,MAAI,WAAW,KAAK;AAClB,QAAI;AACF,YAAM,QAAQ,KAAK,GAAG;AAAA,IACxB,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAEA,eAAe,cACb,QACA,SACA,MACA,KACA,KACA,kBACA,SACA,QACA,iBACA,WACA,cACA,YACA,UACA,cACe;AAEf,MAAI,OAAqB,EAAE,SAAS,CAAC,GAAG,YAAY,CAAC,EAAE;AACvD,MAAI;AAEJ,MAAI,eAAe;AAInB,QAAM,kBAAkB,IAAI,gBAAgB;AAC5C,MAAI,GAAG,SAAS,MAAM;AACpB,QAAI,CAAC,IAAI,cAAe,iBAAgB,MAAM;AAAA,EAChD,CAAC;AACD,MAAI;AAKF,UAAM,EAAE,SAAS,IAAI,IAAI,aAAa,KAAK,WAAW,gBAAgB,MAAM;AAC5E,UAAM,SAAS,QAAQ,OAAO,YAAY;AAC1C,UAAM,UAAU,IAAI;AACpB,UAAM,aAAa,WAAW,QAAQ,QAAQ,OAAO;AAGrD,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,SAAS;AACf,WAAO,SAAS;AAMhB,IAAC,IAAyE,iBAAiB,CACzFC,cACG;AACH,UAAI,aAAc;AAClB,qBAAe;AAGf,WAAK,iBAAiB,UAAUA,WAAU,IAAI,GAAG,GAAG,EAAE,MAAM,MAAM;AAChE,YAAI,CAAC,IAAI,cAAe,KAAI,QAAQ;AAAA,MACtC,CAAC;AAAA,IACH;AAGA,UAAM,gBAAgB,oBAAoB;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAID,UAAM,WACJ,iBAAiB,SAAS,IACtB,MAAM,QAAQ,kBAAkB,KAAK,aAAa,IAClD,MAAM,cAAc;AAG1B,QAAI,CAAC,cAAc;AACjB,YAAM,oBAAoB,UAAU,GAAG;AAAA,IACzC;AAAA,EACF,SAAS,KAAc;AAGrB,QAAI,IAAI,aAAa,IAAI,cAAe;AAGxC,QAAI,cAAc;AAChB,UAAI,WAAW,KAAK;AAClB,YAAI;AACF,gBAAM,QAAQ,KAAK,GAAG;AAAA,QACxB,QAAQ;AAAA,QAER;AAAA,MACF;AACA;AAAA,IACF;AAIA,QAAI,eAAe,sBAAsB;AACvC,UAAI,UAAU,cAAc,OAAO;AACnC,UAAI,KAAK,UAAU,MAAM,IAAI,QAAQ,CAAC;AAAA,IACxC;AACA,UAAM,kBAAkB,KAAK,MAAM,KAAK,SAAS,GAAG;AAAA,EACtD;AACF;;;A3BxuBA,IAAM,mBAAmB,CAAC,iBAAiB;AAG3C,IAAMC,sBAAqB,KAAK,OAAO;AAmBvC,eAAsB,iBAAiB,SAAiD;AACtF,QAAM;AAAA,IACJ;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA,MAAAC,QAAO;AAAA,IACP,QAAAC,UAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAYF;AAAA,EACd,IAAI;AAGJ,QAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,WAAW,SAAS,QAAQ;AAC/D,QAAM,SAAS,WAAW,MAAM;AAGhC,QAAM,aAAa,OACfG,OAAK,WAAW,IAAI,IAClB,OACAA,OAAK,QAAQ,SAAS,IAAI,IAC5B,MAAMC,KAAG,QAAQD,OAAK,KAAK,GAAG,OAAO,GAAG,oBAAoB,CAAC;AAGjE,QAAM,oBAAoB,QAAQ,SAAS,UAAU;AAGrD,QAAM,EAAE,OAAO,IAAI,aAAa;AAAA,IAC9B,QAAQ;AAAA,IACR;AAAA,IACA,MAAM;AAAA,IACN,MAAAF;AAAA,IACA,QAAAC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAGD,QAAM,UAAU,MAAM,mBAAmB,MAAM;AAG/C,MAAI,SAAS;AAEb,QAAM,aAAyB;AAAA,IAC7B;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IAEA,MAAM,QAAuB;AAC3B,UAAI,OAAQ;AACZ,eAAS;AAGT,YAAM,IAAI;AAIV,QAAE,sBAAsB;AACxB,QAAE,uBAAuB;AAGzB,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,eAAO,MAAM,MAAM,QAAQ,CAAC;AAAA,MAC9B,CAAC;AAGD,YAAME,KAAG,GAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAEtE,CAAC;AAGD,4BAAsB;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AACT;AAOA,SAAS,mBAAmB,QAAiC;AAC3D,SAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,WAAO,OAAO,GAAG,MAAM;AACrB,YAAM,OAAO,OAAO,QAAQ;AAC5B,UAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,gBAAQ,oBAAoB,KAAK,IAAI,EAAE;AAAA,MACzC,OAAO;AACL,eAAO,IAAI,MAAM,8BAA8B,CAAC;AAAA,MAClD;AAAA,IACF,CAAC;AACD,WAAO,GAAG,SAAS,CAAC,QAAQ;AAC1B,aAAO,GAAG;AAAA,IACZ,CAAC;AAAA,EACH,CAAC;AACH;;;A+C/MA,SAAS,aAAAC,kBAA+B;AAsDjC,IAAM,eAAN,MAAmB;AAAA,EAChB,QAAkB,CAAC;AAAA,EACnB,UAAwC,CAAC;AAAA,EACzC;AAAA,EAER,YAAY,IAAe;AACzB,SAAK,WAAW,CAAC,SAAkB;AACjC,YAAM,MAAM,iBAAiB,IAAI;AACjC,YAAM,SAAS,KAAK,QAAQ,MAAM;AAClC,UAAI,QAAQ;AACV,eAAO,GAAG;AAAA,MACZ,OAAO;AACL,aAAK,MAAM,KAAK,GAAG;AAAA,MACrB;AAAA,IACF;AACA,OAAG,GAAG,WAAW,KAAK,QAAQ;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,KAAK,UAAU,KAAuB;AACpC,WAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAE9C,YAAM,UAAU,CAACC,SAAgB;AAC/B,qBAAa,KAAK;AAClB,gBAAQA,IAAG;AAAA,MACb;AACA,YAAM,QAAQ,WAAW,MAAM;AAC7B,cAAM,MAAM,KAAK,QAAQ,QAAQ,OAAO;AACxC,YAAI,OAAO,EAAG,MAAK,QAAQ,OAAO,KAAK,CAAC;AACxC,eAAO,IAAI,MAAM,2BAA2B,CAAC;AAAA,MAC/C,GAAG,OAAO;AAEV,YAAM,MAAM,KAAK,MAAM,MAAM;AAC7B,UAAI,QAAQ,QAAW;AACrB,gBAAQ,GAAG;AAAA,MACb,OAAO;AACL,aAAK,QAAQ,KAAK,OAAO;AAAA,MAC3B;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAUA,SAAS,iBAAiB,MAAuB;AAC/C,MAAI,OAAO,SAAS,IAAI,GAAG;AACzB,WAAO,KAAK,SAAS,MAAM;AAAA,EAC7B;AACA,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,OAAO,OAAO,IAAI,EAAE,SAAS,MAAM;AAAA,EAC5C;AAEA,SAAO,OAAO,KAAK,IAA6B,EAAE,SAAS,MAAM;AACnE;AAYO,SAAS,cAAc,IAAe,UAAU,KAAqB;AAC1E,SAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,UAAM,QAAQ,WAAW,MAAM;AAC7B,aAAO,IAAI,MAAM,wBAAwB,CAAC;AAAA,IAC5C,GAAG,OAAO;AAEV,UAAM,UAAU,MAAM;AACpB,mBAAa,KAAK;AAClB,SAAG,eAAe,QAAQ,MAAM;AAChC,SAAG,eAAe,SAAS,OAAO;AAClC,SAAG,eAAe,SAAS,OAAO;AAAA,IACpC;AAEA,UAAM,SAAS,MAAM;AACnB,cAAQ;AACR,cAAQ;AAAA,IACV;AACA,UAAM,UAAU,CAAC,QAAe;AAC9B,cAAQ;AACR,aAAO,GAAG;AAAA,IACZ;AACA,UAAM,UAAU,MAAM;AACpB,cAAQ;AACR,aAAO,IAAI,MAAM,8BAA8B,CAAC;AAAA,IAClD;AAEA,OAAG,KAAK,QAAQ,MAAM;AACtB,OAAG,KAAK,SAAS,OAAO;AACxB,OAAG,KAAK,SAAS,OAAO;AAAA,EAC1B,CAAC;AACH;AAmBA,eAAsB,UACpB,SACA,UACA,UAA+B,CAAC,GACT;AACvB,QAAM,EAAE,UAAU,KAAM,SAAS,UAAU,IAAI;AAG/C,QAAM,YAAY,QAAQ,QAAQ,UAAU,KAAK,EAAE,QAAQ,WAAW,MAAM;AAC5E,QAAM,MAAM,GAAG,SAAS,GAAG,QAAQ;AAEnC,QAAM,KAAK,IAAID,WAAU,KAAK,WAAW,UAAU,EAAE,QAAQ,IAAI,MAAS;AAC1E,QAAM,QAAQ,IAAI,aAAa,EAAE;AAGjC,MAAI;AACF,UAAM,cAAc,IAAI,OAAO;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,GAAG,eAAeA,WAAU,QAAQ,GAAG,eAAeA,WAAU,YAAY;AAC9E,SAAG,MAAM;AAAA,IACX;AACA,UAAM;AAAA,EACR;AAGA,MAAI,SAAS;AAEb,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,QAAuB;AAC3B,UAAI,OAAQ;AACZ,eAAS;AAGT,UAAI,GAAG,eAAeA,WAAU,QAAQ,GAAG,eAAeA,WAAU,YAAY;AAC9E,WAAG,MAAM;AAAA,MACX;AAGA,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,cAAM,QAAQ,WAAW,SAAS,GAAI;AACtC,WAAG,KAAK,SAAS,MAAM;AACrB,uBAAa,KAAK;AAClB,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":["path","body","state","state","state","path","fs","url","path","headers","body","path","fs","path","fs","path","fs","fs","url","path","fs","path","ts","fs","path","path","fs","ts","ts","ts","ts","ts","ts","path","ts","unionInner","path","path","fs","path","path","path","fs","path","fs","fg","ts","path","fs","ts","path","fs","path","fs","ts","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","path","fs","path","fs","path","Readable","path","response","DEFAULT_BODY_LIMIT","cors","helmet","path","fs","WebSocket","msg"]}
1
+ {"version":3,"sources":["../src/response/bufferedBody.ts","../src/utils/isPlainObject.ts","../src/utils/stringifyJson.ts","../src/runtime/sse.ts","../src/errors/FaapiError.ts","../src/errors/httpErrors.ts","../src/response/responseFormatter.ts","../src/logger/formatEntry.ts","../src/logger/logger.ts","../src/utils/queryToObject.ts","../src/utils/readResource.ts","../src/utils/prodPaths.ts","../src/runtime/createContext.ts","../src/response/pendingMeta.ts","../src/response/toResponse.ts","../src/injection/resolveInjection.ts","../src/task/taskRegistry.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/router/sortRoutes.ts","../src/cli/generateSchemaFiles.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/generateZodArtifacts.ts","../src/utils/atomicWrite.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/mergeVary.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 * 缓冲型 body 的 Response 标记(side-channel,与 pendingMeta 同一模式)\n *\n * `new Response(string | Buffer | Uint8Array)` 的 body 是**有限内存型**——发送层\n * 可整体读出后 `res.end` 直写,免去 `Readable.fromWeb + pipe` 的全套流机器(每响应\n * 数个中间流对象,JSON 响应的常态路径)。活跃流(SSE、handler 自建 ReadableStream)\n * 不能这样读——整体缓冲会破坏流式语义并可能永久挂起,但仅凭 Response 对象无法区分\n * 两种形态,因此在构造点标记。\n *\n * WeakSet:Response 被丢弃后标记自动可 GC,无手动清理。\n */\nconst bufferedBodyResponses = new WeakSet<Response>();\n\n/** 标记一个 body 为有限内存型(构造点调用;Response 已带标记时重复调用安全) */\nexport function markBufferedBody(response: Response): void {\n bufferedBodyResponses.add(response);\n}\n\n/** 查询 Response 是否带缓冲型 body 标记(发送层快路径判定用) */\nexport function hasBufferedBody(response: Response): boolean {\n return bufferedBodyResponses.has(response);\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 { isPlainObject } from './isPlainObject';\n\n/**\n * 框架统一 JSON 序列化出口:JSON 原生类型之外的一切值转换为可逆的原生表示。\n *\n * 原生 `JSON.stringify` 对规范外类型的行为不可接受:BigInt 抛 TypeError(响应 500)、\n * Map/Set/RegExp 静默序列化为 \"{}\"(丢数据)、NaN/±Infinity 静默变 null(丢信息)。\n * 设计契约是「线上格式即契约」:序列化把规范外类型转成可逆的原生表示(Date → 毫秒\n * 时间戳、BigInt → 字符串、Map → entries 数组、Set → 数组),客户端按转换后类型\n * 消费或自行还原,不提供序列化之外的第二条数据通路。完整转换表见 ./stringifyJson.md。\n *\n * 实现为「预变换值树 + 原生 stringify」而非 replacer——JSON.stringify 的 replacer\n * 在 toJSON 之后运行,拦不到 Date(此时已是 ISO 字符串)。预变换规则:\n *\n * - Date(含子类)→ `getTime()` 毫秒时间戳\n * - BigInt → `toString()` 字符串\n * - Map → entries 数组、Set → 值数组(键值/元素递归转换)\n * - 非有限 number(NaN/±Infinity)→ `\"NaN\"` / `\"±Infinity\"` 字符串\n * - RegExp → `\"/source/flags\"` 字符串\n * - 带 toJSON 的对象 → 递归转换其返回值(与原生语义对齐,且 toJSON 结果中的\n * Date/BigInt 也被转换)\n * - plain object / array → 递归重建(查找嵌套的特殊类型)\n * - 其他对象(类实例、URL 等)→ 原样返回,由原生 stringify 处理(保留 toJSON 语义)\n * - 循环引用 → 抛 TypeError(结构错误显式失败,不静默产出坏 JSON);非循环的\n * 共享引用不受影响\n */\nexport function stringifyJson(value: unknown): string {\n // 快路径:整棵值树均为 JSON 原生类型(绝大多数响应的形态)时零分配直通原生\n // stringify。慢路径对 plain object/array 无条件递归重建——每个响应都要付出一次\n // 全量深拷贝(GC 压力翻倍);检测本身零分配(只做类型判断),无特殊类型时省掉\n // 整棵树的重建。检测遇循环引用与慢路径同语义抛 TypeError\n if (!needsConversion(value, new Set<object>())) {\n return JSON.stringify(value);\n }\n return JSON.stringify(convertValue(value, new Set<object>()));\n}\n\n/**\n * 检测值树是否需要转换(含 Date/BigInt/Map/Set/NaN/RegExp/toJSON 时走慢路径重建)\n *\n * 与 convertValue 相同的遍历结构与循环引用检测(ancestors),但零分配——\n * 只做类型判断,不重建任何对象/数组。\n */\nfunction needsConversion(value: unknown, ancestors: Set<object>): boolean {\n if (value === null) return false;\n const type = typeof value;\n if (type === 'bigint') return true;\n if (type === 'number') return !Number.isFinite(value as number);\n if (type !== 'object') return false;\n\n const obj = value as object;\n if (ancestors.has(obj)) {\n throw new TypeError('[faapi] Converting circular structure to JSON');\n }\n if (obj instanceof Date || obj instanceof Map || obj instanceof Set || obj instanceof RegExp) {\n return true;\n }\n // 带 toJSON 的对象走慢路径:其返回值中的特殊类型也要转换\n if (typeof (obj as { toJSON?: unknown }).toJSON === 'function') return true;\n\n if (Array.isArray(obj)) {\n ancestors.add(obj);\n for (let i = 0; i < obj.length; i++) {\n if (needsConversion(obj[i], ancestors)) {\n ancestors.delete(obj);\n return true;\n }\n }\n ancestors.delete(obj);\n return false;\n }\n if (isPlainObject(obj)) {\n ancestors.add(obj);\n // for-in + hasOwnProperty 避免分配键数组(Object.keys/entries 每节点一个数组);\n // 可枚举性与 getters 的调用语义与 Object.entries 一致\n for (const key in obj) {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;\n if (needsConversion((obj as Record<string, unknown>)[key], ancestors)) {\n ancestors.delete(obj);\n return true;\n }\n }\n ancestors.delete(obj);\n return false;\n }\n // 其他对象(类实例、URL 等):原样序列化,无需转换\n return false;\n}\n\n/** 递归转换值树中的规范外类型(ancestors 做循环引用检测) */\nfunction convertValue(value: unknown, ancestors: Set<object>): unknown {\n // 原始类型:bigint 转字符串,非有限 number(NaN/±Infinity)转字符串,其余原样\n if (value === null) return value;\n const type = typeof value;\n if (type === 'bigint') return (value as bigint).toString();\n if (type === 'number') return Number.isFinite(value as number) ? value : String(value);\n if (type !== 'object') return value;\n\n const obj = value as object;\n if (ancestors.has(obj)) {\n throw new TypeError('[faapi] Converting circular structure to JSON');\n }\n\n // Date(含子类)→ 毫秒时间戳。在 toJSON 检查之前:Date 自带 toJSON(ISO 字符串),\n // 契约要求时间戳形态\n if (obj instanceof Date) return obj.getTime();\n // Map → entries 数组、Set → 值数组(键值/元素递归转换,保持与输入侧 zod schema\n // 的 coerceMap/coerceSet 还原约定可逆)\n if (obj instanceof Map) {\n ancestors.add(obj);\n const entries = Array.from(obj, ([k, v]) => [\n convertValue(k, ancestors),\n convertValue(v, ancestors),\n ]);\n ancestors.delete(obj);\n return entries;\n }\n if (obj instanceof Set) {\n ancestors.add(obj);\n const values = Array.from(obj, (v) => convertValue(v, ancestors));\n ancestors.delete(obj);\n return values;\n }\n if (obj instanceof RegExp) return obj.toString();\n\n // 带 toJSON 的对象:递归转换其返回值(Date/BigInt 等出现在 toJSON 结果中同样被转换)\n const toJSON = (obj as { toJSON?: unknown }).toJSON;\n if (typeof toJSON === 'function') {\n ancestors.add(obj);\n const result = convertValue((toJSON as () => unknown).call(obj), ancestors);\n ancestors.delete(obj);\n return result;\n }\n\n // plain object / array:递归重建,查找嵌套的特殊类型\n if (Array.isArray(obj)) {\n ancestors.add(obj);\n const arr = obj.map((item) => convertValue(item, ancestors));\n ancestors.delete(obj);\n return arr;\n }\n if (isPlainObject(obj)) {\n ancestors.add(obj);\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(obj)) {\n out[key] = convertValue(val, ancestors);\n }\n ancestors.delete(obj);\n return out;\n }\n\n // 其他对象(类实例、URL 等):原样返回,由原生 stringify 处理(保留 toJSON 语义、\n // 枚举自有属性等原生行为)\n return obj;\n}\n","/**\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/onClose/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、X-Accel-Buffering: no 头,状态码默认 200\n * (X-Accel-Buffering: no 让 nginx 按响应跳过 proxy_buffering,SSE 小包不被攒到流结束才下发;\n * 非 nginx 反代不识别该头,当普通自定义头透传)\n * - close 后再 send 静默忽略,避免 handler 异步流程中误写已关闭的流\n * - sendError 向流写入 event: error 后关闭,用于流式输出中报错的优雅终止\n * - 背压:send/sendRaw 保持同步;desiredSize 暴露缓冲状态,waitForDrain() 供\n * 生产循环在缓冲超水位时暂停(pull 钩子唤醒),防慢客户端内存无界增长\n *\n * 与 ctx 的集成:\n * - ctx.sse() 调用 createSseWriter(),并把 response 缓存到 ctx 内部字段\n * - 首次写入(send/sendRaw/sendError)经 onFirstWrite 触发 server 层提前接管钩子,\n * 响应立即接入底层连接(流式即产即达);无钩子场景回落为 handler 返回后由\n * invokeHandler 检查 ctx 持有的 SSE response,有则优先使用\n */\n\nimport { stringifyJson } from '../utils/stringifyJson';\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(BigInt 安全,见 utils/stringifyJson),多行每行加前缀\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 = stringifyJson(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 * ctx.sse() 的选项\n */\nexport interface SseOptions {\n /**\n * 声明流生命周期独立于 handler 返回(长连接订阅模式)\n *\n * `true` 时 handler 返回后框架不自动 close——handler 只负责把连接挂到推送源\n * (事件总线、change stream),注册完立即返回,连接长存直到显式 close()、\n * 客户端断开或 handler 抛错兜底。清理推送源用 `onClose(callback)`。\n * 默认 false(handler 返回时框架自动 close 兜底)。\n */\n keepOpen?: boolean;\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 /**\n * 注册流结束回调\n *\n * 显式 close、框架兜底 close、客户端断开、sendError 任一路径结束流时恰好触发\n * 一次(多个回调按注册顺序执行,回调自身抛错被忽略);注册时流已结束则立即触发。\n * keepOpen 模式下用于退订/清理推送源,防止客户端断开后订阅随重连累积泄漏。\n */\n onClose(callback: () => void): void;\n /** 流是否已关闭(handler 主动 close 或框架自动 close) */\n readonly closed: boolean;\n /** 客户端是否已断开(ReadableStream 被 cancel) */\n readonly aborted: boolean;\n /** 创建时是否声明了 `{ keepOpen: true }`(handler 返回后框架不自动 close) */\n readonly keepOpen: boolean;\n /**\n * 流缓冲背压状态(透传 controller.desiredSize)\n *\n * `null` = 流已关闭/断开;`<= 0` 表示消费慢于生产(缓冲超过高水位),生产循环\n * 应 `await waitForDrain()` 暂停,否则快生产者(LLM token 流)+ 慢客户端会让\n * 缓冲无界增长。`send`/`sendRaw` 保持同步不抛,背压响应由调用方自决。\n */\n readonly desiredSize: number | null;\n /**\n * 等待流缓冲排空(背压感知)\n *\n * 缓冲低于高水位(desiredSize > 0)时立即返回;否则挂起直到消费者拉取使缓冲\n * 排空(ReadableStream pull 钩子唤醒)、或流关闭/客户端断开(避免悬挂)。\n * 典型用法见 runtime/sse.md「背压」章节。\n */\n waitForDrain(): Promise<void>;\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 *\n * onFirstWrite:首次成功写入(send/sendRaw/sendError)时以 response 调用一次。\n * HTTP 服务场景由 ctx.sse() 接到 server 层的提前接管钩子——首次写入即把响应\n * 接入底层连接,流式期间字节即产即达(见 createServer.md「SSE 提前接管」)。\n *\n * keepOpen:声明后 writer.keepOpen 为 true,invokeHandler 在 handler 正常返回时\n * 跳过自动 close(长连接订阅模式);handler 抛错路径的兜底 close 不受影响。\n */\nexport function createSseWriter(\n options: SseOptions & { onFirstWrite?: (response: Response) => void } = {},\n): SseWriter {\n const { onFirstWrite, keepOpen = false } = options;\n const encoder = new TextEncoder();\n let controller: ReadableStreamDefaultController<Uint8Array> | null = null;\n let closed = false;\n let aborted = false;\n /** 首次写入回调是否已触发(幂等:多次写入只触发一次) */\n let firstWriteTriggered = false;\n /** waitForDrain 挂起中的等待方——消费者拉取(pull)/关闭/断开时唤醒 */\n let drainWaiters: Array<() => void> = [];\n /** onClose 注册的结束回调——任一路径结束流时逐个执行一次 */\n let closeCallbacks: Array<() => void> = [];\n /** 结束回调是否已执行(保证恰好一次:close/cancel 幂等路径不重复触发) */\n let closeCallbacksFired = false;\n\n const safeRunCallback = (callback: () => void): void => {\n try {\n callback();\n } catch {\n // 回调自身抛错不影响流收尾与其他回调(与 onError 钩子「自身抛错被忽略」同约定)\n }\n };\n\n const runCloseCallbacks = (): void => {\n if (closeCallbacksFired) return;\n closeCallbacksFired = true;\n const callbacks = closeCallbacks;\n closeCallbacks = [];\n for (const callback of callbacks) safeRunCallback(callback);\n };\n\n const resolveDrainWaiters = (): void => {\n const waiters = drainWaiters;\n drainWaiters = [];\n for (const resolve of waiters) resolve();\n };\n\n // 首次成功 enqueue 后触发(幂等)。放在 enqueue 之后:接管开始消费时首个\n // chunk 已在流中,字节顺序不因接管时机改变\n const triggerFirstWrite = (): void => {\n if (firstWriteTriggered) return;\n firstWriteTriggered = true;\n onFirstWrite?.(response);\n };\n\n // 高水位 16 个 chunk(SSE chunk 小):给消费者留缓冲余量,超过即 desiredSize <= 0,\n // 生产方可感知背压。无显式策略时默认 HWM=1,waitForDrain 过于激进\n const stream = new ReadableStream<Uint8Array>(\n {\n start(c) {\n controller = c;\n },\n // 消费者排空缓冲后再要数据 → 背压解除,唤醒 waitForDrain 等待方\n pull() {\n resolveDrainWaiters();\n },\n cancel() {\n // 客户端断开连接(cancel ReadableStream)\n aborted = true;\n closed = true;\n controller = null;\n // 结束回调与等待方立即放行(清理出口 + 生产循环靠 aborted 退出)\n runCloseCallbacks();\n resolveDrainWaiters();\n },\n },\n { highWaterMark: 16 },\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 // nginx 认到该头后按响应跳过 proxy_buffering;其他反代/客户端忽略,无副作用\n 'X-Accel-Buffering': 'no',\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 triggerFirstWrite();\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 triggerFirstWrite();\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 triggerFirstWrite();\n } finally {\n writer.close();\n }\n },\n\n close(): void {\n if (closed) return;\n closed = true;\n // 结束回调先于 controller.close:清理出口(退订/清定时器)在流收尾前执行\n runCloseCallbacks();\n // 关闭唤醒全部等待方(resolved 的 Promise 不会让生产循环悬挂)\n resolveDrainWaiters();\n if (controller) {\n try {\n controller.close();\n } catch {\n // controller 可能已关闭,忽略\n }\n controller = null;\n }\n },\n\n onClose(callback: () => void): void {\n // 流已结束(含 cancel 置位的 aborted 路径):立即触发,保证恰好一次语义\n if (closeCallbacksFired) {\n safeRunCallback(callback);\n return;\n }\n closeCallbacks.push(callback);\n },\n\n get closed(): boolean {\n return closed;\n },\n\n get desiredSize(): number | null {\n return controller ? controller.desiredSize : null;\n },\n\n waitForDrain(): Promise<void> {\n // 已关闭/断开:立即返回(生产循环自行感知 closed/aborted 退出)\n if (closed || !controller) return Promise.resolve();\n // 缓冲低于高水位:背压未触发,立即返回\n if ((controller.desiredSize ?? 0) > 0) return Promise.resolve();\n return new Promise<void>((resolve) => {\n drainWaiters.push(resolve);\n });\n },\n\n get aborted(): boolean {\n return aborted;\n },\n\n get keepOpen(): boolean {\n return keepOpen;\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 { markBufferedBody } from './bufferedBody';\nimport { FaapiError } from '../errors/FaapiError';\nimport { ValidationError, MethodNotAllowedError, PayloadTooLargeError } from '../errors/httpErrors';\nimport { stringifyJson } from '../utils/stringifyJson';\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,不包外层(BigInt 安全序列化,见 utils/stringifyJson) */\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 const response = new Response(stringifyJson(body), { status, headers });\n markBufferedBody(response);\n return response;\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 // 通用 fail 分支:FaapiError 家族共享同一 failFn 调用形态,差异只在附加 headers\n // 与额外字段——此前四个同构 if 各自复制 failFn 调用,收敛为此处单一实现\n const buildFailBody = (e: FaapiError): ReturnType<typeof failFn> =>\n failFn({ status: e.statusCode, code: e.code, message: e.message });\n\n if (error instanceof ValidationError) {\n const body = buildFailBody(error);\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 return jsonOk(buildFailBody(error), error.statusCode, {\n Allow: error.allowedMethods.join(', '),\n });\n }\n\n if (error instanceof PayloadTooLargeError) {\n return jsonOk(buildFailBody(error), error.statusCode);\n }\n\n if (error instanceof FaapiError) {\n return jsonOk(buildFailBody(error), 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 { LogEntry } from './loggerTypes';\n\n/**\n * 默认文本格式化(logger.md):`[ISO] LEVEL [scope] message fields-JSON`\n *\n * console sink 与文件 sink(fileSink.ts)共用同一格式——同一条日志在控制台与\n * 文件中内容一致,采集侧无需区分来源。独立成模块避免 fileSink ← logger 的\n * 循环依赖(logger.ts 编排 fileSink,fileSink 只依赖格式化)。\n */\n\n/** fields 值序列化:Error 实例展开为 { name, message, stack }(pino err serializer 惯例),其余原样交给 JSON.stringify */\nfunction serializeFieldValue(value: unknown): unknown {\n if (value instanceof Error) {\n return { name: value.name, message: value.message, stack: value.stack };\n }\n return value;\n}\n\n/** 默认文本格式:[ISO] LEVEL [scope] message fields-JSON */\nexport function formatEntry(entry: LogEntry): string {\n let line = `[${entry.time}] ${entry.level.toUpperCase()} `;\n if (entry.scope) line += `[${entry.scope}] `;\n line += entry.message;\n if (entry.fields !== undefined) {\n const plain = Object.fromEntries(\n Object.entries(entry.fields).map(([k, v]) => [k, serializeFieldValue(v)]),\n );\n try {\n line += ` ${JSON.stringify(plain)}`;\n } catch (err) {\n // 循环引用等不可序列化 fields:降级为提示文本,日志调用不抛错(fallback.md)\n line += ` [unserializable fields: ${err instanceof Error ? err.message : String(err)}]`;\n }\n }\n return line;\n}\n","import type {\n LogConfig,\n LogEntry,\n LogLevel,\n LogSink,\n Logger,\n CreateLoggerOptions,\n} from './loggerTypes';\nimport { formatEntry } from './formatEntry';\nimport { createFileLogSink, type FileLogSinkHandle } from './fileSink';\n\n/**\n * 框架级结构化日志器(logger.md)——参考 egg.js 的单管道模型\n *\n * 一条日志管道、两个输出出口,各有独立阈值:\n * - 管道出口(文件 `dir` / 自定义 `sink`):`level` 阈值,不配不过滤(全量条目,\n * 分流由文件布局或下游 sink 决定)\n * - console 出口:`consoleLevel` 阈值,不配 `'info'`(`false` 关闭 console)\n *\n * 请求日志(middleware/logger.ts)无条件并入管道(`accessLog: false` 关闭),\n * `config.logger` 独立配置已废除——单一 `config.log` 入口管全部输出。\n *\n * 全局配置为进程级资源(stdout/文件本就进程唯一),由 `configureLogging` 设置\n * (createAppBase 启动时读 config.log 调用),多 app 同进程后启动覆盖先启动;\n * 状态经 globalThis 承载跨模块实例共享(dev 下 CLI bundle 与主入口是两份模块副本,\n * 模块级变量会导致业务侧 createLogger 看不到 CLI 侧配置的管道)。\n *\n * 日志调用永不抛错:fields 序列化失败降级为提示文本(fallback.md)。\n */\n\nconst LEVEL_RANK: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\n\nconst VALID_LEVELS: readonly LogLevel[] = ['debug', 'info', 'warn', 'error'];\n\nfunction assertLogLevel(value: unknown, source: string): asserts value is LogLevel {\n if (typeof value !== 'string' || !VALID_LEVELS.includes(value as LogLevel)) {\n throw new Error(\n `[faapi] Invalid log level from ${source}: ${String(value)}. Expected one of: ${VALID_LEVELS.join(', ')}.`,\n );\n }\n}\n\n/**\n * 全局日志管道状态\n *\n * level 为 null 表示未配置——管道出口不过滤(全量条目进文件/sink)。\n * consoleLevel 为 null 表示未配置——console 出口默认 'info';false 关闭 console。\n * sink/fileSink 互斥(configureLogging 校验)。disabled 对应 `config.log: false`\n * (管道与请求日志全部静默)。\n */\ninterface LoggingState {\n level: LogLevel | null;\n consoleLevel: LogLevel | false | null;\n sink: LogSink | null;\n fileSink: FileLogSinkHandle | null;\n stdout: boolean;\n accessLog: boolean;\n disabled: boolean;\n}\n\n/**\n * 管道状态的 globalThis key(`Symbol.for` 创建,跨模块实例命中同一键)\n *\n * 管道状态是进程级资源(stdout/文件本就进程唯一),但不能放模块级变量——dev 模式下\n * 框架存在两个模块实例:`faapi` CLI 跑在 `dist/cli/index.js`(tsup 打包内联框架代码),\n * 业务模块经包主入口加载 `dist/index.js`,两份副本 module cache 独立。模块级变量会让\n * `configureLogging`(CLI 侧 createAppBase 调用)只作用于 CLI 副本,业务 `createLogger`\n * (主入口副本)看到的 fileSink 恒为 null,dev 下 `config.log.dir` 的业务日志不落盘。\n * 与 `getApp` 单例同模式:globalThis + `Symbol.for` 键跨副本共享,prod(单实例)行为无变化。\n */\nconst LOGGING_STATE_KEY = Symbol.for('faapi.logger.state');\n\nfunction createDefaultLoggingState(): LoggingState {\n return {\n level: null,\n consoleLevel: null,\n sink: null,\n fileSink: null,\n stdout: true,\n accessLog: true,\n disabled: false,\n };\n}\n\n/** 全局日志管道状态(globalThis 承载,跨模块实例共享;首次访问时懒初始化) */\nfunction getLoggingState(): LoggingState {\n const g = globalThis as Record<symbol, LoggingState | undefined>;\n let state = g[LOGGING_STATE_KEY];\n if (!state) {\n state = createDefaultLoggingState();\n g[LOGGING_STATE_KEY] = state;\n }\n return state;\n}\n\n/**\n * 设置全局日志配置(进程级,`createAppBase` 启动时以 `config.log` 调用)\n *\n * - `false`:全部静默(含 error 与请求日志,测试降噪场景)\n * - `true` / `{}`:console 出口默认 info,管道出口不过滤,无文件输出\n * - `{ level, consoleLevel, sink, dir, stdout, accessLog }`:egg 模型精细配置\n * (level 管管道出口、consoleLevel 管 console 出口;sink 与 dir 互斥)\n * - `undefined`:重置为默认(编程式多 app 切换全局配置用)\n *\n * LOG_LEVEL env 仅作为管道 level 的环境来源(consoleLevel 无 env)——本函数是\n * env 读取点,非法值启动期报错(fail fast)。\n */\nexport function configureLogging(config?: LogConfig | boolean | undefined): void {\n const state = getLoggingState();\n const objectConfig = typeof config === 'object' ? config : {};\n if (objectConfig.sink && objectConfig.dir) {\n throw new Error(\n '[faapi] config.log.sink and config.log.dir are mutually exclusive: pick one output pipeline (custom sink OR built-in file logs).',\n );\n }\n // 旧文件流先关(切配置/重置不泄漏 fd;close 异步收尾,不影响新配置立即生效)\n void state.fileSink?.close();\n\n state.disabled = config === false;\n state.sink = objectConfig.sink ?? null;\n state.accessLog = objectConfig.accessLog ?? true;\n state.stdout = objectConfig.stdout ?? true;\n state.fileSink = objectConfig.dir\n ? createFileLogSink({ dir: objectConfig.dir, splitByLevel: objectConfig.splitByLevel })\n : null;\n if (objectConfig.consoleLevel !== undefined) {\n if (objectConfig.consoleLevel !== false) {\n assertLogLevel(objectConfig.consoleLevel, 'config.log.consoleLevel');\n }\n state.consoleLevel = objectConfig.consoleLevel;\n } else {\n state.consoleLevel = null;\n }\n if (objectConfig.level !== undefined) {\n assertLogLevel(objectConfig.level, 'config.log.level');\n state.level = objectConfig.level;\n } else if (process.env.LOG_LEVEL !== undefined) {\n assertLogLevel(process.env.LOG_LEVEL, 'LOG_LEVEL env');\n state.level = process.env.LOG_LEVEL as LogLevel;\n } else {\n state.level = null;\n }\n}\n\n/**\n * 管道阈值(文件/sink 收到的条目);undefined = 不过滤(未配置 level 时全量放行,\n * 分流由文件布局或下游 sink 决定)\n */\nfunction pipelineThreshold(): LogLevel | undefined {\n return getLoggingState().level ?? undefined;\n}\n\n/** console 出口阈值;false = console 关闭 */\nfunction consoleThreshold(): LogLevel | false {\n return getLoggingState().consoleLevel ?? 'info';\n}\n\n/** 阈值判定(threshold undefined = 不过滤) */\nfunction belowThreshold(level: LogLevel, threshold: LogLevel | undefined): boolean {\n return threshold !== undefined && LEVEL_RANK[level] < LEVEL_RANK[threshold];\n}\n\n/**\n * 当前生效的管道级别(任务隔离派发用:级别随 workerData 下发做 worker 侧预过滤,\n * 宿主侧 writeLogEntry 再次过滤——两级一致,派发后级别变更以宿主为准;\n * undefined = 不过滤,worker 侧同样放行全量)\n */\nexport function getEffectiveLogLevel(): LogLevel | undefined {\n return pipelineThreshold();\n}\n\nconst CONSOLE_METHOD: Record<LogLevel, 'debug' | 'info' | 'warn' | 'error'> = {\n debug: 'debug',\n info: 'info',\n warn: 'warn',\n error: 'error',\n};\n\n/** console 输出(对应级别方法,warn/error 走 stderr),受 consoleLevel 阈值过滤 */\nfunction consoleDispatch(entry: LogEntry): void {\n const threshold = consoleThreshold();\n if (threshold === false || belowThreshold(entry.level, threshold)) return;\n console[CONSOLE_METHOD[entry.level]](formatEntry(entry));\n}\n\n/**\n * 全局管道:disabled 检查 + 出口解析(各出口独立阈值——egg transport 模型)\n *\n * 优先级:全局 sink(显式接管,受 `level`,不经 console)→ 文件管道(`dir`,\n * 受 `level`;stdout 非 false 时 console 双写,受 `consoleLevel`)→ 纯 console\n * (受 `consoleLevel`)。`level` 与 `consoleLevel` 互不牵扯:文件可只存 error\n * 而.console 看全部。\n */\nfunction globalDispatch(entry: LogEntry): void {\n const state = getLoggingState();\n if (state.disabled) return;\n const pipeThreshold = pipelineThreshold();\n if (state.sink) {\n if (!belowThreshold(entry.level, pipeThreshold)) state.sink(entry);\n return;\n }\n if (state.fileSink) {\n if (!belowThreshold(entry.level, pipeThreshold)) state.fileSink.write(entry);\n if (state.stdout) consoleDispatch(entry);\n return;\n }\n consoleDispatch(entry);\n}\n\n/**\n * 写一条预构建的日志条目(直达全局管道,各出口按自身阈值过滤)\n *\n * 任务隔离执行桥接用:worker 内联日志器把条目作为纯数据 postMessage 回传宿主,\n * 宿主侧经本函数走统一管道(自定义 sink / 文件管道同样覆盖隔离任务)。\n */\nexport function writeLogEntry(entry: LogEntry): void {\n globalDispatch(entry);\n}\n\n/**\n * 刷盘文件日志缓冲(不结束流,flush 后可继续写)\n *\n * 优雅停机场景在 `lifecycle.onClose` 调用,确保缓冲中的条目落盘后再退出进程;\n * 测试场景断言文件内容前调用。无文件管道(未配置 `config.log.dir`)时为 no-op。\n */\nexport function flushLogging(): Promise<void> {\n const state = getLoggingState();\n return state.fileSink ? state.fileSink.flush() : Promise.resolve();\n}\n\n/**\n * 请求日志是否并入管道(middleware/logger.ts 默认输出决策用)\n *\n * 缺省并入(egg 模型:访问日志与业务日志同管道);`config.log.accessLog: false`\n * 或 `config.log: false` 时请求日志不输出。\n */\nexport function isAccessLogEnabled(): boolean {\n const state = getLoggingState();\n return !state.disabled && state.accessLog;\n}\n\n/**\n * 创建日志器\n *\n * @param scope 分类标签(如 'db'、'http'),默认无分类\n * @param options 实例级覆盖(level/sink/构造 fields)\n */\nexport function createLogger(scope?: string, options?: CreateLoggerOptions): Logger {\n const baseScope = scope;\n const baseFields = options?.fields;\n\n const write = (\n level: LogLevel,\n message: string,\n callFields: Record<string, unknown> | undefined,\n scopeSuffix: string | undefined,\n ): void => {\n // 实例级 level 是该 logger 的出口阈值;全局阈值在各出口独立判定(egg transport 模型)\n if (belowThreshold(level, options?.level)) return;\n const entry: LogEntry = {\n level,\n message,\n time: new Date().toISOString(),\n };\n const fullScope =\n scopeSuffix === undefined\n ? baseScope\n : baseScope === undefined\n ? scopeSuffix\n : `${baseScope}:${scopeSuffix}`;\n if (fullScope !== undefined) entry.scope = fullScope;\n if (baseFields !== undefined || callFields !== undefined) {\n entry.fields = { ...baseFields, ...callFields };\n }\n if (options?.sink) {\n // 实例级 sink:显式接管输出管道,不受全局 disabled / consoleLevel 影响\n options.sink(entry);\n } else {\n globalDispatch(entry);\n }\n };\n\n const makeLogger = (scopeSuffix: string | undefined): Logger => ({\n debug: (message, fields) => write('debug', message, fields, scopeSuffix),\n info: (message, fields) => write('info', message, fields, scopeSuffix),\n warn: (message, fields) => write('warn', message, fields, scopeSuffix),\n error: (message, fields) => write('error', message, fields, scopeSuffix),\n child: (childScope) =>\n makeLogger(scopeSuffix === undefined ? childScope : `${scopeSuffix}:${childScope}`),\n });\n\n return makeLogger(undefined);\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 */\n/**\n * 同一 URLSearchParams 实例的解析结果缓存\n *\n * GET 请求每请求会解析两次 query(resolveInput 组装 ctx 输入 + injectParams 注入\n * `query` 参数)——ctx.query 是 createContext 创建的同一 URLSearchParams 实例,\n * WeakMap 按实例缓存后第二次解析直接命中。实例随请求被 GC,无跨请求泄漏。\n */\nconst queryCache = new WeakMap<URLSearchParams, Record<string, unknown>>();\n\nexport function queryToObject(params: URLSearchParams): Record<string, unknown> {\n const cached = queryCache.get(params);\n if (cached) return cached;\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 queryCache.set(params, result);\n return result;\n}\n","import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { isInsideDir, toRealPath } from './prodPaths';\n\n/**\n * 当前 app 的 resources 目录绑定(进程级,globalThis + Symbol.for 承载)\n *\n * 为什么用 globalThis 而非模块级变量(同 appSingleton 的取舍):\n * ① Next.js Turbopack dev runtime 与主进程是两套 module cache,须跨实例共享;\n * ② 任务隔离 worker 的入口 bundle(workerEntry.ts)与 index bundle 是两份代码\n * 副本,由入口内联同名 symbol key 播种(两处字面量需一致,契约测试锚定)。\n * 单 app 强制(createAppBase 检测到存活 app 即抛错)保证绑定唯一且无歧义。\n */\nconst ACTIVE_RESOURCES_DIR_KEY = Symbol.for('faapi.resources.dir');\n\nfunction getActiveResourcesDir(): string | null {\n return (globalThis as Record<symbol, string | undefined>)[ACTIVE_RESOURCES_DIR_KEY] ?? null;\n}\n\n/**\n * 绑定/解绑当前 app 的 resources 目录(框架内部接线,不进公开导出面)\n *\n * 调用点:createAppBase 启动时绑定、app.close() 解绑、taskWorker 从快照播种\n * (内联 globalThis 写入)、createTestContext 按选项绑定(测试 handler 读资源)。\n */\nexport function setActiveResourcesDir(dir: string | null): void {\n if (dir === null) {\n delete (globalThis as Record<symbol, string | undefined>)[ACTIVE_RESOURCES_DIR_KEY];\n } else {\n (globalThis as Record<symbol, string | undefined>)[ACTIVE_RESOURCES_DIR_KEY] = dir;\n }\n}\n\n/**\n * 校验 relativePath 不越出 resources 目录,返回实际可读的目标绝对路径\n *\n * 两层校验:\n * 1. 字符串层——path.resolve 后必须仍在 resources 内(拦绝对路径与 `..` 穿越)\n * 2. 符号链接层——目标真实路径(realpath)必须仍在 resources 内(拦 symlink 指向\n * 目录外);目标不存在时 toRealPath 回退原路径,缺失由 readFile 抛自然 ENOENT\n */\nfunction resolveResourceTarget(resourcesDir: string, relativePath: string): string {\n if (typeof relativePath !== 'string' || relativePath === '') {\n throw new Error('[faapi] readResource: relativePath must be a non-empty string');\n }\n\n const root = toRealPath(path.resolve(resourcesDir));\n const resolved = path.resolve(root, relativePath);\n if (!isInsideDir(resolved, root)) {\n throw new Error(\n `[faapi] readResource: \"${relativePath}\" escapes the resources directory (absolute paths and \"..\" are not allowed)`,\n );\n }\n\n const realTarget = toRealPath(resolved);\n if (!isInsideDir(realTarget, root)) {\n throw new Error(\n `[faapi] readResource: \"${relativePath}\" resolves outside the resources directory via a symlink`,\n );\n }\n return realTarget;\n}\n\n/**\n * 读取当前 app 的运行时资源内容(参数为相对路径,只能读 resources 内的文件)\n *\n * 唯一读取入口:HTTP/WS handler、任务、插件、lifecycle 钩子、编程式调用全部用\n * 本函数——resources 目录在 app 启动时绑定(createAppBase),调用方不传路径根。\n * 越界校验见 resolveResourceTarget——绝对路径、`..` 穿越、符号链接逃逸均显式抛错,\n * resources 内部的合法软链不误伤。\n */\nexport function readResource(relativePath: string): Promise<Buffer>;\nexport function readResource(relativePath: string, encoding: BufferEncoding): Promise<string>;\nexport async function readResource(\n relativePath: string,\n encoding?: BufferEncoding,\n): Promise<Buffer | string> {\n const resourcesDir = getActiveResourcesDir();\n if (!resourcesDir) {\n throw new Error(\n '[faapi] readResource: no active app in this process — resources are bound at app startup (createAppBase); testing direct-call can pass `resourcesDir` to createTestContext to bind',\n );\n }\n const target = resolveResourceTarget(resourcesDir, relativePath);\n return encoding === undefined ? fs.readFile(target) : fs.readFile(target, encoding);\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 { FaapiContext, ResponseMeta, CookieOptions, FailOptions } from './contextTypes';\nimport { markBufferedBody } from '../response/bufferedBody';\nimport { createSseWriter, type SseOptions, type SseWriter } from './sse';\nimport { wrapOkResult, formatFailResponse, jsonOk } from '../response/responseFormatter';\nimport { createLogger } from '../logger/logger';\nimport { queryToObject } from '../utils/queryToObject';\nimport { setActiveResourcesDir } from '../utils/readResource';\n\n/**\n * 解析请求 ID:请求头 `x-request-id` 第一段(逗号分隔取首段,网关透传场景跨服务\n * 串联),缺失或空白时 crypto.randomUUID() 生成(参考 Fastify genReqId 默认语义)\n */\nfunction resolveRequestId(request: Request): string {\n const header = request.headers.get('x-request-id');\n const first = header?.split(',')[0]?.trim();\n if (first) return first;\n return crypto.randomUUID();\n}\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 // 请求级日志器:scope http,字段自动携带 requestId/method/path(业务日志与请求\n // 日志经 requestId 关联);每请求构造一次,child 派生更细分类时字段继承\n const requestId = resolveRequestId(request);\n const log = createLogger('http', {\n fields: { requestId, method: request.method, path: url.pathname },\n });\n\n const ctx = {\n requestId,\n log,\n request,\n params,\n rawParams: params,\n // ctx.query 为对象口径(createContext 挂原始 string 值对象;handler 声明\n // query 类型时管线校验后替换为转换值)。原始 URLSearchParams 恒在 rawQuery。\n query: queryToObject(url.searchParams),\n rawQuery: 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 const response = new Response(html, {\n status: status ?? 200,\n headers,\n });\n markBufferedBody(response);\n return response;\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 推送事件。HTTP 服务场景下首次\n * 写入(send/sendRaw/sendError)即触发 server 层提前接管钩子,把 writer.response\n * 接入底层连接——流式期间字节即产即达(见 createServer.md「SSE 提前接管」)。\n * 无钩子场景(app.inject / 测试直调 invokeHandler)回落为 handler 返回后交出。\n *\n * `{ keepOpen: true }` 声明流生命周期独立于 handler 返回:handler 返回后框架\n * 不自动 close(长连接订阅模式,清理推送源用 writer.onClose);handler 抛错\n * 的兜底 close 不受影响。详见 runtime/sse.md「长连接订阅模式」。\n *\n * 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。\n */\n sse(options?: SseOptions): SseWriter {\n const writer = createSseWriter({\n keepOpen: options?.keepOpen,\n onFirstWrite: (response) => {\n (\n ctx as FaapiContext & { __earlyRespond?: (response: Response) => void }\n ).__earlyRespond?.(response);\n },\n });\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 // 任务客户端:同一 app 实例的 TaskClient(入队/查询),ctx.tasks 与\n // `tasks` 参数注入 / app.tasks 指向同一队列\n ctx.tasks = registries.taskHandle.get(ctx) as FaapiContext['tasks'];\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 resourcesDir,\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 // 绑定全局 readResource 读取根(免传参 readResource 依赖进程级绑定):测试\n // handler 调 readResource('x.md') 时按此绑定解析。测试内多次构造后调覆盖先调;\n // 真实 app 的绑定/解绑由 createAppBase / app.close() 接线。仅绑定——ctx 上\n // 无 resourcesDir 数据字段(已删除,读取统一走 readResource)\n if (resourcesDir) {\n setActiveResourcesDir(resourcesDir);\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 /**\n * 运行时资源根目录(可选)\n *\n * 传入时同时做两件事:绑定全局 readResource 读取根(handler 直调\n * `readResource('x.md')` 依赖);ctx 上无 resourcesDir 数据字段(已删除)\n */\n resourcesDir?: string;\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","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 { stringifyJson } from '../utils/stringifyJson';\nimport type { ResponseMeta } from '../runtime/contextTypes';\nimport { deferMetaHeaders, isHeadersOnlyMeta } from './pendingMeta';\nimport { markBufferedBody } from './bufferedBody';\n\n/**\n * 将 handler 返回值统一转换为 Response\n *\n * 规则:\n * - Response 对象:原样返回\n * - ReadableStream:直接作为 body\n * - Buffer / Uint8Array:作为二进制 body\n * - 普通对象/数组:JSON.stringify(BigInt 安全,BigInt → 字符串),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 markBuffered(\n new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n }),\n );\n }\n if (value instanceof Uint8Array) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return markBuffered(\n new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n }),\n );\n }\n\n // 普通对象/数组:JSON.stringify(BigInt 安全,BigInt → 字符串),Content-Type: application/json\n if (isPlainObject(value) || Array.isArray(value)) {\n const body = stringifyJson(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return markBuffered(\n new Response(body, {\n status: meta?.status ?? 200,\n headers,\n }),\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 markBuffered(\n new Response(value, {\n status: meta?.status ?? 200,\n headers,\n }),\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 markBuffered(\n new Response(String(value), {\n status: meta?.status ?? 200,\n headers,\n }),\n );\n }\n\n // 其他类型 fallback:JSON.stringify(BigInt 安全)\n const body = stringifyJson(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return markBuffered(\n new Response(body, {\n status: meta?.status ?? 200,\n headers,\n }),\n );\n}\n\n/** 标记缓冲型 body 并原样返回(发送层快路径直写,见 bufferedBody.ts) */\nfunction markBuffered(response: Response): Response {\n markBufferedBody(response);\n return response;\n}\n","import ts from 'typescript';\n\n/**\n * 参数注入类型\n *\n * Phase 2.3 扩展 `agent` / `agents`:\n * - `agent` —— AgentHandle(由 `@faapi/agent` 插件工厂注入;调用时需显式传\n * `options.agent` 指定 agent 名,无默认 agent)\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 | 'tasks'\n | 'log'\n | 'rawQuery'\n | 'rawParams'\n | 'rawBody'\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 tasks: 'tasks', // 任务子系统:TaskClient(入队/查询)\n log: 'log', // 请求级日志器(ctx.log 同一实例:scope http,自动带 requestId 等字段)\n // raw 系:恒原始输入(无 schema、无校验、管线永不转换),与 query/params/body\n // 的「校验转换后」口径二分互补\n rawQuery: 'rawQuery', // URLSearchParams(原始查询参数)\n rawParams: 'rawParams', // Record<string, string>(URL 原始路径段)\n rawBody: 'rawBody', // string | undefined(请求体原始文本)\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","import type { TaskMetadata } from './taskTypes';\n\n/**\n * app 实例级任务注册表(与 tool/agent/skill registry 同构,方案 A 实例化)\n *\n * 任务清单来自编译期产物(faapi-tasks.js),reload 时整体替换(hydrate 语义)。\n * 每个 app 持有独立实例,多 app 同进程互不串台;详见 taskRegistry.md。\n */\nexport interface TaskRegistry {\n /** 全量替换(任务清单来自编译期产物,reload 时整体重新生成) */\n hydrate(tasks: TaskMetadata[]): void;\n /** 按任务名查找 */\n get(name: string): TaskMetadata | undefined;\n /** 所有已注册任务(副本) */\n list(): TaskMetadata[];\n clear(): void;\n}\n\nexport function createTaskRegistry(): TaskRegistry {\n let registry = new Map<string, TaskMetadata>();\n return {\n hydrate(tasks) {\n const next = new Map<string, TaskMetadata>();\n for (const task of tasks) {\n next.set(task.name, task);\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","import type { ToolMetadata } from '../ast/extractToolMetadata';\nimport type { AgentMetadata, AgentCore } from '../ast/extractAgentMetadata';\nimport type { FaapiContext } from '../runtime/contextTypes';\nimport type { TaskRegistry } from '../task/taskRegistry';\nimport { createTaskRegistry } from '../task/taskRegistry';\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,声明文件定位) */\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// ─── Task handle 工厂 ────────────────────────────────────────────────\n\n/** task 客户端工厂函数(由 createAppBase 注册,返回 TaskClient 门面) */\nexport type TaskHandleFactory = (ctx: FaapiContext) => unknown;\n\nexport interface TaskHandleStore {\n /** 注册工厂(null 清理);二次注册覆盖 */\n register(factory: TaskHandleFactory | null): void;\n /** 工厂已注册时返回 TaskClient,未注册返回 undefined */\n get(ctx: FaapiContext): unknown;\n clear(): void;\n}\n\nexport function createTaskHandleStore(): TaskHandleStore {\n let currentFactory: TaskHandleFactory | 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 task: TaskRegistry;\n agentHandle: AgentHandleStore;\n taskHandle: TaskHandleStore;\n}\n\n/**\n * 任务侧注册表只读视图(AppRegistries 的查询投影)\n *\n * 注入任务执行上下文(`TaskContext.registries`)——任务执行侧不在 handler 请求链路上\n * (拿不到 `FaapiContext.registries`),`getApp()` 在隔离 worker 线程内也不可用\n * (globalThis 独立)。刻意不暴露 `hydrate`/`clear` 写接口:任务不是注册表的所有者。\n */\nexport interface TaskRegistriesView {\n agent: Pick<\n AgentRegistry,\n | 'getAgent'\n | 'getAgentEntry'\n | 'listAgents'\n | 'asTool'\n | 'resolveAgentTools'\n | 'resolveSubAgents'\n >;\n tool: Pick<ToolRegistry, 'get' | 'list'>;\n skill: Pick<SkillRegistry, 'get' | 'list'>;\n}\n\n/** 从 AppRegistries 构造任务侧只读视图(活引用——进程内执行路径直接使用) */\nexport function createTaskRegistriesView(registries: AppRegistries): TaskRegistriesView {\n return {\n agent: {\n getAgent: (name) => registries.agent.getAgent(name),\n getAgentEntry: (name) => registries.agent.getAgentEntry(name),\n listAgents: () => registries.agent.listAgents(),\n asTool: (name) => registries.agent.asTool(name),\n resolveAgentTools: (name) => registries.agent.resolveAgentTools(name),\n resolveSubAgents: (name) => registries.agent.resolveSubAgents(name),\n },\n tool: {\n get: (name) => registries.tool.get(name),\n list: () => registries.tool.list(),\n },\n skill: {\n get: (name) => registries.skill.get(name),\n list: () => registries.skill.list(),\n },\n };\n}\n\n/** 空视图(`createTaskQueue` 未传 registries 时的兜底——直接构造队列的测试/嵌入场景) */\nexport function createEmptyTaskRegistriesView(): TaskRegistriesView {\n return {\n agent: {\n getAgent: () => undefined,\n getAgentEntry: () => undefined,\n listAgents: () => [],\n asTool: () => undefined,\n resolveAgentTools: () => [],\n resolveSubAgents: () => [],\n },\n tool: { get: () => undefined, list: () => [] },\n skill: { get: () => undefined, list: () => [] },\n };\n}\n\n/** 创建一套 app 级注册表(`createAppBase` 每次调用创建独立实例) */\nexport function createAppRegistries(): AppRegistries {\n const tool = createToolRegistry();\n const agent = createAgentRegistry(tool);\n const skill = createSkillRegistry();\n const task = createTaskRegistry();\n const agentHandle = createAgentHandleStore();\n const taskHandle = createTaskHandleStore();\n return { tool, agent, skill, task, agentHandle, taskHandle };\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)。\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)\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 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 // ctx.query 与管线校验挂载的是同一对象:声明 query 类型时为转换值\n //(number/boolean 字段已 coerce,未声明字段保留原始字符串),未声明时\n // 为原始 query 对象(createContext 挂载后管线不触及)\n return ctx.query;\n case 'params':\n // 路径参数:管线已按声明类型校验并回写 ctx.params(转换后的值),\n // 未声明 params 类型的路由保持原始字符串\n return ctx.params;\n // raw 系:恒原始,管线永不覆盖(ctx 字段即注入值)\n case 'rawQuery':\n return ctx.rawQuery;\n case 'rawParams':\n return ctx.rawParams;\n case 'rawBody':\n return ctx.rawBody;\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 // 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined\n case 'tasks':\n return ctx.registries ? ctx.registries.taskHandle.get(ctx) : undefined;\n // 请求级日志器:与 ctx.log 同一实例(scope http,自动带 requestId/method/path 字段)\n case 'log':\n return ctx.log;\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 * keepOpen 声明豁免返回路径的自动 close——流生命周期独立于 handler 返回\n * (长连接订阅模式,handler 注册完推送源即返回,连接长存);handler 抛错\n * 路径(autoCloseSseOnError)不受 keepOpen 影响,仍无条件关闭。\n */\n const pickSseAndAutoClose = (): Response | null => {\n const sseWriter = (ctx as FaapiContext & { __sseWriter?: SseWriter }).__sseWriter;\n if (!sseWriter) return null;\n // 兜底:handler 未显式 close 时自动关闭(keepOpen 豁免)\n if (!sseWriter.keepOpen && !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 的统一尾部:注入 → SSE 优先 → 自动包装。\n // 无中间件与有中间件(洋葱 finalHandler)此前是两份相同逻辑的复制,收敛于此\n const executeAndFormat = 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 // 无中间件:直接执行 handler\n if (!middlewares || middlewares.length === 0) {\n return executeAndFormat();\n }\n\n // 有中间件:洋葱模型调度\n const finalHandler = executeAndFormat;\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 { 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 /** 全局中间件(外层洋葱) */\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,避免污染断言)\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 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,避免污染断言)\n const { server } = createServer({\n routes: sorted,\n rootDir,\n dist: schemaDist,\n cors,\n helmet,\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 fs from 'node:fs';\nimport { 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 * bustViteCache 分支的模块实例缓存:(filePath, mtime) → 模块\n *\n * 每请求 `?t=${Date.now()}` 新建 URL 会让 ESM 注册表为同一文件积累永不回收的模块\n * 实例(内存无界增长),且下游按模块对象做的 WeakMap 缓存(injection 分析等)\n * 永久 miss,每请求重跑 AST 解析。按 mtime 缓存后:同文件同 mtime 复用同一实例,\n * 文件重编译(mtime 变化)自动失效换新,无需显式清理(条目按路径收敛于项目规模)。\n */\nconst bustCache = new Map<string, { mtimeMs: number; module: Record<string, unknown> }>();\n\n/** cache-bust 单调计数:同毫秒内多次失效加载也要生成不同 URL(Date.now 精度不足) */\nlet bustCounter = 0;\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 * (filePath, mtime) 缓存复用:同文件同 mtime 返回同一实例(下游 WeakMap 缓存可命中),\n * 文件重编译后自动失效。\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 // 按 (filePath, mtime) 复用模块实例——每请求新建 URL 是内存泄漏 + 缓存击穿\n let mtimeMs = 0;\n try {\n mtimeMs = fs.statSync(filePath).mtimeMs;\n } catch {\n // stat 失败按 mtime 0 走 import,模块加载按路径抛真实错误\n }\n const cached = bustCache.get(filePath);\n if (cached && cached.mtimeMs === mtimeMs) {\n return cached.module;\n }\n let url = pathToFileURL(filePath).href;\n url += `?t=${Date.now()}-${++bustCounter}`;\n const module = (await import(url)) as Record<string, unknown>;\n bustCache.set(filePath, { mtimeMs, module });\n return module;\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 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 type { RouteManifest, WsRouteManifest } from '../router/routeTypes';\nimport type { RouteSchemaSource } from './collectRouteSchemaSources';\nimport type { RuntimeType } from '../ast/resolveTypeNode';\nimport { collectRouteSchemaSources } from './collectRouteSchemaSources';\nimport {\n generateZodSchemaSourceParts,\n usesCoerceHelpers,\n HELPERS_FILENAME,\n} from '../ast/generateZodSchema';\nimport { generateZodArtifacts } from './generateZodArtifacts';\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 * @param wsRoutes WS 路由清单(可选)——为声明了约定 interface(Query/Params)的\n * WS 路由生成 WSQuery / WSParams schema(握手校验转换用)\n */\nexport async function generateSchemaFiles(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n wsRoutes?: WsRouteManifest,\n): Promise<void> {\n if (routes.length === 0 && (wsRoutes?.length ?? 0) === 0) return;\n\n const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir, wsRoutes);\n\n await generateZodArtifacts(sources, {\n generateFileSource: generateSchemaFileSource,\n resolversByFile,\n rootDir,\n dist,\n });\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 * `sourceFile` 可选:未触达 checker 的纯语法遍历路径(binder 未运行)没有\n * parent 指针,`node.getSourceFile()` 返回 undefined,此时调用方需显式传入\n * `program.getSourceFile(filePath)` 的结果以携带位置。\n */\n static at(\n node: ts.Node,\n typeText: string,\n reason: string,\n sourceFile?: ts.SourceFile,\n ): SchemaExtractionError {\n const sf = sourceFile ?? node.getSourceFile();\n if (!sf) {\n return new SchemaExtractionError(typeText, reason);\n }\n const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));\n return new SchemaExtractionError(typeText, reason, undefined, {\n file: sf.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, hasBody } from '../runtime/inputType';\nimport { getSchemaName } from '../validator/schemaName';\nimport { analyzeInjectionInSourceFile } from '../injection/analyzeInjection';\nimport type { RouteManifest, WsRouteManifest } from '../router/routeTypes';\nimport path from 'node:path';\nimport ts from 'typescript';\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 */\n/**\n * 查找源文件中导出的约定命名类型(interface / type alias)\n *\n * WS handler 的输入声明是文件级约定(WS 函数形参是 ctx,不携带类型名):\n * handler.ts 导出 `WS` 且同文件声明 `export interface Query` / `export interface\n * Params`(type alias 同样生效)时,为该 WS 路由生成 WSQuery / WSParams schema。\n * 与 HTTP 导出共存时共享同一 interface(同文件同源)。\n */\nfunction findExportedTypeName(sourceFile: ts.SourceFile, name: string): boolean {\n for (const statement of sourceFile.statements) {\n const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : undefined;\n const isExported = modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);\n if (!isExported) continue;\n if (\n (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) &&\n statement.name?.text === name\n ) {\n return true;\n }\n }\n return false;\n}\n\nexport function collectRouteSchemaSources(\n routes: RouteManifest,\n rootDir?: string,\n wsRoutes?: WsRouteManifest,\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 // WS 路由文件:约定 interface(Query/Params)收集进同一批 Program(WS-only\n // 文件不在 HTTP 集合里,需要单独建 Program 与解析器)\n const wsFiles: Array<{ filePath: string; urlPath: string }> = [];\n for (const wsRoute of wsRoutes ?? []) {\n wsFiles.push({\n filePath: rootDir ? path.resolve(rootDir, wsRoute.filePath) : wsRoute.filePath,\n urlPath: wsRoute.urlPath,\n });\n }\n\n // 批量共享 Program:同一次提取只创建一个 Program,避免逐文件全量解析\n const allFiles = [...new Set([...methodsByFile.keys(), ...wsFiles.map((f) => f.filePath)])];\n const programByFile = createPrograms(allFiles);\n\n // 每个文件一个惰性解析器:入口类型立即解析,ref 按需解析(缓存幂等)\n const resolversByFile = new Map<string, LazyTypeResolver>();\n for (const filePath of allFiles) {\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 // params 形参:handler 声明了 `params: XxxParams` 时为每个方法生成\n // <METHOD>Params schema(GETParams / POSTParams / ...),运行时据此校验\n // 路径参数并把 coerce 后的值回写 ctx.params。路径段来源均为 string,\n // coerce 由命名后缀正则推断(Params → true)。未声明则不生成 schema,\n // 运行时跳过校验,ctx.params 保持原始字符串(与未声明行为一致)。\n const paramsArg = meta.params.find((p) => p.type === 'params');\n if (paramsArg?.typeName) {\n sources.push({\n urlPath: entry.urlPath,\n filePath,\n schemaName: getSchemaName(method, 'params'),\n typeInfo: resolver.resolve(paramsArg.typeName) ?? null,\n });\n }\n\n // 次输入 schema:方法主输入之外显式声明的输入同样「声明即校验」。\n // - body 方法(POST/PUT/PATCH)声明 query 形参 → <METHOD>Query(如 POSTQuery,\n // Query 后缀自动 coerce),运行时校验 query 并挂载 __validatedQuery\n // - query 主输入且 hasBody 的方法(DELETE)声明 body/form 形参 → <METHOD>Body\n // (如 DELETEBody),form 与 body 共享 schema 名、coerce=true 显式覆盖\n // - GET/HEAD 声明 body 不收集(hasBody=false,运行时 body 注入恒为 undefined,\n // 该声明属无效标注,保持既往行为)\n if (inputType === 'body') {\n const queryArg = meta.params.find((p) => p.type === 'query');\n if (queryArg?.typeName) {\n sources.push({\n urlPath: entry.urlPath,\n filePath,\n schemaName: getSchemaName(method, 'query'),\n typeInfo: resolver.resolve(queryArg.typeName) ?? null,\n });\n }\n } else if (hasBody(method)) {\n const bodyArg =\n meta.params.find((p) => p.type === 'body') ?? meta.params.find((p) => p.type === 'form');\n if (bodyArg?.typeName) {\n sources.push({\n urlPath: entry.urlPath,\n filePath,\n schemaName: getSchemaName(method, 'body'),\n typeInfo: resolver.resolve(bodyArg.typeName) ?? null,\n coerce: bodyArg.type === 'form' || undefined,\n });\n }\n }\n }\n }\n\n // WS 路由 schema 源:文件级约定声明(export interface/type Query / Params)\n // → WSQuery / WSParams schema(coerce 由 schemaName 后缀正则推断,与 HTTP 的\n // Query/Params 一致)。未声明约定的 WS 文件不生成 schema,握手时透传原始值\n //(params/query 保持 URL 原始字符串),与 HTTP 未声明形参行为对齐。\n for (const wsFile of wsFiles) {\n const program = programByFile.get(wsFile.filePath);\n const sourceFile = program?.getSourceFile(wsFile.filePath);\n if (!sourceFile) continue;\n const resolver = resolversByFile.get(wsFile.filePath)!;\n if (findExportedTypeName(sourceFile, 'Query')) {\n sources.push({\n urlPath: wsFile.urlPath,\n filePath: wsFile.filePath,\n schemaName: getSchemaName('WS', 'query'),\n typeInfo: resolver.resolve('Query') ?? null,\n });\n }\n if (findExportedTypeName(sourceFile, 'Params')) {\n sources.push({\n urlPath: wsFile.urlPath,\n filePath: wsFile.filePath,\n schemaName: getSchemaName('WS', 'params'),\n typeInfo: resolver.resolve('Params') ?? null,\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 // 输入接受 ISO 字符串与毫秒时间戳双形态(与响应序列化 Date → getTime() 毫秒\n // 时间戳可逆往返,见 utils/stringifyJson);其他类型原样传入\n // z.coerce.date() 会把 boolean 等也强转,不符合需求\n // date 类型本身已用 preprocess,coerce 模式下无需再包一层\n return 'z.preprocess((v) => (typeof v === \"string\" || typeof v === \"number\" ? 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 { existsSync } from 'node:fs';\nimport { atomicWriteFile } from '../utils/atomicWrite';\nimport type { RuntimeType } from '../ast/resolveTypeNode';\nimport {\n generateHelpersFileSource,\n usesCoerceHelpers,\n HELPERS_FILENAME,\n} from '../ast/generateZodSchema';\nimport { getSchemaOutputPath, getHelpersImportPath } from './generateSchemaFiles';\n\n/**\n * zod.js 产物生成的共享管线\n *\n * routes / tools / tasks 三条产物管线的「分组 → 算 helpers import 路径 → 生成\n * 源码 → 按需生成 faapi-helpers.js → 并行原子写」流程完全同构,此前三处复制粘贴\n * (新增字段或 coerce 语义变化需同步改三处)。本模块把差异项(每个 source 的\n * 文件路径、按文件生成源码的回调)抽为参数,流程只此一份。\n *\n * 产物布局约定(与 getSchemaOutputPath 一致):源文件剥 src/ 前缀后同级目录写\n * zod.js,faapi-helpers.js 固定在 dist 根部。\n */\n\n/** 管线输入的单条 schema 源(与 RouteSchemaSource/ToolSchemaSource/TaskSchemaSource 的公共面) */\nexport interface ZodArtifactSource {\n /** 源文件绝对路径(分组键 + 输出路径推导) */\n filePath: string;\n}\n\nexport interface GenerateZodArtifactsOptions<T extends ZodArtifactSource> {\n /** 同一文件的 schema 源列表 → 该文件 zod.js 源码 */\n generateFileSource: (\n fileSources: T[],\n resolveType: (name: string) => RuntimeType | undefined,\n helpersImportPath: string,\n ) => string;\n /** 文件绝对路径 → 惰性类型解析器(解析 ref) */\n resolversByFile: Map<string, { resolve(name: string): { runtimeType: RuntimeType } | null }>;\n rootDir: string;\n dist: string;\n}\n\n/**\n * 生成 zod.js 产物集(含按需的 faapi-helpers.js)\n *\n * helpers 生成条件:任一 zod.js 源码引用 coerce 函数。helpers.js 已存在时跳过\n * (三条管线共享同一份,先到的生成)。全部文件并行原子写——dev watch 重建与在途\n * 请求并发时,请求侧 import 不到半成品。\n */\nexport async function generateZodArtifacts<T extends ZodArtifactSource>(\n sources: T[],\n options: GenerateZodArtifactsOptions<T>,\n): Promise<void> {\n const { generateFileSource, resolversByFile, rootDir, dist } = options;\n if (sources.length === 0) return;\n\n // 按源文件分组(同一 handler.ts 的多个方法合并到一个 zod.js)\n const sourcesByFile = new Map<string, T[]>();\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 // 每文件生成源码(先暂存,helpers 检测需要全部源码)\n const fileEntries: { outputPath: string; source: string }[] = [];\n for (const [filePath, fileSources] of sourcesByFile) {\n const relFile = path.relative(rootDir, filePath).replace(/\\\\/g, '/');\n const outputPath = getSchemaOutputPath(relFile, dist, rootDir);\n const resolver = resolversByFile.get(filePath);\n\n // zod.js 所在目录相对 dist 的路径(helpers import 相对路径推导):\n // strip src/ 前缀后取目录部分,与 getSchemaOutputPath 的目录推导一致\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\n const source = generateFileSource(\n fileSources,\n (name) => resolver?.resolve(name)?.runtimeType,\n getHelpersImportPath(zodRelDir),\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 if (!existsSync(helpersPath)) {\n await atomicWriteFile(helpersPath, generateHelpersFileSource());\n }\n }\n\n // 并行原子写所有 zod.js\n await Promise.all(\n fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source)),\n );\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 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 * 注意与取反形式 `source.mtime > product.mtime`(\"源码更新\")完全等价——\n * 同毫秒保存时产物视为 fresh(`>=`),两种写法结论一致,只是表达方向不同\n */\nexport function 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 * - in-flight 编译失败时等待方重抛同一真实错误——吞掉后返回 false 会让调用方去\n * import 不存在的产物,报误导性的 ERR_MODULE_NOT_FOUND,掩盖真实编译错误\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 // 失败时 inFlight 的 rejection 原样传播(真实编译错误优先于误导性的模块缺失)\n const inFlight = state.inFlightCompilations.get(sourceAbsPath);\n if (inFlight) {\n await inFlight;\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 * - in-flight 生成失败时等待方重抛同一真实错误(与 ensureCompiled 同语义)\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 * @param wsRoutes WS 路由清单(可选)——同文件 WS 路由的约定 interface(Query/Params)\n * 一并生成,避免\"HTTP 请求先触发生成 → WS schema 缺失\"的静默降级;反向同理\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 wsRoutes?: import('../router/routeTypes').WsRouteManifest,\n): Promise<boolean> {\n // mutex: 同一 schemaPath 正在被别的请求生成 → 等待并返回 false;\n // 失败时 inFlight 的 rejection 原样传播(与 ensureCompiled 同语义)\n const inFlight = state.inFlightSchemaGenerations.get(schemaPath);\n if (inFlight) {\n await inFlight;\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 // HTTP 清单与 WS 清单合并过滤,保证生成的 zod.js 同时含两类 schema)\n const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);\n const fileWsRoutes = (wsRoutes ?? []).filter((r) => r.filePath === routeFilePath);\n if (fileRoutes.length === 0 && fileWsRoutes.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 const sourceWsRoutes = fileWsRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));\n\n // 触发生成:注册 in-flight Promise 防止并发重复生成\n const generatePromise = (async () => {\n // 生成失败时抛错(不吞错误),由 createServer 错误处理链接管\n await generateSchemaFiles(sourceRoutes, rootDir, dist, sourceWsRoutes);\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/** 全量编译的统一 ignore 规则:测试文件、声明文件、resources 运行时静态文件(原样复制不编译) */\nconst SOURCE_IGNORE = ['**/*.test.ts', '**/*.e2e.test.ts', '**/*.d.ts', `${APP_DIR}/resources/**`];\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: SOURCE_IGNORE,\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: SOURCE_IGNORE,\n });\n}\n","import type { Plugin } from 'esbuild';\nimport ts from 'typescript';\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 * - `.js` 说明符对应产物文件不存在时回退探测 `.ts`/`.tsx`/`.jsx` 源\n * (`moduleResolution: Node16` 风格的 `import './x.js'` 实际指向 `x.ts`,产物必有\n * `x.js`)——返回源路径,调用方按\"已带产物后缀\"保持说明符原样\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 if (fs.existsSync(base)) return base;\n // Node16 风格 .js 说明符 → .ts/.tsx/.jsx 源回退(产物必有对应 .js)\n if (base.endsWith('.js')) {\n for (const ext of ['.ts', '.tsx', '.jsx']) {\n const file = base.slice(0, -3) + ext;\n if (fs.existsSync(file)) return file;\n }\n }\n return 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` 阶段用 TypeScript AST 定位源码中真实的 import/export/动态 import\n * 说明符(注释与字符串字面量天然不参与),重写为产物相对路径(带 `.js` 后缀):\n *\n * 1. **相对 specifier**(`./xxx`、`../xxx`):解析到实际源文件,重写为产物相对路径。\n * - 无后缀 → 解析 + 加 `.js`\n * - `.ts`/`.tsx`/`.jsx` → 改为 `.js`\n * - `.js`/`.mjs`/`.cjs` → 不重写(视为产物路径);`.js` 说明符在产物文件不存在时\n * 回退探测 `.ts`/`.tsx`/`.jsx` 源(Node16 风格 `import './x.js'` 指向 `x.ts`),\n * 探测到即确认产物必有对应 `.js`,保持原样\n * - **解析失败(项目内不存在对应文件)→ 返回 esbuild error,构建直接失败**——\n * `bundle: false` 下 esbuild 不解析依赖,静默保留会让源码笔误推迟到生产运行时\n * (Node ESM 严格解析报 ERR_MODULE_NOT_FOUND)才暴露\n *\n * 2. **别名 specifier**(tsconfig.paths,如 `@/xxx`):调 `resolveAlias` 解析候选路径,\n * 命中则重写为产物相对路径。解析失败保持原样——paths 只是候选提示,未命中时裸\n * 说明符可能仍由 node_modules 运行时解析,不误报\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 形式(AST 定位,注释/字符串不误伤):\n * - `import { x } from './base'` / `import { x } from '@/base'` / `import './base'`(副作用导入)\n * - `export { x } from './base'` / `export * from './base'`\n * - `import('./base')`(动态 import,仅字面量实参)\n *\n * 绝对路径(`/`)、`file:` URL、`node:` 协议不处理,交 esbuild 默认。\n */\nexport function createAliasPlugin(\n config: TsconfigPathsConfig,\n options?: AliasPluginOptions,\n): Plugin {\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\n /** 探测别名候选对应的源文件(源后缀 → index 文件) */\n const probeCandidateFile = (candidate: string): string | null => {\n for (const ext of SOURCE_EXTS) {\n const file = candidate + ext;\n if (fs.existsSync(file)) return file;\n }\n for (const indexExt of INDEX_EXTS) {\n const file = candidate + indexExt;\n if (fs.existsSync(file)) return file;\n }\n return null;\n };\n\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\n // AST 定位真实说明符——正则会误伤注释与字符串(注释掉的 `// import ... from './deleted'`\n // 会被当成真实导入,严格报错时大面积误报),TypeScript 解析器只给出真实节点与精确位置\n const sourceFile = ts.createSourceFile(importer, source, ts.ScriptTarget.Latest, false);\n const literals: ts.StringLiteral[] = [];\n const visit = (node: ts.Node): void => {\n // import type / export type 被编译器整体擦除,说明符不会进入产物——不重写也不报错\n if (ts.isImportDeclaration(node)) {\n if (!node.importClause?.isTypeOnly && node.moduleSpecifier) {\n if (ts.isStringLiteral(node.moduleSpecifier)) literals.push(node.moduleSpecifier);\n }\n } else if (ts.isExportDeclaration(node)) {\n if (!node.isTypeOnly && node.moduleSpecifier) {\n if (ts.isStringLiteral(node.moduleSpecifier)) literals.push(node.moduleSpecifier);\n }\n } else if (\n ts.isCallExpression(node) &&\n node.expression.kind === ts.SyntaxKind.ImportKeyword &&\n node.arguments[0] &&\n ts.isStringLiteral(node.arguments[0])\n ) {\n literals.push(node.arguments[0]);\n }\n ts.forEachChild(node, visit);\n };\n ts.forEachChild(sourceFile, visit);\n\n const replacements: { start: number; end: number; text: string }[] = [];\n const unresolvable: ts.StringLiteral[] = [];\n\n for (const lit of literals) {\n const specifier = lit.text;\n // 绝对路径 / file: URL / node: 协议不处理\n if (\n specifier.startsWith('/') ||\n specifier.startsWith('file:') ||\n specifier.startsWith('node:')\n ) {\n continue;\n }\n\n // 相对 specifier(./ 或 ../):解析到实际文件并重写\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n const resolved = resolveRelativeSpecifier(importer, specifier);\n if (!resolved) {\n unresolvable.push(lit);\n continue;\n }\n // 已带产物后缀的说明符不重写(含 .js → .ts 源回退命中的 Node16 风格,产物必有 .js)\n if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) continue;\n\n let prodPath: string;\n // src 前缀剥离:importer 在 src 外、resolved 在 src 内\n // → 重写为剥离前缀的产物路径(相对 importer 产物位置)\n if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {\n prodPath = toProdImportFromImporter(\n importer,\n options!.rootDir!,\n toStrippedProdImportPath(resolved, options!.rootDir!),\n );\n } else {\n prodPath = toProdImportPath(resolved, importer);\n }\n const quote = source[lit.getStart(sourceFile)];\n replacements.push({\n start: lit.getStart(sourceFile),\n end: lit.getEnd(),\n text: quote + prodPath + quote,\n });\n continue;\n }\n\n // 别名 specifier:调 resolveAlias 解析\n for (const candidate of resolveAlias(specifier, config)) {\n const file = probeCandidateFile(candidate);\n if (!file) continue;\n\n let prodPath: string;\n if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {\n prodPath = toProdImportFromImporter(\n importer,\n options!.rootDir!,\n toStrippedProdImportPath(file, options!.rootDir!),\n );\n } else {\n prodPath = toProdImportPath(file, importer);\n }\n const quote = source[lit.getStart(sourceFile)];\n replacements.push({\n start: lit.getStart(sourceFile),\n end: lit.getEnd(),\n text: quote + prodPath + quote,\n });\n break;\n }\n }\n\n if (unresolvable.length > 0) {\n const lineStarts = sourceFile.getLineStarts();\n return {\n errors: unresolvable.map((lit) => {\n const { line, character } = sourceFile.getLineAndCharacterOfPosition(\n lit.getStart(sourceFile),\n );\n const lineEnd =\n line + 1 < lineStarts.length ? lineStarts[line + 1] : sourceFile.getEnd();\n return {\n text: `无法解析的相对导入 \"${lit.text}\"——项目内不存在对应源文件(faapi 逐文件编译(bundle: false)不解析依赖,该导入会原样进入产物,生产 Node ESM 运行时报 ERR_MODULE_NOT_FOUND)。请修正路径或补全后缀`,\n location: {\n file: importer,\n line: line + 1,\n column: character,\n lineText: source.slice(lineStarts[line], lineEnd).replace(/\\r?\\n$/, ''),\n },\n };\n }),\n };\n }\n\n if (replacements.length === 0) return undefined;\n // 从后往前应用替换,避免前面的替换使后面的位置失效\n replacements.sort((a, b) => b.start - a.start);\n let newSource = source;\n for (const r of replacements) {\n newSource = newSource.slice(0, r.start) + r.text + newSource.slice(r.end);\n }\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 { queryToObject } from '../utils/queryToObject';\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 } 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 /** 请求体大小限制(字节) */\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 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 — 默认启用,请求日志无条件并入统一日志管道(config.log.accessLog: false 关闭,\n // config.log: false 随管道全静默;编程式自定义输出经 middlewares: [logger({ log })]\n configMiddlewares.push(loggerMiddleware());\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 routesRef.wsCurrent,\n ).catch((err) => {\n // 兜底留痕:进入这里说明 sendErrorResponse 自身也失败(如响应头已发的二次\n // 响应尝试),恰恰是最需要排查痕迹的极端场景,静默吞掉会让 500 无从定位\n console.error('[faapi] Request pipeline failed after error response attempt:', err);\n try {\n if (!res.headersSent) {\n res.statusCode = 500;\n res.end();\n } else if (!res.writableEnded) {\n res.end();\n }\n } catch {\n // 连接已不可用,忽略\n }\n });\n });\n\n // 挂载 WebSocket 升级处理(无条件挂载:upgrade 处理器内部对无匹配路由返回 404,\n // 空清单成本一次函数调用。若按初始清单条件挂载,dev watch 中新增第一个 WS 路由后\n // upgrade 监听器不会补挂,WS 路由永远 404)\n attachWebSocket({\n server,\n routesRef,\n rootDir,\n dist,\n config,\n globalMiddlewares,\n trustedProxy,\n registries,\n });\n\n return { server, routesRef };\n}\n\n/**\n * 准备请求上下文:已解析的 Web Request + FaapiContext\n *\n * 提取为独立函数,让 handleRequest 主流程聚焦于路由 + 中间件调度,\n * 便于单测与未来扩展(如自定义 context 字段来源)。\n *\n * Web Request / URL 由调用方(handleRequest)解析一次后传入——提前路由匹配\n * 需要在构造 ctx 之前拿到 method/urlPath,且 Request 的 body 流只能包装一次。\n *\n * preMatched:提前匹配的路由结果(handleRequest 在构造 ctx 前完成匹配,\n * 让全局中间件 next() 之前即可见 ctx.params/ctx.rawParams 原始段)。\n * 未命中传 null——ctx.params 挂空对象,404/405 仍由 routePipeline 抛出\n * (错误响应保持经过完整外层中间件链,CORS 头不丢)。\n */\nfunction prepareRequest(\n req: IncomingMessage,\n request: Request,\n url: URL,\n config: Record<string, unknown> | undefined,\n trustedProxy: boolean,\n registries?: AppRegistries,\n preMatched?: RouteMatch | null,\n): {\n request: Request;\n url: URL;\n ctx: FaapiContext;\n meta: ResponseMeta;\n method: string;\n urlPath: string;\n} {\n const method = request.method.toUpperCase();\n const urlPath = url.pathname;\n // 路由匹配后构造 ctx:params 初值即匹配的原始段(全局中间件 next() 前可见),\n // createContext 内部把 params 同时挂为 ctx.rawParams(恒原始引用)\n const ctx = createContextFromUrl(\n request,\n url,\n preMatched?.params ?? {},\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\n/** 合并注入器缓存(route 记录 → 全局+目录合并结果;route 记录 reload 即换代) */\nconst mergedInjectorsCache = new WeakMap<object, InjectorMap>();\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 /** 提前匹配的路由结果;null 时由本管线抛 404/405(保持错误经过全局中间件链) */\n preMatched: RouteMatch | null;\n /** WS 路由清单(同文件 WS 路由的 schema 一并按需生成,防静默缺失) */\n wsRoutes?: WsRouteManifest;\n}): () => Promise<Response> {\n const {\n routes,\n method,\n urlPath,\n url,\n ctx,\n request,\n rootDir,\n dist,\n globalInjectors,\n preMatched,\n wsRoutes,\n } = opts;\n return async () => {\n // 1. 路由匹配:命中已在 handleRequest 提前完成(ctx.params/rawParams 已挂原始段);\n // 未命中在此抛 RouteNotFound / MethodNotAllowed\n const match = preMatched ?? resolveRouteOrThrow(routes, method, urlPath);\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 // rawBody(请求体原始文本)随之挂载(GET/HEAD 与 multipart 为 undefined)\n const { input, rawBody } = await resolveInputFromUrl(route.method, request, url);\n if (rawBody !== undefined) {\n ctx.rawBody = rawBody;\n }\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, wsRoutes);\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\n // 主输入是 query 的方法(GET/DELETE/HEAD):校验后的值替换 ctx.query\n //(声明 number/boolean 的字段已是转换后的值)。以原始 query 打底合并——\n // schema 是 z.object,声明之外的字段会被剥掉,未声明的兜底键保持原始\n // 字符串,行为与未声明 schema 时一致。ctx.query 即 handler 的 query 注入\n //(同一对象),无声明时 validateInput 原样透传、不替换。\n if (\n inputType === 'query' &&\n result.data !== input &&\n typeof result.data === 'object' &&\n result.data !== null\n ) {\n ctx.query = {\n ...(input as Record<string, unknown>),\n ...(result.data as Record<string, unknown>),\n };\n }\n\n // params 校验与回写:handler 声明了 `params: XxxParams` 时按声明类型校验\n // 路径参数(number/boolean 由 schema 内联 coerce 完成字符串→值转换),并把\n // 转换后的值回写 ctx.params——handler 注入、目录/全局中间件与诊断日志拿到的\n // 都是转换后的值。同样以原始 params 打底合并(防 catch-all 等声明之外的段被\n // z.object 剥掉);ctx.rawParams 保持原始引用不受影响。无声明时 validateInput\n // 原样透传,回写等于无操作。\n const paramsResult = await validateInput(schemaPath, route.method, 'params', match.params);\n if (!paramsResult.valid) {\n throw new ValidationError('参数校验失败', paramsResult.issues);\n }\n if (paramsResult.data !== match.params) {\n ctx.params =\n typeof paramsResult.data === 'object' && paramsResult.data !== null\n ? {\n ...match.params,\n ...(paramsResult.data as Record<string, string | number | boolean>),\n }\n : match.params;\n }\n\n // 次输入校验(声明即校验,与主输入同一 validateInput 通道):\n // body 方法(POST/PUT/PATCH)声明 query 形参时存在 POSTQuery schema——校验 query\n // 并替换 ctx.query(声明字段拿到转换值,未声明字段以原始 query 打底保留\n // 原始字符串);无声明时 schema 缺失、data === input,不替换,ctx.query 保持\n // createContext 挂载的原始 query 对象。\n if (inputType === 'body') {\n const rawQuery = queryToObject(url.searchParams);\n const queryResult = await validateInput(schemaPath, route.method, 'query', rawQuery);\n if (!queryResult.valid) {\n throw new ValidationError('参数校验失败', queryResult.issues);\n }\n if (\n queryResult.data !== rawQuery &&\n typeof queryResult.data === 'object' &&\n queryResult.data !== null\n ) {\n ctx.query = {\n ...(rawQuery as Record<string, unknown>),\n ...(queryResult.data as Record<string, unknown>),\n };\n }\n }\n // body 计算与主输入分流:\n // - POST/PUT/PATCH:主输入就是 body,用校验后的值\n // - DELETE:主输入是 query(校验 DELETEQuery),body 单独解析注入——\n // 若把校验后的 query 当 body 传入,handler 声明 body 会静默拿到 query;\n // 同时请求体流不被消费,keep-alive 连接无法复用。\n // 声明 body/form 形参时存在 DELETEBody schema:解析结果校验后再注入\n // (Date 字段转换与 POST body 一致;form 声明 coerce=true)。空请求体\n // (null)与 POST 同路径:有 schema 时 safeParse 失败 422,无 schema 透传\n // - GET/HEAD:无 body\n let body: unknown;\n if (inputType === 'query' && hasBody(route.method)) {\n const parsed = await resolveBodyForQueryMethod(request);\n if (parsed.rawBody !== undefined) {\n ctx.rawBody = parsed.rawBody;\n }\n const bodyResult = await validateInput(schemaPath, route.method, 'body', parsed.input);\n if (!bodyResult.valid) {\n throw new ValidationError('参数校验失败', bodyResult.issues);\n }\n body = bodyResult.data;\n } else if (hasBody(route.method)) {\n body = result.data;\n }\n // 挂载校验后的 body:目录中间件(handler 之前)可经 ctx.body 读取请求体;\n // 与 handler 的 body 注入是同一对象。GET/HEAD 及空请求体(null,无 schema\n // 透传场景)恒 undefined\n if (body !== undefined && body !== null) {\n ctx.body = body;\n }\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 // 合并结果对固定 route 恒定(injectors 在上方第 5 步按需加载后就绪,\n // reload 换新 route 记录),按 route WeakMap 缓存避免每请求 spread 重建\n let mergedInjectors = route.injectors;\n if (globalInjectors) {\n let merged = mergedInjectorsCache.get(route);\n if (!merged) {\n merged = { ...globalInjectors, ...route.injectors };\n mergedInjectorsCache.set(route, merged);\n }\n mergedInjectors = merged;\n }\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 wsRoutes?: WsRouteManifest,\n): Promise<void> {\n // meta/ctx 兜底:请求准备阶段抛错(如 content-length 超限的 413)时尚无 ctx\n let meta: ResponseMeta = { headers: {}, setCookies: [] };\n let ctx: FaapiContext | undefined;\n // SSE 提前接管标记:声明在 try 之外——catch 错误路径需读取(流已开始则不再发错误响应)\n let sseEarlySent = false;\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. 路由匹配提前:在构造 ctx 之前完成(matchRoute 纯函数无副作用),\n // 命中则 params 原始段进入 ctx.params/ctx.rawParams——全局中间件\n // await next() 之前即可读取;未命中传 null,404/405 仍由 routePipeline\n // 抛出(错误响应保持经过完整外层中间件链)\n const { request, url } = toWebRequest(req, bodyLimit, abortController.signal);\n const method = request.method.toUpperCase();\n const urlPath = url.pathname;\n const preMatched = matchRoute(routes, method, urlPath);\n\n // 2. 准备请求上下文(createContext)——Request/URL 由上方解析一次后传入\n const prepared = prepareRequest(\n req,\n request,\n url,\n config,\n trustedProxy,\n registries,\n preMatched,\n );\n ctx = prepared.ctx;\n meta = prepared.meta;\n\n // SSE 提前接管(详见 createServer.md「SSE 提前接管」):ctx.sse() 首次写入\n // (send/sendRaw/sendError)触发本钩子,立即把 SSE Response 接入 res——流式\n // 期间字节即产即达,writer.aborted 也能实时感知客户端断开。early-sent 后\n // 正常/错误路径均不再发送响应\n (ctx as FaapiContext & { __earlyRespond?: (response: Response) => void }).__earlyRespond = (\n response: Response,\n ) => {\n if (sseEarlySent) return;\n sseEarlySent = true;\n // meta(含 CORS 头等)在首次写入时刻合并;此后的 setStatus/setHeader 不再生效。\n // fire-and-forget:接管完成在流关闭后,不阻塞 handler;管道错误按断连收尾\n void sendNodeResponse(mergeMeta(response, meta), res).catch(() => {\n if (!res.writableEnded) res.destroy();\n });\n };\n\n // 3. 创建路由执行管线(校验 + 中间件加载 + handler 调用)\n const routePipeline = createRoutePipeline({\n routes,\n method,\n urlPath,\n url,\n ctx,\n request,\n rootDir,\n dist,\n globalInjectors,\n preMatched,\n wsRoutes,\n });\n\n // 4. 执行外层中间件链(CORS → helmet → logger → 全局 → routePipeline,\n // 数组已在 createServer 启动期组装,此处仅按需 compose)\n const response =\n outerMiddlewares.length > 0\n ? await compose(outerMiddlewares, ctx, routePipeline)\n : await routePipeline();\n // 5. 发送响应。SSE 已在首次写入时提前接管(响应在线上):handler 返回值及\n // 中间件 await next() 之后替换的响应被忽略(日志等副作用仍正常执行)\n if (!sseEarlySent) {\n await sendSuccessResponse(response, res);\n }\n } catch (err: unknown) {\n // 客户端已断开或响应已完成:网络中断/连接销毁不是服务端错误,\n // 不向已销毁的连接写 500(写入无效),也不触发 onError 误报\n if (res.destroyed || res.writableEnded) return;\n // SSE 流已开始(响应头已发出):无法再改发错误响应——流由 writer 关闭自然\n // 收尾(invokeHandler 的 autoClose 保证),仍触发 onError 副作用供告警观测\n if (sseEarlySent) {\n if (onError && ctx) {\n try {\n await onError(err, ctx);\n } catch {\n // onError 自身抛错不影响已发出的响应\n }\n }\n return;\n }\n // 413(请求体超限):客户端可能仍在上传——响应附 Connection: close 并在写出\n // 后销毁请求连接。不关闭的话:请求体未消费,keep-alive 连接无法复用,客户端\n // 上传也只会收到晦涩的连接重置而非明确的 413\n if (err instanceof PayloadTooLargeError) {\n res.setHeader('Connection', 'close');\n res.once('finish', () => req.destroy());\n }\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 // 无动态路由时直接返回(path.split 也不必做——纯静态清单的热路径)\n if (index.dynamics.length === 0) {\n return null;\n }\n\n // 请求路径 segments 循环外 split 一次——N 条候选动态路由不再重复分割同一 path\n const pathSegments = path.split('/').filter(Boolean);\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, pathSegments, 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 已预编译,path 循环外 split 一次)\n if (index.dynamics.length > 0) {\n const pathSegments = path.split('/').filter(Boolean);\n for (const entry of index.dynamics) {\n const params = matchSegments(\n entry.segments,\n pathSegments,\n entry.route.paramNames,\n entry.route.isCatchAll,\n );\n if (params !== null) {\n methods.add(entry.route.method);\n }\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(\n pattern.split('/').filter(Boolean),\n path.split('/').filter(Boolean),\n paramNames,\n isCatchAll,\n );\n}\n\n/**\n * 基于**预编译 segments** 的动态匹配(热路径内部用)\n *\n * 与 matchDynamicPath 语义一致,但模式段由索引构建期一次性 split、请求段由\n * 调用方循环外 split 一次传入,免去对同一字符串的重复分割\n */\nfunction matchSegments(\n patternSegments: string[],\n pathSegments: string[],\n paramNames: string[],\n isCatchAll?: boolean,\n): Record<string, string> | null {\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/** 输入解析结果:input 为校验管线的输入值,rawBody 为请求体原始文本(无则 undefined) */\nexport interface ResolvedInput {\n input: unknown;\n /** 请求体原始文本:JSON 为未解析字符串、form-urlencoded 为原始编码文本;multipart/无请求体为 undefined */\n rawBody?: string;\n}\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(有 schema 时 safeParse 失败 422,\n * 无 schema 透传;与 DELETE 的 body 空体行为完全一致)\n * - 请求体非空但 JSON 格式非法,抛 ValidationError(code=INVALID_FORMAT),\n * 不再静默返回 null 导致后续报\"字段缺失\"\n *\n * @param method HTTP 方法\n * @param request Request 对象\n * @returns input(query 对象或 body 对象)+ rawBody(原始请求体文本)\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveInput(method: string, request: Request): Promise<ResolvedInput> {\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<ResolvedInput> {\n const inputType = getInputTypeForMethod(method);\n\n // 主输入是 body 的方法:解析请求体\n if (inputType === 'body') {\n return parseBodyByContentType(request);\n }\n\n // 主输入是 query 的方法(GET / DELETE / HEAD):从 URL 提取 query\n return { input: queryToObject(url.searchParams) };\n}\n\n/**\n * 按 Content-Type 解析请求体(POST/PUT/PATCH 主输入与 DELETE 次输入共用)\n *\n * - multipart/form-data:fields + files(二进制流,rawBody 为 undefined)\n * - application/x-www-form-urlencoded:字符串对象(rawBody 为原始编码文本)\n * - 默认按 JSON 解析(application/json 及其它未明确类型)\n *\n * 空请求体(或纯空白)视为无 body,input 为 null(rawBody 为 undefined)。\n */\nasync function parseBodyByContentType(request: Request): Promise<ResolvedInput> {\n const contentType = request.headers.get('content-type') ?? '';\n\n // multipart/form-data:字段 + 文件\n if (contentType.includes('multipart/form-data')) {\n return { input: await 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 { input: 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 { input: obj, rawBody: text };\n }\n\n // 默认按 JSON 解析(application/json 及其它未明确类型)\n const text = await request.text();\n\n // 空请求体:视为无 body(有 schema 时校验失败,无 schema 透传)\n if (isBlankText(text)) {\n return { input: 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 { input: result.data, rawBody: text };\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 * Content-Type 分流与 POST/PUT/PATCH 主输入完全对称(JSON / form-urlencoded /\n * multipart)。解析结果的校验由 createServer 管线执行——handler 声明 `body`/\n * `form` 形参时存在 DELETEBody schema,校验通过后注入(Date 字段转换与 POST\n * body 一致;form 声明 coerce=true)。空请求体 input 为 null,与 POST 同路径:\n * 有 schema 时 safeParse 失败 422,无 schema 透传(handler 未声明 body 形参,\n * 无暴露面)。\n *\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveBodyForQueryMethod(request: Request): Promise<ResolvedInput> {\n return parseBodyByContentType(request);\n}\n","import type { ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { consumePendingMetaHeaders } from './pendingMeta';\nimport { hasBufferedBody } from './bufferedBody';\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 // 快路径:构造点标记为缓冲型的 body(string/Buffer——绝大多数 JSON 响应的形态,\n // 见 bufferedBody.ts)整体读出后 res.end 直写,免掉 Readable.fromWeb + pipe 的\n // 全套流机器。未标记的(SSE、handler 自建流)保持 pipe 流式语义\n if (hasBufferedBody(response)) {\n const buffer = await readBufferedBody(response.body);\n await new Promise<void>((resolve) => {\n res.on('close', () => {\n // 正常完成也会触发 'close'('finish' 之后),未写完才是断连\n if (!res.writableEnded) res.destroy();\n });\n if (res.destroyed) {\n resolve();\n return;\n }\n res.end(buffer, () => resolve());\n });\n return;\n }\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\n/**\n * 读取缓冲型 body 的完整内容\n *\n * 仅对构造点标记 hasBufferedBody 的 Response 调用——body 保证有限(内存型),\n * 读到 done 即返回。读取错误向上抛(与 pipe 路径的 reject 语义一致,走错误响应)。\n */\nasync function readBufferedBody(body: ReadableStream): Promise<Uint8Array> {\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n } finally {\n try {\n reader.releaseLock();\n } catch {\n // releaseLock 在流 errored 后可能抛错,忽略\n }\n }\n if (chunks.length === 0) return new Uint8Array(0);\n if (chunks.length === 1) return chunks[0]!;\n return Buffer.concat(chunks);\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 { ResponseMeta } from '../runtime/contextTypes';\n\n/**\n * 向响应 meta.headers 大小写不敏感地合并 Vary 值\n *\n * 延迟落头(headers-only meta)通道是响应头的唯一权威来源——读请求头\n * (ctx.headers.get('vary'))既看不到其他中间件已设置的响应 Vary,还会把客户端\n * 伪造的 Vary 请求头带进响应。CORS(Vary: Origin)与 compression\n * (Vary: Accept-Encoding)共用此函数,互不覆盖。\n */\n\n/** 向 meta.headers 合并 Vary 值(CORS 等中间件可能已设置 Vary,不能覆盖) */\nexport function 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 // 大小写不敏感地检查是否已包含(按逗号分段精确匹配,避免 'X-Origin' 误判含 'Origin')\n const segments = existing.split(',').map((s) => s.trim().toLowerCase());\n if (!segments.includes(value.toLowerCase())) {\n const key = meta.headers['Vary'] !== undefined ? 'Vary' : 'vary';\n meta.headers[key] = `${existing}, ${value}`;\n }\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { mergeVary } from './mergeVary';\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 meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\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 // 合并读权威来源 meta.headers(延迟落头通道)——读请求头既看不到其他中间件\n // 已设置的响应 Vary,还会把客户端伪造的 Vary 请求头带进响应\n if (origin === true || Array.isArray(origin)) {\n mergeVary(meta, 'Origin');\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 { mergeVary } from './mergeVary';\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/**\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';\nimport { isAccessLogEnabled, writeLogEntry } from '../logger/logger';\nimport type { LogLevel } from '../logger/loggerTypes';\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 */\n log?: LoggerFn;\n}\n\n/** 请求日志条目(自定义 log 函数的第一参数;index 签名兼容 LoggerFn 的 Record 形参) */\ninterface RequestLogEntry {\n [key: string]: unknown;\n requestId: string;\n method: string;\n path: string;\n status: number;\n durationMs: number;\n error?: string;\n}\n\n/** status → 日志级别映射(管道条目用):5xx error、4xx warn、其余 info */\nfunction levelForStatus(status: number): LogLevel {\n if (status >= 500) return 'error';\n if (status >= 400) return 'warn';\n return 'info';\n}\n\n/**\n * 默认输出(`options.log` 未配置时):请求日志无条件并入统一日志管道\n *\n * 条目转 LogEntry 经 writeLogEntry 输出(scope `access`,fields 携带\n * requestId/method/path/status/durationMs)——与业务日志同文件/同 sink/console,\n * 受 `config.log.level`(管道阈值)与 `consoleLevel`(console 出口)统一过滤。\n * `config.log.accessLog: false` 或 `config.log: false` 时丢弃不输出。\n */\nfunction defaultLog(entry: RequestLogEntry, text: string): void {\n if (!isAccessLogEnabled()) return;\n writeLogEntry({\n level: levelForStatus(entry.status),\n message: text,\n time: new Date().toISOString(),\n scope: 'access',\n fields: entry,\n });\n}\n\n/**\n * 创建请求日志中间件(洋葱模型)\n *\n * 默认并入统一日志管道(egg 模型:访问日志与业务日志同管道,一份 config.log\n * 管全部输出),`config.log.accessLog: false` 关闭。\n *\n * 日志格式(文本 message):GET /api/users 200 12ms\n * 错误格式:POST /api/users 400 45ms - Error: ...\n *\n * before/after 一体,闭包变量共享开始时间,无需污染 ctx。\n * 错误用 try/catch 捕获,记录后重新抛出(让上层处理)。\n * 成功时从 next() 返回的 Response 读取状态码。\n *\n * log 函数每次请求时读取(options.log ?? defaultLog),运行时切换全局日志配置\n * 同样生效。编程式自定义输出:`middlewares: [logger({ log: myFn })]`。\n */\nexport function logger(options: LoggerOptions = {}): FaapiMiddleware {\n return async (ctx, next) => {\n const log = options.log ?? defaultLog;\n const start = Date.now();\n try {\n const response = await next();\n const duration = Date.now() - start;\n const entry = {\n requestId: ctx.requestId,\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 requestId: ctx.requestId,\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 { queryToObject } from '../utils/queryToObject';\nimport { nodeHttpToWebHeaders, buildErrorResponse } from './serverUtils';\nimport { validateInput } from '../validator/validateInput';\nimport { ValidationError } from '../errors/httpErrors';\nimport { getRuntimeSchemaPath } from '../cli/generateSchemaFiles';\nimport {\n ensureCompiled,\n ensureSchemaGenerated,\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 *\n * 用户回调异常隔离(导出供测试锚定契约):onOpen/onMessage/onClose 抛错被捕获,\n * 故障半径限定当前连接——EventEmitter 监听器同步抛出会沿 emit 传播为\n * uncaughtException(Node 15+ 默认终止进程),HTTP 路径有完整错误链,WS 事件\n * 模型对齐。有 onError 时转交业务方(可 ws.close() 自决),无 onError 时\n * console.error;onError 自身抛错同样捕获仅留痕。框架不因回调抛错主动关连接。\n */\nexport function bindEvents(rawSocket: WebSocket, handlers: WsEventHandlers | void): void {\n if (!handlers) return;\n const ws = wrapWsSocket(rawSocket);\n\n /** 单个用户回调的异常隔离;onError 转交失败时降级 console.error(不递归抛) */\n const guard = (event: string, fn: () => void): void => {\n try {\n fn();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (handlers.onError) {\n try {\n handlers.onError(ws, error);\n } catch (hookErr) {\n console.error(`[faapi] WS onError 回调抛错 (after ${event}):`, hookErr);\n }\n } else {\n console.error(`[faapi] WS ${event} 回调抛错:`, error);\n }\n }\n };\n\n if (handlers.onOpen) {\n // handleUpgrade 回调触发时 socket 已是 OPEN,直接同步调用\n if (rawSocket.readyState === WebSocket.OPEN) {\n guard('onOpen', () => handlers.onOpen!(ws));\n } else {\n rawSocket.once('open', () => guard('onOpen', () => 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 guard('onMessage', () => handlers.onMessage!(ws, isBinary ? buf : buf.toString('utf8')));\n });\n }\n if (handlers.onClose) {\n rawSocket.on('close', (code: number, reason: Buffer) => {\n guard('onClose', () => handlers.onClose!(ws, code, reason.toString('utf8')));\n });\n }\n if (handlers.onError) {\n rawSocket.on('error', (err: Error) => {\n // onError 是错误出口本身,只防它抛错升级为 uncaughtException\n try {\n handlers.onError!(ws, err);\n } catch (hookErr) {\n console.error('[faapi] WS onError 回调抛错 (after onError):', hookErr);\n }\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 /** 产物输出目录(如 '.faapi' 或 'dist'),用于计算 WS schema 路径 */\n dist: 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 dist,\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 // 全握手唯一一次 URL 解析:Request 构造与 query 校验共享(searchParams 复用)\n const fullUrl = new URL(`http://${host}${req.url ?? '/'}`);\n\n // 构造 Web Request 与 FaapiContext(与 HTTP 请求一致,供中间件使用)\n const request = new Request(fullUrl.toString(), { 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:schema 校验转换 + 加载 WS handler + 协议升级 + 绑定事件\n const finalHandler = async (): Promise<Response> => {\n // 声明即校验(与 HTTP 管线同一 validateInput 通道):handler.ts 导出 WS 且\n // 同文件声明 `export interface Query` / `export interface Params` 时存在\n // WSQuery / WSParams schema(coerce=true,URL 段/查询串来源均为 string)。\n // 校验失败抛 ValidationError → 错误兜底链生成 422 JSON 响应写回 socket 拒绝\n // 握手;未声明约定的文件无 schema,validateInput 原样透传(params/query 保持\n // URL 原始字符串)。校验后的值回写 ctx.params/ctx.query(与 HTTP 一致:原始\n // 打底合并防剥未声明段),ctx.rawParams/ctx.rawQuery 恒为原始值。\n const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);\n // zod.js 不存在 = 该 WS 文件未声明约定 interface(无 WSQuery/WSParams 源时\n // 生成器不产出文件)→ 无校验,params/query 透传 URL 原始字符串(文档化行为,\n // 与 HTTP 未声明形参对齐)。文件存在但 schema key 缺失/加载失败仍按\n // validateInput 语义抛 InternalError,不静默放行。\n if (fs.existsSync(schemaPath)) {\n if (isDevOnDemandEnabled()) {\n const devDist = getDevDist();\n if (devDist) {\n await ensureSchemaGenerated(\n schemaPath,\n route.filePath,\n routesRef.current,\n rootDir,\n dist,\n routesRef.wsCurrent,\n );\n }\n }\n const searchParams = fullUrl.searchParams;\n const rawQueryObject = queryToObject(searchParams);\n const paramsResult = await validateInput(schemaPath, 'WS', 'params', params);\n if (!paramsResult.valid) {\n throw new ValidationError('参数校验失败', paramsResult.issues);\n }\n if (\n paramsResult.data !== params &&\n typeof paramsResult.data === 'object' &&\n paramsResult.data !== null\n ) {\n ctx.params = {\n ...params,\n ...(paramsResult.data as Record<string, string | number | boolean>),\n };\n }\n const queryResult = await validateInput(schemaPath, 'WS', 'query', rawQueryObject);\n if (!queryResult.valid) {\n throw new ValidationError('参数校验失败', queryResult.issues);\n }\n if (\n queryResult.data !== rawQueryObject &&\n typeof queryResult.data === 'object' &&\n queryResult.data !== null\n ) {\n ctx.query = {\n ...(rawQueryObject as Record<string, unknown>),\n ...(queryResult.data as Record<string, unknown>),\n };\n }\n }\n\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 { markBufferedBody } from '../response/bufferedBody';\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 const fallback = 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 markBufferedBody(fallback);\n return fallback;\n }\n}\n","/**\n * WebSocket Handler 类型定义与 socket 封装\n *\n * 用户在 handler.ts 中导出 WS 函数,返回事件回调对象。\n * 框架在协议升级成功后调用对应回调,传递封装后的 WsSocket。\n *\n * @see wsHandler.md 功能说明\n */\n\nimport { stringifyJson } from '../utils/stringifyJson';\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 *\n * 输入口径与 HTTP 一致(二分):params/query 为校验转换后的值,rawParams/rawQuery\n * 恒为原始值。声明来源是 handler.ts 的文件级约定——导出 `WS` 且同文件声明\n * `export interface Query` / `export interface Params` 时,握手阶段按声明类型\n * 校验并转换(失败拒绝握手);未声明时 params/query 保持 URL 原始字符串。\n */\nexport interface WsContext {\n /** 动态路由参数(声明 Params 时校验转换,未声明为 URL 原始字符串) */\n params: Record<string, string | number | boolean>;\n /** 动态路由参数的原始值(恒 URL 字符串,校验不触及) */\n rawParams: Record<string, string>;\n /** URL 查询参数(声明 Query 时校验转换,未声明为原始字符串对象) */\n query: Record<string, unknown>;\n /** URL 查询参数的原始值(恒 URLSearchParams) */\n rawQuery: 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 // BigInt 安全序列化(含 BigInt 的对象 send 不再抛错),见 utils/stringifyJson\n const payload =\n typeof data === 'string' || Buffer.isBuffer(data) ? data : stringifyJson(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","state","state","state","path","fs","url","path","headers","body","path","fs","path","fs","path","fs","fs","url","path","fs","path","ts","fs","path","path","fs","ts","ts","ts","ts","ts","ts","path","ts","unionInner","path","path","fs","path","path","path","fs","path","fs","fg","ts","path","fs","ts","path","fs","path","fs","ts","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","response","DEFAULT_BODY_LIMIT","cors","helmet","path","fs","WebSocket","msg"]}