@faapi/faapi 5.1.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +238 -173
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +154 -89
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-DG0eL3l5.d.ts → routeTypes-Bm--uTbm.d.ts} +2 -2
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +109 -52
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ast/createProgram.ts","../src/ast/resolveTypeNode.ts","../src/ast/extractHandlerTypes.ts","../src/runtime/inputType.ts","../src/validator/schemaName.ts","../src/injection/resolveInjection.ts","../src/injection/analyzeInjection.ts","../src/cli/collectRouteSchemaSources.ts","../src/utils/atomicWrite.ts","../src/ast/generateZodSchema.ts","../src/cli/generateSchemaFiles.ts","../src/index.ts","../src/injection/registries.ts","../src/injection/agentRegistry.ts","../src/injection/toolRegistry.ts","../src/injection/skillRegistry.ts","../src/loader/loadAgentModule.ts","../src/loader/resolveExports.ts","../src/utils/importWithCacheBust.ts","../src/cli/compileOnDemand.ts","../src/cli/compileSourceFiles.ts","../src/cli/aliasPlugin.ts","../src/utils/resolveAlias.ts","../src/utils/prodPaths.ts","../src/utils/readTsconfig.ts","../src/cli/compileDevRoutes.ts","../src/cli/collectImports.ts","../src/loader/loadToolModule.ts","../src/loader/loadToolSchema.ts","../src/cli/generateToolArtifacts.ts","../src/ast/extractToolMetadata.ts","../src/ast/jsDocMetadata.ts","../src/injection/agentHandle.ts","../src/middleware/cors.ts","../src/middleware/logger.ts","../src/middleware/helmet.ts","../src/config/loadConfig.ts","../src/cli/loadEnv.ts","../src/errors/FaapiError.ts","../src/errors/httpErrors.ts","../src/cli/createAppCore.ts","../src/router/sortRoutes.ts","../src/router/detectRouteConflicts.ts","../src/server/createServer.ts","../src/router/matchRoute.ts","../src/loader/validateRouteModule.ts","../src/loader/loadRouteModule.ts","../src/runtime/sse.ts","../src/response/responseFormatter.ts","../src/runtime/createContext.ts","../src/utils/queryToObject.ts","../src/utils/parseJsonBody.ts","../src/utils/parseMultipart.ts","../src/runtime/resolveInput.ts","../src/utils/isPlainObject.ts","../src/response/pendingMeta.ts","../src/response/toResponse.ts","../src/injection/injectParams.ts","../src/runtime/invokeHandler.ts","../src/response/sendNodeResponse.ts","../src/validator/validateInput.ts","../src/utils/getClientIp.ts","../src/middleware/compression.ts","../src/middleware/etag.ts","../src/server/handleWsUpgrade.ts","../src/server/serverUtils.ts","../src/middleware/loadMiddlewares.ts","../src/runtime/wsHandler.ts","../src/server/startServer.ts","../src/cli/generateRoutes.ts","../src/cli/generateAgentArtifacts.ts","../src/ast/extractAgentMetadata.ts","../src/cli/loadPlugins.ts","../src/cli/compileConfig.ts","../src/router/scanRoutes.ts","../src/router/constants.ts","../src/utils/normalizePath.ts","../src/router/parseRouteFile.ts","../src/tools/scanTools.ts","../src/agents/scanAgents.ts","../src/cli/createDevApp.ts","../src/cli/createProdApp.ts"],"sourcesContent":["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';\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","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","/**\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';\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 | 'unknown';\n\n/**\n * 注入项信息\n */\nexport interface InjectionItem {\n name: string; // 参数名\n type: InjectionType; // 注入类型\n hasType: boolean; // 是否有类型标注(运行时类型已擦除,始终为 false)\n}\n\n/**\n * 参数名到注入类型的映射(单一来源,构建期和运行时共用)\n *\n * `form` 与 `body` 互斥:handler 声明其一即可。`form` 适用于\n * `application/x-www-form-urlencoded` 请求体(值均为 string,schema coerce=true);\n * `body` 适用于 JSON 请求体(coerce=false)。两者共享 `resolveInput` 的解析结果。\n *\n * Phase 2.3 添加 `agent` / `agents`:\n * - `agent` → 注入默认 agent 元数据(Phase 2.4 实现,暂返回 `undefined`)\n * - `agents` → 注入所有已注册 agent 元数据列表(`agentRegistry.listAgents()`)\n *\n * Phase 3.x 的 `@faapi/agent` 插件通过注入器机制增强为 `AgentHandle`\n * (含可调用 `run` 函数),覆盖内置的元数据注入。\n */\nexport const PARAM_TYPE_MAP: Record<string, InjectionType> = {\n query: 'query',\n body: 'body',\n form: 'form',\n headers: 'headers',\n params: 'params',\n context: 'context',\n ctx: 'context', // 别名\n cookies: 'cookies',\n ip: 'ip',\n ua: 'ua',\n files: 'files',\n fields: 'fields',\n agent: 'agent', // Phase 2.3\n agents: 'agents', // Phase 2.3\n};\n\n/**\n * 分析结果按函数引用缓存。\n *\n * handler 函数在路由模块加载后引用稳定,参数列表不会变化;用 WeakMap 弱键,\n * dev 热替换后旧模块的函数不再被引用即可随 GC 回收,不需要手动失效。\n */\ntype AnyFn = (...args: unknown[]) => unknown;\nconst injectionCache = new WeakMap<AnyFn, InjectionItem[]>();\n\n/**\n * 分析函数参数,决定需要注入什么内容\n *\n * 使用 TypeScript AST 解析 fn.toString() 的结果,\n * 正确处理解构参数、默认值、rest 参数等情况。\n *\n * 注意:运行时类型信息已被擦除,hasType 始终为 false。\n *\n * 返回的数组是缓存的共享实例,调用方不得就地修改。\n */\nexport function resolveInjection(fn: (...args: unknown[]) => unknown): InjectionItem[] {\n const cached = injectionCache.get(fn);\n if (cached) {\n return cached;\n }\n\n const fnStr = fn.toString();\n const params = extractParamsWithAst(fnStr);\n\n const items = params.map((param) => {\n const type = PARAM_TYPE_MAP[param.name] || 'unknown';\n return {\n name: param.name,\n type,\n hasType: false, // 运行时类型已擦除\n };\n });\n\n injectionCache.set(fn, items);\n return items;\n}\n\n/**\n * 使用 TypeScript AST 从函数字符串中提取参数名\n *\n * 相比正则解析,AST 能正确处理:\n * - 解构参数 `function GET({ page, size })` → page, size\n * - 默认值 `function GET(query = {})` → query\n * - rest 参数 `function GET(...args)` → args\n * - 含逗号的泛型(类型擦除后不存在该问题)\n */\nfunction extractParamsWithAst(fnStr: string): Array<{ name: string }> {\n // 包装成模块,让 TS 能解析\n const sourceFile = ts.createSourceFile(\n '__faapi_injection__.ts',\n fnStr,\n ts.ScriptTarget.Latest,\n true,\n );\n\n const paramNames: string[] = [];\n\n function visit(node: ts.Node): void {\n // 函数声明:function GET(query) {}\n if (ts.isFunctionDeclaration(node) && node.parameters.length > 0) {\n for (const param of node.parameters) {\n extractParamName(param, paramNames);\n }\n return;\n }\n\n // 箭头函数 / 函数表达式:(query) => {} 或 function(query) {}\n if ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && node.parameters.length > 0) {\n for (const param of node.parameters) {\n extractParamName(param, paramNames);\n }\n return;\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n\n return paramNames.map((name) => ({ name }));\n}\n\n/**\n * 从单个参数节点提取参数名\n *\n * 支持的参数形式:\n * - 标识符:query → query\n * - 解构对象:{ page, size } → page, size(每个属性都作为一个注入项)\n * - 解构数组:[a, b] → a, b\n * - rest 参数:...args → args\n * - 默认值:query = {} → query\n */\nfunction extractParamName(param: ts.ParameterDeclaration, names: string[]): void {\n const name = param.name;\n\n // 标识符:query\n if (ts.isIdentifier(name)) {\n names.push(name.text);\n return;\n }\n\n // 解构对象:{ page, size }\n if (ts.isObjectBindingPattern(name)) {\n for (const element of name.elements) {\n if (ts.isBindingElement(element)) {\n const elemName = element.name;\n if (ts.isIdentifier(elemName)) {\n names.push(elemName.text);\n }\n }\n }\n return;\n }\n\n // 解构数组:[a, b]\n if (ts.isArrayBindingPattern(name)) {\n for (const element of name.elements) {\n if (element && ts.isBindingElement(element)) {\n const elemName = element.name;\n if (ts.isIdentifier(elemName)) {\n names.push(elemName.text);\n }\n }\n }\n return;\n }\n}\n","import ts from 'typescript';\nimport type { InjectionType } from './resolveInjection';\nimport { PARAM_TYPE_MAP } from './resolveInjection';\n\n/**\n * 参数元数据\n */\nexport interface ParamMeta {\n name: string; // 参数名\n type: InjectionType; // 注入类型\n typeName?: string; // 引用类型名(如 'Query')\n schema?: PropertyInfo[]; // 类型结构(内联类型时)\n}\n\n/**\n * 属性信息\n */\nexport interface PropertyInfo {\n name: string;\n type: string;\n optional: boolean;\n}\n\n/**\n * 注入元数据\n */\nexport interface InjectionMeta {\n params: ParamMeta[];\n}\n\n/**\n * AST 分析 handler 函数,提取参数注入元数据\n *\n * @param code handler 源码字符串\n * @param functionName 函数导出名(如 'GET')\n */\nexport function analyzeInjection(code: string, functionName: string): InjectionMeta {\n const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);\n return analyzeInjectionInSourceFile(sourceFile, functionName);\n}\n\n/**\n * 从已有 SourceFile 分析 handler 函数的参数注入元数据\n *\n * 与 analyzeInjection 语义一致,但接受 AST 节点而非源码字符串——\n * 调用方(如 collectRouteSchemaSources)的 program 中已有解析好的 SourceFile,\n * 逐方法重新 parse 同一文件字符串是纯浪费(同文件 N 个方法 = N 次全量重复解析)。\n *\n * @param sourceFile 已解析的 AST\n * @param functionName 函数导出名(如 'GET')\n */\nexport function analyzeInjectionInSourceFile(\n sourceFile: ts.SourceFile,\n functionName: string,\n): InjectionMeta {\n const params: ParamMeta[] = [];\n\n // 遍历 AST 查找目标函数\n ts.forEachChild(sourceFile, (node) => {\n if (ts.isFunctionDeclaration(node) && node.name?.text === functionName) {\n // 分析参数\n for (const param of node.parameters) {\n const paramMeta = analyzeParam(param, sourceFile);\n params.push(paramMeta);\n }\n }\n });\n\n return { params };\n}\n\n/**\n * 分析单个参数\n */\nfunction analyzeParam(param: ts.ParameterDeclaration, sourceFile: ts.SourceFile): ParamMeta {\n const name = param.name.getText(sourceFile);\n const type = PARAM_TYPE_MAP[name] || 'unknown';\n\n const result: ParamMeta = { name, type };\n\n // 分析类型\n if (param.type) {\n if (ts.isTypeReferenceNode(param.type)) {\n // 引用类型:Query, Body 等\n result.typeName = param.type.typeName.getText(sourceFile);\n } else if (ts.isTypeLiteralNode(param.type)) {\n // 内联类型:{ page: number }\n result.schema = extractSchema(param.type, sourceFile);\n }\n }\n\n return result;\n}\n\n/**\n * 从类型字面量提取 schema\n */\nfunction extractSchema(typeNode: ts.TypeLiteralNode, sourceFile: ts.SourceFile): PropertyInfo[] {\n const schema: PropertyInfo[] = [];\n\n for (const member of typeNode.members) {\n if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {\n const propName = member.name.text;\n const optional = !!member.questionToken;\n const propType = member.type?.getText(sourceFile) || 'unknown';\n\n schema.push({\n name: propName,\n type: propType,\n optional,\n });\n }\n }\n\n return schema;\n}\n","import { createPrograms } from '../ast/createProgram';\nimport {\n createLazyTypeResolver,\n type HandlerTypeInfo,\n type LazyTypeResolver,\n} from '../ast/extractHandlerTypes';\nimport { getInputTypeForMethod } from '../runtime/inputType';\nimport { getSchemaName } from '../validator/schemaName';\nimport { analyzeInjectionInSourceFile } from '../injection/analyzeInjection';\nimport type { RouteManifest } from '../router/routeTypes';\nimport path from 'node:path';\n\n/**\n * 单个路由的 schema 提取结果\n *\n * key 使用 urlPath(如 '/api/hello')而非 filePath,因为 urlPath 在 dev/prod 完全一致,\n * 无需 remapManifestKeys 桥接 .ts/.js 路径差异。\n */\nexport interface RouteSchemaSource {\n /** 路由 URL 路径(如 '/api/hello'),作为 schema key */\n urlPath: string;\n /** 源文件绝对路径(用于 generateSchemaFiles 按文件分组生成 zod.js) */\n filePath: string;\n schemaName: string;\n typeInfo: HandlerTypeInfo | null;\n /**\n * 是否对 number/boolean 字段生成 z.preprocess 字符串转换(coerce)。\n *\n * - query/params:始终 coerce=true(URL 来源均为 string)\n * - body:始终 coerce=false(JSON 解析已是天然 JS 类型)\n * - form:coerce=true(form-urlencoded 来源均为 string),由本函数在提取时\n * 检测到 handler 声明 `form` 参数时显式设置。schema 名仍为 `POSTBody`\n * (与 body 共享运行时 schema key),运行时 validateInput 无需感知 form/body 差异。\n *\n * 未设置时由 generateSchemaFileSource 回退到 schemaName 后缀正则推断(Query/Params → true)。\n */\n coerce?: boolean;\n}\n\n/**\n * 从路由清单收集 schema 提取所需的原始数据\n *\n * dev 和 prd 共享的核心提取流程:\n * 1. 按文件分组遍历路由\n * 2. 批量共享 Program(同一次提取只按文件组创建少量 Program)\n * 3. 对每个路由用 analyzeInjectionInSourceFile 定位入口参数类型,\n * extractTypeInfo 解析入口类型(入口类型必须严格解析,失败抛 SchemaExtractionError)\n * 4. 返回按文件分组的惰性类型解析器——generateSchemaFiles 生成 zod.js 时\n * 遇到 ref(同文件循环引用)按需解析并缓存\n *\n * 惰性语义:与路由无关的类型(未被任何入口类型引用)不会被解析——文件里\n * 存在一个含不支持语法的无关类型不再拖垮整个 build/reload。\n */\nexport function collectRouteSchemaSources(\n routes: RouteManifest,\n rootDir?: string,\n): {\n sources: RouteSchemaSource[];\n /** 按文件分组的惰性类型解析器(generateSchemaFiles 解析 ref 用) */\n resolversByFile: Map<string, LazyTypeResolver>;\n} {\n // 按文件分组收集方法(去重)\n // key 是文件绝对路径(createProgram 需要),但 schema key 用 urlPath\n const methodsByFile = new Map<string, { urlPath: string; methods: Set<string> }>();\n for (const route of routes) {\n const filePath = rootDir ? path.resolve(rootDir, route.filePath) : route.filePath;\n let entry = methodsByFile.get(filePath);\n if (!entry) {\n entry = { urlPath: route.urlPath, methods: new Set() };\n methodsByFile.set(filePath, entry);\n }\n entry.methods.add(route.method);\n }\n\n // 批量共享 Program:同一次提取只创建一个 Program,避免逐文件全量解析\n const programByFile = createPrograms([...methodsByFile.keys()]);\n\n // 每个文件一个惰性解析器:入口类型立即解析,ref 按需解析(缓存幂等)\n const resolversByFile = new Map<string, LazyTypeResolver>();\n for (const filePath of methodsByFile.keys()) {\n resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath)!, filePath));\n }\n\n // 每个文件提取 schema(key 用 urlPath)\n const sources: RouteSchemaSource[] = [];\n for (const [filePath, entry] of methodsByFile) {\n const program = programByFile.get(filePath)!;\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) continue;\n const resolver = resolversByFile.get(filePath)!;\n for (const method of entry.methods) {\n const inputType = getInputTypeForMethod(method);\n const schemaName = getSchemaName(method, inputType);\n // 复用 program 已解析的 SourceFile,逐方法零重复 parse\n const meta = analyzeInjectionInSourceFile(sourceFile, method);\n // POST/PUT/PATCH(inputType='body'):优先找 body 参数,找不到再找 form 参数。\n // form 与 body 共享 schema 名(POSTBody),运行时 validateInput 仍按 POSTBodySchema 查找;\n // 差异仅在校验:form 声明时通过 source.coerce=true 显式覆盖(form 值均为 string,\n // 需 z.preprocess 转换 number/boolean 字段)。\n const param =\n meta.params.find((p) => p.type === inputType) ??\n (inputType === 'body' ? meta.params.find((p) => p.type === 'form') : undefined);\n const isForm = param?.type === 'form';\n // 入口类型:必须严格解析(失败抛 SchemaExtractionError,带文件/类型上下文)\n const typeInfo = param?.typeName ? resolver.resolve(param.typeName) : null;\n sources.push({\n urlPath: entry.urlPath,\n filePath,\n schemaName,\n typeInfo,\n coerce: isForm || undefined,\n });\n }\n }\n\n return { sources, resolversByFile };\n}\n","import 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 {\n SchemaExtractionError,\n type RuntimeType,\n type TupleElement,\n type TypeConstraint,\n} from './resolveTypeNode';\nimport type { HandlerTypeInfo } from './extractHandlerTypes';\n\n/**\n * 类型解析器:根据类型名获取 RuntimeType\n *\n * 用于解析循环引用中的 ref。调用方负责提供(通常从 AST 提取)。\n */\nexport type TypeResolver = (name: string) => RuntimeType | undefined;\n\n/**\n * 代码生成上下文\n *\n * 跟踪已收集的命名类型,用于生成 z.lazy 声明。\n */\nclass CodeGenContext {\n /** 命名类型集合:name → RuntimeType */\n readonly namedTypes = new Map<string, RuntimeType>();\n /** 类型解析器(用于解析 ref 的实际类型) */\n readonly resolveType: TypeResolver;\n /** 入口类型原始名(typeInfo.name,用于识别入口类型的自引用) */\n entryTypeName = '';\n /** 入口类型导出名(exportName,自引用时用此名生成变量名) */\n entryExportName = '';\n /**\n * 是否生成 coerce 逻辑(query/params 场景,URL 来源均为 string)\n *\n * true 时为 number/boolean 字段包 z.preprocess,把合法的字符串转成对应类型。\n * 嵌套类型(array/object/tuple/union 等)的元素递归处理。\n */\n coerce = false;\n\n constructor(resolveType: TypeResolver) {\n this.resolveType = resolveType;\n }\n}\n\n/**\n * 从 RuntimeType 收集所有命名类型(ref)\n *\n * 递归遍历 RuntimeType,遇到 ref 时解析并加入 namedTypes。\n * 已收集的类型不重复处理(防止循环引用无限递归)。\n */\nexport function collectNamedTypes(type: RuntimeType, ctx: CodeGenContext): void {\n switch (type.kind) {\n case 'string':\n case 'number':\n case 'boolean':\n case 'bigint':\n case 'null':\n case 'undefined':\n case 'any':\n case 'unknown':\n case 'literal':\n case 'date':\n return;\n\n case 'array':\n collectNamedTypes(type.element, ctx);\n return;\n\n case 'tuple':\n for (const el of type.elements) {\n collectNamedTypes(el.type, ctx);\n }\n return;\n\n case 'object':\n for (const prop of type.properties) {\n collectNamedTypes(prop.type, ctx);\n }\n return;\n\n case 'union':\n for (const member of type.members) {\n collectNamedTypes(member, ctx);\n }\n return;\n\n case 'record':\n collectNamedTypes(type.key, ctx);\n collectNamedTypes(type.value, ctx);\n return;\n\n case 'map':\n collectNamedTypes(type.key, ctx);\n collectNamedTypes(type.value, ctx);\n return;\n\n case 'set':\n collectNamedTypes(type.element, ctx);\n return;\n\n case 'ref': {\n if (ctx.namedTypes.has(type.name)) return;\n // 先占位,防止循环引用无限递归\n ctx.namedTypes.set(type.name, { kind: 'any' });\n const resolved = ctx.resolveType(type.name);\n if (resolved) {\n ctx.namedTypes.set(type.name, resolved);\n collectNamedTypes(resolved, ctx);\n return;\n }\n // ref 只在 AST 提取阶段遇到真实 TS 类型引用时产生(首现内联、二次标 ref),\n // 声明必然存在于某个源文件中;解析不到说明查找能力不足(如命名空间内声明),\n // 按框架约定显式抛错而非静默降级为 z.unknown()(AGENTS.md §6.3)\n throw new SchemaExtractionError(\n type.name,\n `无法解析命名类型引用 \"${type.name}\"(handler 文件与 program 源文件中均未找到同名顶层声明)`,\n );\n }\n }\n}\n\n/**\n * RuntimeType → zod 表达式字符串(不含声明)\n *\n * @param type 类型描述\n * @param ctx 代码生成上下文\n * @param constraints 字段级约束(仅 object 字段传入,影响最外层 zod 链)\n */\nexport function runtimeTypeToZodExpression(\n type: RuntimeType,\n ctx: CodeGenContext,\n constraints?: TypeConstraint[],\n): string {\n const expr = baseExpression(type, ctx);\n // 字段级约束链(@max/@min/@regex 等)追加到基础表达式后\n // coerce 模式下约束需作用在 preprocess 内部的 z.X() 上,故先应用约束再包裹 preprocess\n const withConstraints =\n constraints && constraints.length > 0 ? applyConstraints(expr, constraints, type.kind) : expr;\n // coerce 模式下,number/boolean 在外层包 z.preprocess\n // 嵌套类型(array/object/tuple/union 等)内部已递归处理,外层不再包裹\n if (ctx.coerce && (type.kind === 'number' || type.kind === 'boolean')) {\n return wrapCoercePreprocess(type.kind, withConstraints);\n }\n // coerce 模式下,数字/布尔字面量同样需要字符串转换:query 声明 `status: 1 | 2`\n // 时 URL 传来 \"1\"(string),裸 z.literal(1) 必然校验失败。union 成员经本函数\n // 递归生成,字面量成员逐个包裹(string 字面量天然命中,无需转换)\n if (\n ctx.coerce &&\n type.kind === 'literal' &&\n (typeof type.value === 'number' || typeof type.value === 'boolean')\n ) {\n return wrapCoercePreprocess(\n typeof type.value === 'number' ? 'number' : 'boolean',\n withConstraints,\n );\n }\n return withConstraints;\n}\n\n/**\n * 在 zod 表达式上追加约束链\n *\n * 约束与字段类型的匹配在 AST 提取阶段已校验,这里直接生成链式调用。\n *\n * coerce 模式下调用方应先 applyConstraints 再 wrapCoercePreprocess,\n * 使约束作用在 preprocess 内部的 z.X() 上,而非 preprocess 外壳。\n *\n * @param baseExpr 基础 zod 表达式(未包裹 preprocess)\n * @param constraints 约束数组\n * @param typeKind 字段类型 kind\n */\nfunction applyConstraints(\n baseExpr: string,\n constraints: TypeConstraint[],\n typeKind: RuntimeType['kind'],\n): string {\n const suffix = constraints.map((c) => constraintToZodChain(c, typeKind)).join('');\n return `${baseExpr}${suffix}`;\n}\n\n/**\n * 单个约束 → zod 链式方法字符串\n *\n * @param constraint 约束\n * @param typeKind 字段类型 kind(用于区分 string/array 的 max/min 含义)\n */\nfunction constraintToZodChain(constraint: TypeConstraint, _typeKind: RuntimeType['kind']): string {\n switch (constraint.kind) {\n case 'max':\n return `.max(${constraint.value})`;\n case 'min':\n return `.min(${constraint.value})`;\n case 'int':\n return '.int()';\n case 'positive':\n return '.positive()';\n case 'negative':\n return '.negative()';\n case 'nonnegative':\n return '.nonnegative()';\n case 'nonpositive':\n return '.nonpositive()';\n case 'maxLength':\n return `.max(${constraint.value})`;\n case 'minLength':\n return `.min(${constraint.value})`;\n case 'length':\n return `.length(${constraint.value})`;\n case 'regex': {\n const flags = constraint.flags ?? '';\n // 用 new RegExp 构造,避免 pattern 中含 / 字符导致的字面量语法错误\n return `.regex(new RegExp(${JSON.stringify(constraint.pattern)}${flags ? `, ${JSON.stringify(flags)}` : ''}))`;\n }\n case 'email':\n return '.email()';\n case 'url':\n return '.url()';\n case 'uuid':\n return '.uuid()';\n }\n}\n\n/**\n * 生成基础 zod 表达式(不含 coerce 包裹)\n */\nfunction baseExpression(type: RuntimeType, ctx: CodeGenContext): string {\n switch (type.kind) {\n case 'string':\n return 'z.string()';\n case 'number':\n return 'z.number()';\n case 'boolean':\n return 'z.boolean()';\n case 'bigint':\n // AST 提取阶段已抛错,这里不会到达\n return 'z.never()';\n case 'null':\n return 'z.null()';\n case 'undefined':\n return 'z.undefined()';\n case 'any':\n case 'unknown':\n return 'z.unknown()';\n case 'literal':\n return `z.literal(${JSON.stringify(type.value)})`;\n case 'array':\n return `z.array(${runtimeTypeToZodExpression(type.element, ctx)})`;\n case 'tuple':\n return generateTupleExpression(type.elements, ctx);\n case 'object':\n return generateObjectExpression(type, ctx);\n case 'union':\n return generateUnionExpression(type.members, ctx);\n case 'date':\n // z.preprocess 限制只接受 string/Date:string → new Date(string),其他原样传入\n // z.coerce.date() 会把 number 也转为 Date(new Date(123)),不符合需求\n // date 类型本身已用 preprocess,coerce 模式下无需再包一层\n return 'z.preprocess((v) => (typeof v === \"string\" ? new Date(v) : v), z.date())';\n case 'record':\n return `z.record(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)})`;\n case 'map':\n // Map<K,V> — JSON 序列化为 entries 数组,coerceMap 还原为 Map 实例后再交给 z.map 校验\n // coerce=false(body)场景也需要 preprocess:JSON.parse 出来是数组,不是 Map 实例\n return `z.preprocess(coerceMap, z.map(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)}))`;\n case 'set':\n // Set<T> — JSON 序列化为数组,coerceSet 还原为 Set 实例后再交给 z.set 校验\n // coerce=false(body)场景也需要 preprocess:JSON.parse 出来是数组,不是 Set 实例\n return `z.preprocess(coerceSet, z.set(${runtimeTypeToZodExpression(type.element, ctx)}))`;\n case 'ref':\n // ref 引用命名类型,直接用 NameSchema 变量\n // 入口类型的自引用需用 exportName(exportName 可能与 typeInfo.name 不同)\n if (type.name === ctx.entryTypeName) {\n return `${ctx.entryExportName}Schema`;\n }\n return `${type.name}Schema`;\n }\n}\n\n/**\n * coerceNumber 公用函数源码(ESM export 格式)\n *\n * 用于 query/params 的 string → number 转换:\n * - 合法数字字符串(\"1\"、\"3.14\")转为 number\n * - 空串/NaN 保留原值,让 z.number() 报错(避免 Number(\"\") = 0 的陷阱)\n *\n * 写入 dist 根部的 faapi-helpers.js,各 zod.js 通过相对路径 import 复用。\n */\nexport const COERCE_NUMBER_HELPER =\n 'export const coerceNumber = (v) => typeof v === \"string\" && v.trim() !== \"\" && !isNaN(Number(v)) ? Number(v) : v;';\n\n/**\n * coerceBoolean 公用函数源码(ESM export 格式)\n *\n * 用于 query/params 的 string → boolean 转换:\n * - 'true'/'1' → true\n * - 'false'/'0' → false\n * - 其他值保留原值,让 z.boolean() 报错\n *\n * 写入 dist 根部的 faapi-helpers.js,各 zod.js 通过相对路径 import 复用。\n */\nexport const COERCE_BOOLEAN_HELPER =\n 'export const coerceBoolean = (v) => {\\n' +\n ' const lower = typeof v === \"string\" ? v.toLowerCase() : v;\\n' +\n ' return lower === \"true\" || lower === \"1\" ? true : lower === \"false\" || lower === \"0\" ? false : v;\\n' +\n '};';\n\n/**\n * coerceMap 公用函数源码(ESM export 格式)\n *\n * 用于 Map<K,V> 的 JSON 还原:JSON 序列化为 entries 数组,JSON.parse 后还原为 Map 实例\n * - entries 数组 [[\"k\",v],...] → new Map(entries)\n * - Map 实例 → 原样返回\n * - 普通对象 { k: v } → new Map(Object.entries(v))\n * - 其他值保留原值,让 z.map() 报错\n *\n * 与 coerceNumber/coerceBoolean 不同,coerceMap 在 coerce=false(body 场景)下也使用:\n * JSON.parse 出来的是数组/对象,不是 Map 实例,必须还原才能交给 z.map() 校验。\n */\nexport const COERCE_MAP_HELPER =\n 'export const coerceMap = (v) => Array.isArray(v) ? new Map(v) : v instanceof Map ? v : (v && typeof v === \"object\" ? new Map(Object.entries(v)) : v);';\n\n/**\n * coerceSet 公用函数源码(ESM export 格式)\n *\n * 用于 Set<T> 的 JSON 还原:JSON 序列化为数组,JSON.parse 后还原为 Set 实例\n * - Set 实例 → 原样返回\n * - 数组 [item,...] → new Set(array)\n * - 其他值保留原值,让 z.set() 报错\n *\n * 与 coerceNumber/coerceBoolean 不同,coerceSet 在 coerce=false(body 场景)下也使用:\n * JSON.parse 出来的是数组,不是 Set 实例,必须还原才能交给 z.set() 校验。\n */\nexport const COERCE_SET_HELPER =\n 'export const coerceSet = (v) => v instanceof Set ? v : (Array.isArray(v) ? new Set(v) : v);';\n\n/**\n * faapi-helpers.js 文件名(生成在 dist 根部)\n */\nexport const HELPERS_FILENAME = 'faapi-helpers.js';\n\n/**\n * 生成 faapi-helpers.js 文件源码\n *\n * 包含 coerceNumber / coerceBoolean / coerceMap / coerceSet 四个公用函数(ESM export)。\n * 由 generateSchemaFiles 按需生成:query/params 场景的 number/boolean 字段触发 coerceNumber/coerceBoolean;\n * Map/Set 字段(query/params 与 body 均包含)触发 coerceMap/coerceSet。\n */\nexport function generateHelpersFileSource(): string {\n return [\n '// faapi-helpers.js — faapi 自动生成的公用函数(请勿手动编辑)',\n COERCE_NUMBER_HELPER,\n COERCE_BOOLEAN_HELPER,\n COERCE_MAP_HELPER,\n COERCE_SET_HELPER,\n '',\n ].join('\\n');\n}\n\n/**\n * 检测代码是否引用了 coerceNumber / coerceBoolean / coerceMap / coerceSet 变量\n *\n * 用于 generateSchemaFiles 判断是否需要生成 faapi-helpers.js。\n * 注意:Map/Set 在 coerce=false(body 场景)下也会引用 coerceMap/coerceSet,\n * 故仅凭此检测即可覆盖所有需要生成 helpers 的场景。\n */\nexport function usesCoerceHelpers(code: string): boolean {\n return (\n code.includes('coerceNumber') ||\n code.includes('coerceBoolean') ||\n code.includes('coerceMap') ||\n code.includes('coerceSet')\n );\n}\n\n/**\n * 为 number/boolean 表达式包裹 z.preprocess(用于 query coerce)\n *\n * 引用从 faapi-helpers.js import 的 coerceNumber / coerceBoolean 变量,\n * 避免每个字段都内联一长串函数。\n */\nfunction wrapCoercePreprocess(kind: 'number' | 'boolean', inner: string): string {\n if (kind === 'number') {\n return `z.preprocess(coerceNumber, ${inner})`;\n }\n return `z.preprocess(coerceBoolean, ${inner})`;\n}\n\n/**\n * 生成元组 zod 表达式\n *\n * zod 的 tuple 不直接支持可选元素,需要用 union 模拟:\n * - 全部必填:`z.tuple([e0, e1, ...])`\n * - 含可选元素:`z.union([z.tuple([e0]), z.tuple([e0, e1]), ...])`(从短到长)\n * - 含 rest:`z.tuple([e0, ...]).rest(eRest)`\n */\nfunction generateTupleExpression(elements: TupleElement[], ctx: CodeGenContext): string {\n const fixedExprs: string[] = [];\n const fixedOptional: boolean[] = [];\n let restExpression = '';\n let restStarted = false;\n\n for (const el of elements) {\n if (el.rest) {\n restExpression = runtimeTypeToZodExpression(el.type, ctx);\n restStarted = true;\n } else if (!restStarted) {\n fixedExprs.push(runtimeTypeToZodExpression(el.type, ctx));\n fixedOptional.push(el.optional);\n }\n }\n\n // 含 rest 元素:z.tuple([fixed...]).rest(restType)。\n // 可选前缀保留(zod v4 原生支持 z.tuple([X.optional()]).rest(...))——\n // 此前丢弃 fixedOptional 导致 [string?, ...number[]] 的空数组被误拒\n if (restExpression) {\n const fixedWithOptional = fixedExprs.map((expr, i) =>\n fixedOptional[i] ? `${expr}.optional()` : expr,\n );\n return `z.tuple([${fixedWithOptional.join(', ')}]).rest(${restExpression})`;\n }\n\n // 无可选元素:z.tuple([e0, e1, ...])\n const hasOptional = fixedOptional.some((o) => o);\n if (!hasOptional) {\n return `z.tuple([${fixedExprs.join(', ')}])`;\n }\n\n // 含可选元素:从后往前截断可选元素,生成 union\n // TS 语法要求可选元素在必填元素之后,所以只需从后往前去掉可选元素\n const variants: string[] = [];\n for (let len = fixedExprs.length; len >= 0; len--) {\n // len === 0 时只有全部可选才有效(空元组)\n // len > 0 时,第 len-1 个元素必须存在(必填或可选都行)\n // 截断点:去掉 fixedExprs[len..end],这些必须都是可选的\n const removed = fixedOptional.slice(len);\n if (removed.length > 0 && removed.some((o) => !o)) {\n // 被截断的元素中有必填元素,无效截断\n break;\n }\n const subset = fixedExprs.slice(0, len);\n variants.push(`z.tuple([${subset.join(', ')}])`);\n }\n\n // 从短到长排序(更自然的校验顺序)\n variants.reverse();\n if (variants.length === 1) {\n return variants[0]!;\n }\n return `z.union([${variants.join(', ')}])`;\n}\n\n/**\n * 生成对象 zod 表达式\n *\n * 可选字段用 .optional(),约束链在 .optional() 之前生成。\n * 带索引签名的对象(catchall)生成 z.object({...}).catchall(valueSchema)——\n * 索引签名与属性共存时属性保留(resolveTypeNode 不再丢弃)。\n */\nfunction generateObjectExpression(\n type: Extract<RuntimeType, { kind: 'object' }>,\n ctx: CodeGenContext,\n): string {\n const fields = type.properties.map((prop) => {\n const expr = runtimeTypeToZodExpression(prop.type, ctx, prop.constraints);\n const finalExpr = prop.optional ? `${expr}.optional()` : expr;\n return `${JSON.stringify(prop.name)}: ${finalExpr}`;\n });\n const objectExpr = `z.object({ ${fields.join(', ')} })`;\n return type.catchall !== undefined\n ? `${objectExpr}.catchall(${runtimeTypeToZodExpression(type.catchall, ctx)})`\n : objectExpr;\n}\n\n/**\n * 生成联合类型 zod 表达式\n *\n * 优化:如果联合中包含 null,用 .nullable() 简化\n * 否则用 z.union([m0, m1, ...])\n */\nfunction generateUnionExpression(members: RuntimeType[], ctx: CodeGenContext): string {\n const hasNull = members.some((m) => m.kind === 'null');\n const nonNull = members.filter((m) => m.kind !== 'null');\n\n // 只有一个非 null 成员 + null → .nullable()\n if (hasNull && nonNull.length === 1) {\n return `${runtimeTypeToZodExpression(nonNull[0]!, ctx)}.nullable()`;\n }\n\n // 多个成员 + null → z.union([...]).nullable()\n if (hasNull) {\n const unionInner = nonNull.map((m) => runtimeTypeToZodExpression(m, ctx)).join(', ');\n return `z.union([${unionInner}]).nullable()`;\n }\n\n // 无 null → z.union([...])\n const unionInner = members.map((m) => runtimeTypeToZodExpression(m, ctx)).join(', ');\n return `z.union([${unionInner}])`;\n}\n\n/**\n * 生成命名类型的 zod schema 声明\n *\n * - 无循环引用:`const NameSchema = z.object({ ... })`\n * - 有循环引用:`const NameSchema = z.lazy(() => z.object({ ... }))`\n */\nfunction generateNamedTypeDeclaration(\n name: string,\n type: RuntimeType,\n ctx: CodeGenContext,\n): string {\n const expr = runtimeTypeToZodExpression(type, ctx);\n const hasRef = containsRef(type, new Set([name]));\n if (hasRef) {\n // 循环引用:用 z.lazy 延迟求值\n return `const ${name}Schema = z.lazy(() => ${expr});`;\n }\n return `const ${name}Schema = ${expr};`;\n}\n\n/**\n * 检查 RuntimeType 是否包含对自身的 ref(循环引用)\n */\nfunction containsRef(type: RuntimeType, visited: Set<string>): boolean {\n switch (type.kind) {\n case 'ref':\n return visited.has(type.name);\n case 'array':\n return containsRef(type.element, visited);\n case 'tuple':\n return type.elements.some((el) => containsRef(el.type, visited));\n case 'object':\n return type.properties.some((prop) => containsRef(prop.type, visited));\n case 'union':\n return type.members.some((m) => containsRef(m, visited));\n case 'record':\n return containsRef(type.key, visited) || containsRef(type.value, visited);\n case 'map':\n return containsRef(type.key, visited) || containsRef(type.value, visited);\n case 'set':\n return containsRef(type.element, visited);\n default:\n return false;\n }\n}\n\n/**\n * 生成单个类型的 zod schema 代码(含命名类型声明)\n *\n * 返回的代码包含:\n * - `import { z } from 'zod'`\n * - 内部命名类型的 const 声明(带 z.lazy 循环引用保护)\n * - `export const NameSchema = ...` 入口导出\n *\n * @param typeInfo 类型信息\n * @param resolveType 类型解析器(用于解析 ref 的实际类型)\n * @param exportName 导出的 schema 变量名(不含 Schema 后缀)。默认用 typeInfo.name。\n * validateInput 按 `${schemaName}Schema` 查找,需与导出名一致。\n * @param coerce 是否生成 coerce 逻辑(query/params 场景)。默认 false。\n * true 时为 number/boolean 字段(含嵌套元素)包 z.preprocess。\n */\nexport function generateZodSchemaSource(\n typeInfo: HandlerTypeInfo,\n resolveType: TypeResolver,\n exportName?: string,\n coerce = false,\n): string {\n const { namedTypeDeclarations, entryDeclaration } = generateZodSchemaSourceParts(\n typeInfo,\n resolveType,\n exportName,\n coerce,\n );\n\n const lines: string[] = [];\n lines.push(\"import { z } from 'zod';\");\n lines.push('');\n\n for (const { declaration } of namedTypeDeclarations) {\n lines.push(declaration);\n }\n if (namedTypeDeclarations.length > 0) lines.push('');\n\n lines.push(entryDeclaration);\n\n return lines.join('\\n');\n}\n\n/**\n * 生成单个类型的 zod schema 代码片段(结构化形式)\n *\n * 与 generateZodSchemaSource 的区别:不拼 import 头,命名类型声明与入口导出分开返回,\n * 供文件级组装按名去重——同一 handler 文件的多个方法引用同一命名类型时,命名类型\n * 声明只允许出现一次,否则拼接产物含重复 const 声明,import 即 SyntaxError。\n *\n * 语义与 generateZodSchemaSource 完全一致(含 coerce / z.lazy 循环引用处理)。\n */\nexport interface ZodSchemaSourceParts {\n /** 命名类型声明(按首次引用顺序排列;name 为不含 Schema 后缀的类型名) */\n namedTypeDeclarations: Array<{ name: string; declaration: string }>;\n /** 入口 schema 导出(`export const NameSchema = ...;`) */\n entryDeclaration: string;\n}\n\nexport function generateZodSchemaSourceParts(\n typeInfo: HandlerTypeInfo,\n resolveType: TypeResolver,\n exportName?: string,\n coerce = false,\n): ZodSchemaSourceParts {\n const ctx = new CodeGenContext(resolveType);\n const name = exportName ?? typeInfo.name;\n ctx.entryTypeName = typeInfo.name;\n ctx.entryExportName = name;\n ctx.coerce = coerce;\n\n // 收集入口类型中的所有命名类型(不含入口类型本身,避免重复声明)\n collectNamedTypes(typeInfo.runtimeType, ctx);\n ctx.namedTypes.delete(typeInfo.name);\n\n const namedTypeDeclarations = [...ctx.namedTypes].map(([n, type]) => ({\n name: n,\n declaration: generateNamedTypeDeclaration(n, type, ctx),\n }));\n\n // 入口 schema 导出(含循环引用时用 z.lazy)\n const entryExpr = runtimeTypeToZodExpression(typeInfo.runtimeType, ctx);\n const hasSelfRef = containsRef(typeInfo.runtimeType, new Set([typeInfo.name]));\n const entryDeclaration = hasSelfRef\n ? `export const ${name}Schema = z.lazy(() => ${entryExpr});`\n : `export const ${name}Schema = ${entryExpr};`;\n\n return { namedTypeDeclarations, entryDeclaration };\n}\n","import path from 'node:path';\nimport { atomicWriteFile } from '../utils/atomicWrite';\nimport type { RouteManifest } from '../router/routeTypes';\nimport type { RouteSchemaSource } from './collectRouteSchemaSources';\nimport type { RuntimeType } from '../ast/resolveTypeNode';\nimport { collectRouteSchemaSources } from './collectRouteSchemaSources';\nimport {\n generateZodSchemaSourceParts,\n generateHelpersFileSource,\n usesCoerceHelpers,\n HELPERS_FILENAME,\n} from '../ast/generateZodSchema';\n\n/**\n * 源文件路径 → 产物 zod.js 路径\n *\n * 每个 handler 目录下生成一个 `zod.js`(与 handler.js 同级,文件名固定为 zod.js):\n * - `src/api/hello/handler.ts` → `<dist>/api/hello/zod.js`\n *\n * 路由源码目录写死为 src,剥离 `src/` 前缀。\n *\n * @param sourceFile 源文件相对路径(相对 rootDir,如 'src/api/hello/handler.ts')\n * @param dist 输出目录(如 'dist' 或 '.faapi')\n * @param rootDir 项目根目录\n */\nexport function getSchemaOutputPath(sourceFile: string, dist: string, rootDir: string): string {\n let rel = sourceFile.replace(/\\\\/g, '/');\n // 去掉 src/ 前缀(打平产物结构)\n if (rel.startsWith('src/')) {\n rel = rel.slice(4);\n }\n // 取目录部分,basename 固定为 zod.js(每个 handler 目录一个 zod.js)\n const idx = rel.lastIndexOf('/');\n const relDir = idx >= 0 ? rel.slice(0, idx) : '';\n return path.resolve(rootDir, dist, relDir, 'zod.js');\n}\n\n/**\n * 运行时从 route.filePath 计算对应 zod.js 绝对路径\n *\n * 每个 handler 目录下的 zod.js(文件名固定为 zod.js,与 handler.js 同级):\n * - dev 模式:route.filePath 是源码路径(如 'src/api/hello/handler.ts'),\n * zod.js 在 `<rootDir>/<dist>/api/hello/zod.js`\n * - prod 模式:route.filePath 是产物路径(如 'dist/api/hello/handler.js'),\n * zod.js 在 `<rootDir>/dist/api/hello/zod.js`(与 handler.js 同级)\n *\n * 通过同时检查 src 和 dist 前缀,统一处理两种模式:\n * - dev:strip 'src/' 前缀,join dist\n * - prod:strip 'dist/' 前缀,join dist(dist='dist',结果与 rootDir 一致)\n *\n * @param filePath route.filePath(dev 为源码路径,prod 为产物路径)\n * @param dist 输出目录(如 'dist' 或 '.faapi')\n * @param rootDir 项目根目录\n */\nexport function getRuntimeSchemaPath(filePath: string, dist: string, rootDir: string): string {\n let rel = filePath.replace(/\\\\/g, '/');\n // strip src/ 前缀(dev 模式:filePath = 'src/...')\n if (rel.startsWith('src/')) {\n rel = rel.slice(4);\n }\n // strip dist 前缀(prod 模式:filePath = 'dist/...')\n else if (rel.startsWith(`${dist}/`)) {\n rel = rel.slice(dist.length + 1);\n }\n // 取目录部分,basename 固定为 zod.js\n const idx = rel.lastIndexOf('/');\n const relDir = idx >= 0 ? rel.slice(0, idx) : '';\n return path.resolve(rootDir, dist, relDir, 'zod.js');\n}\n\n/**\n * 计算 zod.js 到 faapi-helpers.js 的相对 import 路径\n *\n * faapi-helpers.js 固定在 dist 根部,zod.js 可能在子目录(如 'api/hello/zod.js')。\n * 根据子目录深度计算 `../` 数量:\n * - zod.js 在 `api/hello/`(深度 2)→ `../../faapi-helpers.js`\n * - zod.js 在 `api/`(深度 1)→ `../faapi-helpers.js`\n * - zod.js 在 dist 根(深度 0,理论不会发生)→ `./faapi-helpers.js`\n *\n * @param relDir zod.js 所在目录相对 dist 的路径(如 'api/hello' 或 '')\n * @returns ESM import 路径(如 '../../faapi-helpers.js')\n */\nexport function getHelpersImportPath(relDir: string): string {\n if (!relDir) return `./${HELPERS_FILENAME}`;\n const depth = relDir.split('/').filter(Boolean).length;\n return `${'../'.repeat(depth)}${HELPERS_FILENAME}`;\n}\n\n/**\n * 生成单个 handler 文件的 zod.js 源码\n *\n * 自包含:extractAllTypes 在 AST 阶段已通过 TypeScript checker 内联跨文件类型,\n * 每个 zod.js 无需 import 其他 zod.js。ref 仅用于同文件内的循环引用(通过 z.lazy 处理)。\n *\n * 导出格式:\n * - `<SchemaName>Schema`:zod schema 对象(用于 safeParse 校验)\n *\n * 无类型声明的方法不导出对应 Schema(validateInput 检测到 undefined 跳过校验)。\n *\n * coerce 逻辑:query/params 来源均为 string,schemaName 以 \"Query\" 或 \"Params\" 结尾时\n * 生成 coerce=true 的 schema(number/boolean 字段用 z.preprocess 包裹字符串转换)。\n * body 是 JSON 解析的天然 JS 类型,不需要 coerce。form 与 body 共享 schema 名(POSTBody),\n * 但 `RouteSchemaSource.coerce=true` 显式覆盖(form 值均为 string,需 coerce)。\n * Map/Set 字段在两种场景下都生成 z.preprocess 包裹(JSON.parse 出来的是数组/对象,需还原为 Map/Set 实例)。\n *\n * 命名类型声明文件级去重:同一文件的多个方法引用同一命名类型时,`const XSchema` 只在\n * 文件头声明一次(按首次引用顺序),避免重复 const 声明导致 zod.js import 即 SyntaxError。\n *\n * 公用函数复用:schema 引用 coerceNumber / coerceBoolean / coerceMap / coerceSet 变量时,\n * 这些变量从 dist 根部的 faapi-helpers.js import(跨文件复用,仅一份声明)。\n *\n * @param sources 同一文件的 schema 提取结果(含多个方法,如 GETQuery + POSTBody)\n * @param resolveType ref 解析函数(解析循环引用中的 ref):名称 → 运行时类型,\n * 未声明返回 undefined。惰性实现由 collectRouteSchemaSources 的 resolversByFile 提供。\n * @param helpersImportPath 到 faapi-helpers.js 的相对 import 路径(如 '../../faapi-helpers.js')。\n * 传空字符串表示不注入 coerce helpers 的 import(用于无 coerce schema 的文件或不支持外部 import 的测试场景)。\n * @returns zod.js 源码字符串\n */\nexport function generateSchemaFileSource(\n sources: RouteSchemaSource[],\n resolveType: (name: string) => RuntimeType | undefined,\n helpersImportPath: string,\n): string {\n const lines: string[] = [\"import { z } from 'zod';\"];\n\n // 文件级命名类型声明去重:同一文件的多个方法引用同一命名类型(如 GETQuery 与\n // POSTBody 都引用 Item)时,const 声明只提升到文件头生成一次——否则拼接产物含\n // 重复 const 声明,zod.js import 即 SyntaxError,该文件所有路由 500。\n const namedTypeDeclarations: string[] = [];\n const seenNamedTypes = new Set<string>();\n\n // 先生成所有 schema 入口导出,暂存到 schemaBlocks\n const schemaBlocks: string[] = [];\n for (const source of sources) {\n const { schemaName, typeInfo } = source;\n if (!typeInfo) {\n // 无类型声明,不导出对应 Schema\n continue;\n }\n\n // 推断 coerce:\n // - source.coerce 显式设置时优先使用(form 声明时由 collectRouteSchemaSources 设置为 true)\n // - 否则回退到 schemaName 后缀正则:query/params 需要 coerce(URL 来源均为 string),body 不需要\n const coerce = source.coerce ?? /(?:Query|Params)$/.test(schemaName);\n\n // 生成结构化片段:命名类型声明按名去重后提升到文件头,入口导出留在方法 block 内\n const { namedTypeDeclarations: decls, entryDeclaration } = generateZodSchemaSourceParts(\n typeInfo,\n resolveType,\n schemaName,\n coerce,\n );\n for (const { name, declaration } of decls) {\n if (seenNamedTypes.has(name)) continue;\n seenNamedTypes.add(name);\n namedTypeDeclarations.push(declaration);\n }\n\n schemaBlocks.push([`// ${schemaName}`, entryDeclaration, ''].join('\\n'));\n }\n\n if (namedTypeDeclarations.length > 0) {\n lines.push(...namedTypeDeclarations);\n lines.push('');\n }\n\n // 检测是否有 schema 引用了 coerce 公用函数,若有则注入 import 语句\n // 公用函数包含 coerceNumber / coerceBoolean(query/params 的 string 转换)和\n // coerceMap / coerceSet(Map/Set 的 JSON 还原,body 场景也会引用)\n const allSchemaCode = [...namedTypeDeclarations, ...schemaBlocks].join('\\n');\n if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {\n lines.push(\n `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`,\n );\n }\n lines.push('');\n\n lines.push(...schemaBlocks);\n\n return lines.join('\\n').replace(/\\n+$/, '\\n');\n}\n\n/**\n * 为路由清单中每个 handler 生成 zod.js\n *\n * 输出路径与 handler.js 同级(文件名固定为 zod.js):\n * - dev:`.faapi/api/hello/zod.js`\n * - prod:`dist/api/hello/zod.js`\n *\n * 公用函数复用:若项目中存在 coerce schema(query/params)或 Map/Set 字段,\n * 在 dist 根部生成 `faapi-helpers.js`(导出 coerceNumber / coerceBoolean / coerceMap / coerceSet),\n * 各 zod.js 通过相对路径 import。\n *\n * 内部流程:\n * 1. collectRouteSchemaSources 从路由清单提取 schema 源数据(AST 从源码 .ts)\n * 2. 按 filePath 分组 sources\n * 3. 为每个文件生成 zod.js 源码(暂存,用于检测是否需要 helpers)\n * 4. 检测是否需要 helpers,需要则生成 faapi-helpers.js\n * 5. 并行写入所有 zod.js\n *\n * @param routes 排序后的路由清单\n * @param rootDir 项目根目录\n * @param dist 输出目录(如 '.faapi' 或 'dist')\n */\nexport async function generateSchemaFiles(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<void> {\n if (routes.length === 0) return;\n\n const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir);\n\n // 按 filePath 分组 sources(同一 handler 的多个方法合并到一个 zod.js)\n const sourcesByFile = new Map<string, RouteSchemaSource[]>();\n for (const source of sources) {\n let list = sourcesByFile.get(source.filePath);\n if (!list) {\n list = [];\n sourcesByFile.set(source.filePath, list);\n }\n list.push(source);\n }\n\n // 为每个文件生成 zod.js 源码(先暂存,用于检测是否需要 helpers)\n const fileEntries: { outputPath: string; source: string }[] = [];\n for (const [filePath, fileSources] of sourcesByFile) {\n // filePath 是绝对路径,转为相对 rootDir 的路径用于计算输出路径\n const relFile = path.relative(rootDir, filePath).replace(/\\\\/g, '/');\n const outputPath = getSchemaOutputPath(relFile, dist, rootDir);\n // 计算 zod.js 所在目录相对 dist 的路径(用于 import helpers)\n // 与 getSchemaOutputPath 的目录计算逻辑一致:strip src/ 前缀后取目录部分\n let relForDir = relFile;\n if (relForDir.startsWith('src/')) {\n relForDir = relForDir.slice(4);\n }\n const dirIdx = relForDir.lastIndexOf('/');\n const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : '';\n const helpersImportPath = getHelpersImportPath(zodRelDir);\n\n const resolver = resolversByFile.get(filePath);\n const source = generateSchemaFileSource(\n fileSources,\n (name) => resolver?.resolve(name)?.runtimeType,\n helpersImportPath,\n );\n fileEntries.push({ outputPath, source });\n }\n\n // 检测是否需要生成 faapi-helpers.js\n const allSourceCode = fileEntries.map((e) => e.source).join('\\n');\n if (usesCoerceHelpers(allSourceCode)) {\n const helpersPath = path.resolve(rootDir, dist, HELPERS_FILENAME);\n await writeSchemaFile(helpersPath, generateHelpersFileSource());\n }\n\n // 并行写入所有 zod.js\n await Promise.all(\n fileEntries.map(({ outputPath, source }) => writeSchemaFile(outputPath, source)),\n );\n}\n\n/**\n * 写入 zod.js 文件(确保目录存在)\n */\nasync function writeSchemaFile(outputPath: string, source: string): Promise<void> {\n // 原子写:dev watch 重建与在途请求并发时,请求 import zod.js 不能读到半成品\n await atomicWriteFile(outputPath, source);\n}\n","export type { FaapiContext, FaapiContextConfig, FailOptions } from './runtime/contextTypes';\nexport type { FaapiMiddleware } from './middleware/middlewareTypes';\nexport type { Injector, InjectorMap } from './middleware/injectorTypes';\nexport type { CorsOptions } from './middleware/cors';\nexport type { LoggerOptions } from './middleware/logger';\nexport type { HelmetOptions } from './middleware/helmet';\nexport type {\n FaapiConfig,\n LifecycleHooks,\n LifecycleContext,\n ResponseConfig,\n AgentConfig,\n LlmConfig,\n LlmModelConfig,\n} from './config/configTypes';\nexport type {\n FaapiPlugin,\n PluginContext,\n PluginDeclaration,\n RequestHandler,\n UpgradeHandler,\n} from './config/pluginTypes';\nexport type { SseWriter, SseEvent } from './runtime/sse';\nexport type { WsContext, WsSocket, WsHandler, WsEventHandlers } from './runtime/wsHandler';\nexport type {\n RouteManifest,\n RouteInfo,\n RouteInputSchema,\n RouteOutputSchema,\n RouteParamSchema,\n} from './router/routeTypes';\n\n// AST 能力(供 @faapi/schema 等扩展包复用)\nexport { createProgram, createPrograms, invalidateProgramCache } from './ast/createProgram';\nexport { extractTypeInfo, type HandlerTypeInfo } from './ast/extractHandlerTypes';\nexport type { RuntimeType, PropertyType, TypeConstraint } from './ast/resolveTypeNode';\nexport { SchemaExtractionError, resolveTypeNode } from './ast/resolveTypeNode';\nexport { getInputTypeForMethod } from './runtime/inputType';\nexport { collectRouteSchemaSources, type RouteSchemaSource } from './cli/collectRouteSchemaSources';\n\n// agent 运行时集成面(供 @faapi/agent 等扩展包消费)\n// 类型导出——描述返回结构,运行时擦除\n// 运行时访问器——@faapi/agent 插件 setup 时导入,构造 AgentDeps 注入到 Agent 类\nexport type { AgentCore, AgentMetadata, AgentPathMeta } from './ast/extractAgentMetadata';\nexport type { ToolCore, ToolMetadata, ToolPathMeta } from './ast/extractToolMetadata';\nexport type { AgentToolDescriptor } from './injection/registries';\n// app 级注册表(方案 A:实例化——每个 app 持有独立实例,全局函数为默认实例便捷访问器)\nexport type {\n AppRegistries,\n ToolRegistry,\n AgentRegistry,\n SkillRegistry,\n AgentHandleStore,\n AgentHandleFactory,\n} from './injection/registries';\nexport { createAppRegistries } from './injection/registries';\nexport type { AgentModule } from './loader/loadAgentModule';\nexport type { ToolModule } from './loader/loadToolModule';\nexport type { ToolSchemaModule } from './loader/loadToolSchema';\n// 注册表访问器(单例模块,createAppBase 水合后可直接 import 调用)\n// getAgent 返回 AgentCore(LLM 可见字段);getAgentEntry 返回 AgentMetadata(含 filePath/hasRun,供加载 handler.js)\nexport {\n getAgent,\n getAgentEntry,\n resolveAgentTools,\n resolveSubAgents,\n} from './injection/agentRegistry';\nexport { getTool } from './injection/toolRegistry';\n// skill 注册表(运行时 DB-driven skills,业务方 plugin 接入外部源时调)\n// skill 与 agent 物理隔离——agentRegistry 查询函数不 fallback 到 skillRegistry,\n// skill 不参与 agent 查询链路、不覆盖文件型 agent、不参与 sub-agent 递归。\n// 业务方 plugin 自行编排使用(如自定义注入器或中间件注入到 handler)。\nexport {\n hydrateSkillRegistry,\n upsertSkill,\n removeSkill,\n getSkill,\n listSkills,\n} from './injection/skillRegistry';\n// 动态加载器(dev 按需编译模式需 rootDir,prod 模式直接 import 产物)\nexport { loadAgentModule } from './loader/loadAgentModule';\nexport { loadToolModule } from './loader/loadToolModule';\nexport { loadToolSchema, getToolSchemaPath } from './loader/loadToolSchema';\n\n// agent handle 工厂注册(Phase 3.5)——@faapi/agent 插件 setup 时注册,\n// injectParams 在 agent 参数注入时调工厂获取 AgentHandle\nexport { registerAgentHandleFactory, clearAgentHandleFactory } from './injection/agentHandle';\n\nexport { cors } from './middleware/cors';\nexport { logger } from './middleware/logger';\nexport { helmet } from './middleware/helmet';\nexport { loadConfig } from './config/loadConfig';\nexport { loadEnv } from './cli/loadEnv';\n\n// 错误类(供业务侧 instanceof 判定与自定义错误中间件使用)\nexport {\n ValidationError,\n RouteNotFoundError,\n MethodNotAllowedError,\n InternalError,\n ModuleLoadError,\n type ValidationIssue,\n type ValidationErrorCode,\n} from './errors/httpErrors';\nexport { FaapiError } from './errors/FaapiError';\n\n// 高层编程式启动 API(参考 NestJS NestFactory.create())\n// dev/prod 拆分:createDevApp(含 reloadRoutes 热替换)/ createProdApp(精简)\n// createApp 为 createProdApp 的向后兼容别名\nexport { createDevApp, type DevApp } from './cli/createDevApp';\nexport { createProdApp, type ProdApp } from './cli/createProdApp';\nexport { createApp, type App, type CreateAppOptions } from './cli/createApp';\nexport { getApp } from './cli/createAppCore';\nexport type { InjectOptions, InjectResponse } from './cli/createAppCore';\n","import type { ToolMetadata } from '../ast/extractToolMetadata';\nimport type { AgentMetadata, AgentCore } from '../ast/extractAgentMetadata';\nimport type { FaapiContext } from '../runtime/contextTypes';\n\n/**\n * app 级注册表(方案 A:注册表实例化)\n *\n * 每个应用实例(`createAppBase`)持有一套独立的注册表,随 app 创建、随\n * `app.close()` 销毁——多 app 同进程(测试 / 嵌入 / 多租户)互不串台。\n * 此前注册表是模块级全局单例 + hydrate 整体替换语义,后创建的 app 会覆盖\n * 先创建的 app 的清单,任一 app close 会清空全部(详见各模块 .md)。\n *\n * 原四个模块(toolRegistry / agentRegistry / skillRegistry / agentHandle)\n * 保留同名全局函数作为**默认实例的便捷访问器**(供编程式直调 / 单元测试 /\n * 无 app 上下文的场景),内部全部路由到本模块的 `defaultRegistries`。\n * 框架自身链路(hydrate / 请求注入 / `@faapi/agent` 插件 / lifecycle 钩子)\n * 一律走 app 实例,不再读写全局默认实例。\n */\n\n// ─── Tool 注册表 ─────────────────────────────────────────────────────\n\nexport interface ToolRegistry {\n /** 全量替换(tool 清单来自编译期产物,reload 时整体重新生成) */\n hydrate(tools: ToolMetadata[]): void;\n /** 按全名查找(如 `weather.getWeather`) */\n get(name: string): ToolMetadata | undefined;\n /** 所有已注册 tool(副本) */\n list(): ToolMetadata[];\n clear(): void;\n}\n\nexport function createToolRegistry(): ToolRegistry {\n let registry = new Map<string, ToolMetadata>();\n return {\n hydrate(tools) {\n const next = new Map<string, ToolMetadata>();\n for (const tool of tools) {\n next.set(tool.name, tool);\n }\n registry = next;\n },\n get(name) {\n return registry.get(name);\n },\n list() {\n return Array.from(registry.values());\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Agent 注册表 ────────────────────────────────────────────────────\n\n/** agent 包装为 tool 的描述符(reactLoop 据此识别 sub-agent 递归) */\nexport interface AgentToolDescriptor {\n kind: 'agent';\n name: string;\n agentName: string;\n description?: string;\n metadata: AgentCore;\n}\n\nexport interface AgentRegistry {\n /** 全量替换(与 hydrateToolRegistry 同构) */\n hydrate(agents: AgentMetadata[]): void;\n /** LLM 可见元数据(AgentCore) */\n getAgent(name: string): AgentCore | undefined;\n /** 完整元数据(含 filePath / hasRun,供加载 handler.js 执行 run) */\n getAgentEntry(name: string): AgentMetadata | undefined;\n /** 所有已注册 agent 的 LLM 可见元数据(副本) */\n listAgents(): AgentCore[];\n /** 包装为 tool 描述符 */\n asTool(name: string): AgentToolDescriptor | undefined;\n /**\n * 解析 agent 显式声明的 tool 集合。\n * 跨注册表依赖:经由构造时绑定的 tool 注册表查找(同属一套 AppRegistries,\n * 由 createAppBase 在同一启动阶段水合)\n */\n resolveAgentTools(name: string): ToolMetadata[];\n /** 解析 agent 可调用的子 agent 集合 */\n resolveSubAgents(name: string): AgentCore[];\n clear(): void;\n}\n\nexport function createAgentRegistry(tool: ToolRegistry): AgentRegistry {\n let registry = new Map<string, AgentMetadata>();\n\n const getAgent = (name: string): AgentCore | undefined => registry.get(name);\n\n return {\n hydrate(agents) {\n const next = new Map<string, AgentMetadata>();\n for (const agent of agents) {\n next.set(agent.name, agent);\n }\n registry = next;\n },\n getAgent,\n getAgentEntry(name) {\n return registry.get(name);\n },\n listAgents() {\n const merged = new Map<string, AgentCore>();\n for (const agent of registry.values()) merged.set(agent.name, agent);\n return Array.from(merged.values());\n },\n asTool(name) {\n const agent = getAgent(name);\n if (!agent) return undefined;\n return {\n kind: 'agent',\n name: `agent.${agent.name}`,\n agentName: agent.name,\n description: agent.description,\n metadata: agent,\n };\n },\n resolveAgentTools(name) {\n const agent = getAgent(name);\n if (!agent) return [];\n\n const result = new Map<string, ToolMetadata>();\n if (agent.tools) {\n for (const toolName of agent.tools) {\n const resolved = tool.get(toolName);\n if (resolved) result.set(resolved.name, resolved);\n // 未找到的 tool 名静默跳过(tool 可选可用,不强制存在)\n }\n }\n return Array.from(result.values());\n },\n resolveSubAgents(name) {\n const agent = getAgent(name);\n if (!agent || !agent.agents) return [];\n\n const result: AgentCore[] = [];\n for (const subName of agent.agents) {\n const sub = getAgent(subName);\n if (sub) result.push(sub);\n // 未注册的 agent 名跳过(agent 可选可用,不强制存在)\n }\n return result;\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Skill 注册表 ────────────────────────────────────────────────────\n\nexport interface SkillRegistry {\n /** 全量替换(DB change stream 场景也可用 upsert 增量) */\n hydrate(skills: AgentCore[]): void;\n /** 增量注册 / 覆盖(业务方监听 DB 单条变更) */\n upsert(skill: AgentCore): void;\n remove(name: string): void;\n get(name: string): AgentCore | undefined;\n list(): AgentCore[];\n clear(): void;\n}\n\nexport function createSkillRegistry(): SkillRegistry {\n let registry = new Map<string, AgentCore>();\n return {\n hydrate(skills) {\n const next = new Map<string, AgentCore>();\n for (const skill of skills) {\n next.set(skill.name, skill);\n }\n registry = next;\n },\n upsert(skill) {\n registry.set(skill.name, skill);\n },\n remove(name) {\n registry.delete(name);\n },\n get(name) {\n return registry.get(name);\n },\n list() {\n return Array.from(registry.values());\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Agent handle 工厂 ───────────────────────────────────────────────\n\n/** agent handle 工厂函数(由 `@faapi/agent` 插件注册) */\nexport type AgentHandleFactory = (ctx: FaapiContext) => unknown;\n\nexport interface AgentHandleStore {\n /** 注册工厂(null 清理);二次注册覆盖 */\n register(factory: AgentHandleFactory | null): void;\n /** 工厂已注册时返回 AgentHandle 实例,未注册返回 undefined */\n get(ctx: FaapiContext): unknown;\n clear(): void;\n}\n\nexport function createAgentHandleStore(): AgentHandleStore {\n let currentFactory: AgentHandleFactory | null = null;\n return {\n register(factory) {\n currentFactory = factory;\n },\n get(ctx) {\n if (currentFactory === null) return undefined;\n return currentFactory(ctx);\n },\n clear() {\n currentFactory = null;\n },\n };\n}\n\n// ─── App 级集合 ──────────────────────────────────────────────────────\n\n/** 一个 app 实例持有的全套注册表 */\nexport interface AppRegistries {\n tool: ToolRegistry;\n agent: AgentRegistry;\n skill: SkillRegistry;\n agentHandle: AgentHandleStore;\n}\n\n/** 创建一套 app 级注册表(`createAppBase` 每次调用创建独立实例) */\nexport function createAppRegistries(): AppRegistries {\n const tool = createToolRegistry();\n const agent = createAgentRegistry(tool);\n const skill = createSkillRegistry();\n const agentHandle = createAgentHandleStore();\n return { tool, agent, skill, agentHandle };\n}\n\n/**\n * 默认实例:原全局函数的退路(编程式直调 / 单元测试 / 无 app 上下文场景)。\n *\n * 框架自身链路不读写此实例——app 创建自己的 AppRegistries 并在请求链路 /\n * 插件 / lifecycle 钩子中传递。多 app 场景下默认实例无隔离语义(等同旧全局行为)。\n */\nexport const defaultRegistries: AppRegistries = createAppRegistries();\n","import type { AgentCore, AgentMetadata } from '../ast/extractAgentMetadata';\nimport { defaultRegistries } from './registries';\n\n// 类型从 registries 转发(AgentToolDescriptor 的定义源已随实例化迁入 registries)\nexport type { AgentToolDescriptor } from './registries';\n\n/**\n * agent 注册表全局访问器(默认实例便捷入口)\n *\n * 框架自身链路(`createAppBase` 水合 / `@faapi/agent` 插件 deps / `agents`\n * 参数注入)已改为读写 **app 实例级注册表**(见 [registries.md](./registries.md))——\n * agent 注册表实例在构造时绑定同套 tool 注册表实例(`resolveAgentTools` 跨表查找),\n * 多 app 同进程互不串台。\n *\n * 本模块的同名函数保留为**默认实例**的便捷访问器(语义约束不变:不 fallback 到\n * skillRegistry、Core / Entry 双查询入口),供编程式直调 / 单元测试使用。\n *\n * ## 与 skillRegistry 物理隔离\n *\n * agent 负责核心流程,skill 用于拓展——查询函数不 fallback 到 skillRegistry,\n * 实例化后依旧如此(两套注册表实例互不引用)。\n *\n * 详见 [agentRegistry.md](./agentRegistry.md)。\n */\n\n/**\n * 水合默认实例的 agent 注册表(全量替换)\n *\n * 框架路径请使用 app 实例:`ctx.registries.agent.hydrate(agents)`。\n *\n * @param agents 从 `faapi-agents.js` 水合还原的 `AgentMetadata[]`\n */\nexport function hydrateAgentRegistry(agents: AgentMetadata[]): void {\n defaultRegistries.agent.hydrate(agents);\n}\n\n/**\n * 清空默认实例的 agent 注册表\n *\n * app close 清理的是 app 自己的实例,不经过此函数。\n */\nexport function clearAgentRegistry(): void {\n defaultRegistries.agent.clear();\n}\n\n/**\n * 按名查找单个 agent 的 LLM 可见元数据(默认实例)\n *\n * 返回 [AgentCore](../ast/extractAgentMetadata.md)(不含 filePath / hasRun)。\n * 加载 handler.js 执行 `run` 函数请用 [getAgentEntry](#getAgentEntry)。\n *\n * @param name agent 名(如 `researcher`,含 `@agent` 覆盖值)\n */\nexport function getAgent(name: string): AgentCore | undefined {\n return defaultRegistries.agent.getAgent(name);\n}\n\n/**\n * 按名查找单个 agent 的完整元数据(默认实例,含 filePath / hasRun)\n */\nexport function getAgentEntry(name: string): AgentMetadata | undefined {\n return defaultRegistries.agent.getAgentEntry(name);\n}\n\n/**\n * 返回默认实例所有已注册 agent 的 LLM 可见元数据(副本,仅文件型 agent)\n */\nexport function listAgents(): AgentCore[] {\n return defaultRegistries.agent.listAgents();\n}\n\n/**\n * 把 agent 包装为 tool 描述符(默认实例)\n */\nexport function asTool(name: string) {\n return defaultRegistries.agent.asTool(name);\n}\n\n/**\n * 解析 agent 显式声明的 tool 集合(默认实例,跨查默认 tool 注册表)\n */\nexport function resolveAgentTools(name: string) {\n return defaultRegistries.agent.resolveAgentTools(name);\n}\n\n/**\n * 解析 agent 可调用的子 agent 集合(默认实例,按声明顺序)\n */\nexport function resolveSubAgents(name: string): AgentCore[] {\n return defaultRegistries.agent.resolveSubAgents(name);\n}\n","import type { ToolMetadata } from '../ast/extractToolMetadata';\nimport { defaultRegistries } from './registries';\n\n/**\n * tool 注册表全局访问器(默认实例便捷入口)\n *\n * 框架自身链路(`createAppBase` 水合 / 请求注入 / `@faapi/agent` 插件)已改为\n * 读写 **app 实例级注册表**(见 [registries.md](./registries.md))——每个 app\n * 持有独立实例,随 app 创建/销毁,多 app 同进程互不串台。\n *\n * 本模块的同名函数保留为**默认实例**的便捷访问器,供编程式直调 / 单元测试 /\n * 无 app 上下文的场景使用;多 app 场景下默认实例无隔离语义(等同旧全局行为),\n * 框架路径不再读写它。\n *\n * 详见 [toolRegistry.md](./toolRegistry.md)。\n */\n\n/**\n * 水合默认实例的 tool 注册表(全量替换)\n *\n * 框架路径请使用 app 实例:`ctx.registries.tool.hydrate(tools)`。\n *\n * @param tools 从 `faapi-tools.js` 水合还原的 `ToolMetadata[]`\n */\nexport function hydrateToolRegistry(tools: ToolMetadata[]): void {\n defaultRegistries.tool.hydrate(tools);\n}\n\n/**\n * 清空默认实例的 tool 注册表\n *\n * app close 清理的是 app 自己的实例,不经过此函数。\n */\nexport function clearToolRegistry(): void {\n defaultRegistries.tool.clear();\n}\n\n/**\n * 按全名查找单个 tool(默认实例)\n *\n * 框架路径(`@faapi/agent` 插件 / 请求注入)从 app 实例查找。\n *\n * @param tool 全名(如 `weather.getWeather`)\n * @returns `ToolMetadata` 或 `undefined`(未注册)\n */\nexport function getTool(name: string): ToolMetadata | undefined {\n return defaultRegistries.tool.get(name);\n}\n\n/**\n * 返回默认实例所有已注册 tool(副本)\n */\nexport function listTools(): ToolMetadata[] {\n return defaultRegistries.tool.list();\n}\n","import type { AgentCore } from '../ast/extractAgentMetadata';\n\n/**\n * skill 注册表(单例,运行时动态 skill)\n *\n * 与 [agentRegistry](./agentRegistry.md) 物理隔离,承载 DB-driven skills\n * (业务方在 plugin 里从数据库 / 外部源加载的 skill 元数据)。\n *\n * ## 与 agentRegistry 物理隔离\n *\n * - `agentRegistry` —— 编译期 `faapi-agents.js` 产物一次性水合,dev `reloadAgents`\n * 整体重新替换。来源是文件系统(`src/agents/<name>/handler.ts`)。承载文件型\n * agent,负责核心流程(含 `run` 函数的多步串联、sub-agent 递归)。\n * - `skillRegistry` —— 运行时动态,业务方 plugin `onReady` 启动期灌入 + DB change\n * stream 单条增删。来源是数据库 / 外部 API。仅供业务方 plugin 内部使用。\n *\n * 两者职责正交不耦合:**skill 用于拓展**,不参与 agent 查询链路、不覆盖文件型\n * agent、不被 agent 的 `agents` 列表自动引用。agentRegistry 的查询函数\n * (getAgent / listAgents / asTool / resolveAgentTools / resolveSubAgents)\n * **不 fallback 到本模块**。\n *\n * 物理隔离避免 `reloadAgents` 清空 DB skill(dev 模式每次改文件都触发 reload,\n * 业务方手工重新塞 DB skill 不可接受)。\n *\n * ## 存储 AgentCore 而非 AgentMetadata\n *\n * skillRegistry 存储 [AgentCore](../ast/extractAgentMetadata.md) 而非完整\n * `AgentMetadata`——DB skill 无源文件,无需 `filePath` / `hasRun` / `hasConfig`\n * 等代码加载占位字段。业务方从 DB 字段直接映射到 `AgentCore` 的 LLM 可见字段\n * (name / description / systemPrompt / tools / agents / model / maxTurns)即可。\n *\n * 详见 [skillRegistry.md](./skillRegistry.md)。\n */\n\nimport { defaultRegistries } from './registries';\n\n/**\n * skill 注册表全局访问器(默认实例便捷入口)\n *\n * 框架推荐路径已改为 **app 实例级注册表**:业务方 plugin 在\n * `lifecycle.onReady(ctx)` 中通过 `ctx.registries.skill` 灌入 DB skill——\n * 这样 skill 与该 app 的生命周期绑定,多 app 同进程互不串台,且 app close\n * 时随实例销毁。\n *\n * 本模块的同名函数保留为**默认实例**的便捷访问器(向后兼容),但注意:\n * 默认实例与 app 实例相互独立——经全局函数灌入的 skill 不会出现在\n * 该 app 的请求链路中。\n *\n * 详见 [skillRegistry.md](./skillRegistry.md) 与 [registries.md](./registries.md)。\n */\n\n/**\n * 水合默认实例的 skill 注册表(全量替换)\n *\n * 框架推荐路径:`lifecycle.onReady(ctx)` 中 `ctx.registries.skill.hydrate(skills)`。\n */\nexport function hydrateSkillRegistry(skills: AgentCore[]): void {\n defaultRegistries.skill.hydrate(skills);\n}\n\n/**\n * 清空默认实例的 skill 注册表\n */\nexport function clearSkillRegistry(): void {\n defaultRegistries.skill.clear();\n}\n\n/**\n * 单条增改默认实例的 skill(运行时增量)\n *\n * 框架推荐路径:`ctx.registries.skill.upsert(skill)`。\n */\nexport function upsertSkill(core: AgentCore): void {\n defaultRegistries.skill.upsert(core);\n}\n\n/**\n * 单条删除默认实例的 skill(幂等)\n */\nexport function removeSkill(name: string): void {\n defaultRegistries.skill.remove(name);\n}\n\n/**\n * 按名查默认实例的单个 skill\n */\nexport function getSkill(name: string): AgentCore | undefined {\n return defaultRegistries.skill.get(name);\n}\n\n/**\n * 返回默认实例所有已注册 skill(副本)\n */\nexport function listSkills(): AgentCore[] {\n return defaultRegistries.skill.list();\n}\n","import fs from 'node:fs';\nimport { resolveExport } from './resolveExports';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport {\n ensureCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n prodPathToSourcePath,\n} from '../cli/compileOnDemand';\n\n/**\n * 加载后的 agent 模块\n *\n * 与 [ToolModule](./loadToolModule.md) 对称——agent 的代码本体只有可选的 `run` 函数\n * (自定义 agent 运行逻辑,替代默认 reactLoop)。\n *\n * > `config` 字段已移除——`AgentMetadata` 已含 AST 提取的字面量字段\n * > (systemPrompt / tools / agents / model / maxTurns),`AgentModule.config`\n * > 原本用于运行时拿到完整 config 对象(含动态字段),但 `executeSubAgent`\n * > 拿到 `mod.config` 后从不读取(run 函数在自己模块内直接引用 config 变量),\n * > 属于死链路,故移除。\n *\n * `AgentMetadata`(从 `faapi-agents.js` 水合)已含字面量字段,本模块仅用于\n * 在运行时拿到 `run` 函数引用。\n */\nexport interface AgentModule {\n /**\n * 自定义 agent 运行函数(可选)\n *\n * `hasRun` 为 true 时一定为 function;为 false 时为 `undefined`。\n * 调用方式由 `@faapi/agent` 子包的 Agent 类定义(Phase 3.x)。\n */\n run: ((...args: unknown[]) => unknown) | undefined;\n}\n\n/**\n * 动态 import agent handler 文件并提取 `run` 导出\n *\n * Dev 按需编译模式(Vite 风格):先 `ensureCompiled` 确保产物存在再 import,\n * 避免 import 不存在的文件污染 Vite SSR 内部状态(详见 [loadRouteModule](./loadRouteModule.md))。\n * Prod 模式:产物在 build 阶段已固化,直接 import,失败即报错。\n *\n * 与 [loadToolModule](./loadToolModule.md) 的差异:\n * - tool 按 `functionName` 提取单个函数(校验为 function)\n * - agent 只提取 `run`(函数,可选)——config 块字段已在 AST 阶段提取为字面量,\n * 运行时无需再加载 config 对象\n *\n * 错误传递:\n * - 编译失败 → 抛 \"Failed to compile agent module\"\n * - import 失败 → 抛 \"Failed to load agent module\"\n *\n * @param filePath agent handler 文件的绝对路径(产物形式,如 `dist/agents/researcher/handler.js`)\n * @param hasRun 是否应提取 run 导出(来自 `AgentMetadata.hasRun`)\n * @param rootDir 项目根目录(按需编译模式用,可选)\n */\nexport async function loadAgentModule(\n filePath: string,\n hasRun: boolean,\n rootDir?: string,\n): Promise<AgentModule> {\n // Dev 按需模式:先确保编译再 import\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 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 agent 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 agent module \"${filePath}\": ${reason}`, { cause: err });\n }\n\n // 提取 run:hasRun 为 true 时解析并校验为 function,失败抛错\n let run: ((...args: unknown[]) => unknown) | undefined;\n if (hasRun) {\n const runExport = resolveExport(module, 'run');\n if (typeof runExport !== 'function') {\n throw new Error(\n `Agent module \"${filePath}\" does not export a valid \"run\" function (hasRun=true). ` +\n `Expected a function, got ${runExport === undefined ? 'undefined' : typeof runExport}.`,\n );\n }\n run = runExport as (...args: unknown[]) => unknown;\n }\n\n return { run };\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","import { pathToFileURL } from 'node:url';\n\n/**\n * 当前模块加载时间戳(watch 模式下用于绕过 ESM 缓存)\n *\n * 由 createDevApp.reloadRoutes 调用 setLoadTimestamp 设置。\n * ES 模块单例保证所有 import 此模块的地方共享同一个值,无需 globalThis。\n */\nlet loadTs: number | undefined;\n\nexport function setLoadTimestamp(ts: number): void {\n loadTs = ts;\n}\n\n/**\n * vitest 的 importActual 函数类型\n *\n * vi.importActual(path) 走 Vite SSR pipeline:\n * - 识别 vitest.config.ts 的 resolve.alias 与 tsconfig paths 别名\n * - 让 vi.mock 在加载的模块内生效\n */\ntype ImportActualFn = (path: string) => Promise<unknown>;\n\n/**\n * 检测 vitest 的 vi.importActual(globals: true 时注入 globalThis.vi)\n *\n * 业务方 vitest.config.ts 需 test.globals: true,或在测试文件内\n * 显式 `import { vi } from 'vitest'` 后挂到 globalThis.vi。\n *\n * 不在 vitest 环境下返回 undefined,调用方回退到 Node 原生 import()。\n */\nfunction getVitestImportActual(): ImportActualFn | undefined {\n const vi = (globalThis as { vi?: { importActual?: ImportActualFn } }).vi;\n if (typeof vi?.importActual !== 'function') return undefined;\n return vi.importActual.bind(vi);\n}\n\n/**\n * 动态 import 文件\n *\n * 两种加载路径:\n *\n * 1. **vitest 环境**(`globalThis.vi.importActual` 可用):走 Vite SSR pipeline,\n * 识别 tsconfig paths 别名(如 `@/lib/db`)+ 让 `vi.mock` 生效。\n * 业务方在 vitest 下用 `createTestServer` 时自动启用。\n *\n * 2. **非 vitest 环境**:Node 原生 `import()`。watch 模式下拼接 `?t=<timestamp>`\n * query 绕过 ESM 缓存;非 watch 模式等价普通 `import()`。\n *\n * @param filePath 文件绝对路径\n * @param bustViteCache dev 按需模式下走 Node 原生 import(绕过 Vite SSR pipeline)。\n * handler.js / zod.js 是已编译产物(别名已在编译时重写为相对路径),不需要 Vite alias 解析,\n * 直接走 Node 原生 import + 时间戳 query 避免 Vite SSR 缓存干扰。\n * @returns 模块导出对象\n */\nexport async function importWithCacheBust(\n filePath: string,\n bustViteCache = false,\n): Promise<Record<string, unknown>> {\n // vitest 环境优先走 Vite pipeline(识别 tsconfig paths + vi.mock)\n const importActual = getVitestImportActual();\n if (importActual) {\n if (bustViteCache) {\n // dev 按需模式:handler.js / zod.js 是已编译产物(别名已在编译时重写为相对路径),\n // 不需要 Vite alias 解析,直接走 Node 原生 import + 时间戳 query 避免 Vite SSR 缓存干扰\n let url = pathToFileURL(filePath).href;\n url += `?t=${Date.now()}`;\n return (await import(url)) as Record<string, unknown>;\n }\n return (await importActual(filePath)) as Record<string, unknown>;\n }\n\n // 否则走 Node 原生 ESM import\n let url = pathToFileURL(filePath).href;\n if (loadTs !== undefined) {\n url += `?t=${loadTs}`;\n }\n return (await import(url)) as Record<string, unknown>;\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport { compileDevRoutes } from './compileDevRoutes';\nimport { generateSchemaFiles } from './generateSchemaFiles';\nimport { getRuntimeSchemaPath } from './generateSchemaFiles';\nimport { collectRelativeImports } from './collectImports';\nimport type { RouteManifest } from '../router/routeTypes';\n\n/**\n * 按需编译(Vite 风格):仅在请求时编译被访问的 handler.ts 及其依赖闭包 + 生成 zod.js\n *\n * 设计目标:dev 启动时零编译、零 schema 生成,handler.js / zod.js 首次被需要时才触发。\n * 与阶段 1(scanRoutes 去 import)配合,dev 冷启动近乎瞬开。\n *\n * 依赖闭包:`bundle: false` 逐文件编译不分析 import 关系,只编译 handler 单文件时,\n * handler 引用的共享模块(如 `../../lib/db`)没有产物,首次请求 import 即\n * ERR_MODULE_NOT_FOUND。因此 ensureCompiled 先收集依赖闭包\n * (collectRelativeImports,相对 + tsconfig paths 别名),跳过产物已新鲜的文件后\n * 批量编译。src 外依赖不收集(outbase=src 语义限制,与 watcher 增量编译一致)。\n *\n * 四种触发路径:\n * 1. `loadRouteModule` 先调 `ensureCompiled` 编译源码 → 再 import handler.js\n * 2. `loadWsHandler` 同上(WS handler.js)\n * 3. `createServer` 调 `ensureSchemaGenerated` 检查 zod.js 是否存在/最新 → 不存在则生成\n * 4. watcher 文件变化 → 增量编译该文件(保留现有逻辑)+ 删除 stale zod.js\n * 5. `createServer` / `handleWsUpgrade` 首次加载目录中间件前调 `ensureMiddlewaresCompiled`\n *\n * mtime 缓存(阶段 4):\n * - `ensureCompiled`:handler.js 存在且 mtime ≥ 源码 mtime → 跳过编译\n * - `ensureSchemaGenerated`:zod.js 存在且 mtime ≥ 源码 mtime → 跳过生成\n * - 首次调用后写入内存 Set,后续直接跳过(避免 fs.statSync 调用)\n * - watcher 触发时清除内存 Set,mtime 重新评估\n *\n * 并发去重(mutex):\n * - 同一文件的并发编译请求会共享同一个 in-flight Promise,避免重复触发 esbuild\n * - 同一 schemaPath 的并发生成请求同理\n * - watcher 触发 clear 时同步清空 in-flight Map\n */\n\n/**\n * 比较源文件和产物文件的 mtime\n *\n * @returns true 表示产物是最新的(可复用),false 表示需要重新生成\n */\nfunction isProductFresh(sourceAbsPath: string, productAbsPath: string): boolean {\n try {\n const srcStat = fs.statSync(sourceAbsPath);\n const prodStat = fs.statSync(productAbsPath);\n return prodStat.mtimeMs >= srcStat.mtimeMs;\n } catch {\n return false;\n }\n}\n\n// ─── DevOnDemand 状态封装 ────────────────────────────────────────────\n\n/**\n * 按需编译运行时状态\n *\n * 封装原本散落的 4 个模块级可变状态 + 2 个 mutex Map,避免全局污染 +\n * 便于测试隔离(_resetDevOnDemandState 重置回初始状态)。\n *\n * faapi 单进程单 server 设计下,单例 state 已足够。多实例测试场景通过\n * _resetDevOnDemandState 在 beforeEach / afterEach 中清空状态。\n */\ninterface DevOnDemandState {\n /** 是否启用 dev 按需编译模式(prod 始终 false) */\n enabled: boolean;\n /** dev 模式产物目录(仅 enabled 时使用) */\n distDir: string | undefined;\n /** 已编译文件缓存(源码绝对路径) */\n compiledFiles: Set<string>;\n /** 已生成 schema 缓存(schemaPath 绝对路径) */\n generatedSchemas: Set<string>;\n /** handler.js 编译 mutex:同一 sourceAbsPath 的并发请求共享同一 Promise */\n inFlightCompilations: Map<string, Promise<void>>;\n /** zod.js 生成 mutex:同一 schemaPath 的并发请求共享同一 Promise */\n inFlightSchemaGenerations: Map<string, Promise<void>>;\n}\n\nfunction createDevOnDemandState(): DevOnDemandState {\n return {\n enabled: false,\n distDir: undefined,\n compiledFiles: new Set(),\n generatedSchemas: new Set(),\n inFlightCompilations: new Map(),\n inFlightSchemaGenerations: new Map(),\n };\n}\n\nconst state: DevOnDemandState = createDevOnDemandState();\n\n/**\n * 重置 dev on demand 状态(仅测试用)\n *\n * 清空所有缓存 + mutex + 标记位,让下一次测试从干净状态开始。\n * 生产代码不应调用此函数——会丢失已编译产物的缓存。\n */\nexport function _resetDevOnDemandState(): void {\n state.enabled = false;\n state.distDir = undefined;\n state.compiledFiles.clear();\n state.generatedSchemas.clear();\n state.inFlightCompilations.clear();\n state.inFlightSchemaGenerations.clear();\n}\n\n// ─── handler.js 按需编译 ────────────────────────────────────────────\n\n/**\n * 清空所有按需编译缓存(reloadRoutes 时调用)\n *\n * 同时清空 in-flight mutex Map + 产物→源码映射缓存(源文件新建/删除后映射需重建),\n * 避免 watcher 重置后旧 Promise 永久阻塞。\n */\nexport function clearCompiledFiles(): void {\n state.compiledFiles.clear();\n state.inFlightCompilations.clear();\n sourcePathCache.clear();\n}\n\n/**\n * 确保源文件及其依赖闭包已编译为产物,未编译则触发批量编译\n *\n * 调用方(`loadRouteModule` / `loadWsHandler`)在 import 产物之前调用此函数,确保产物已生成。\n *\n * 依赖闭包:handler 引用的项目内共享模块(相对 import + tsconfig paths 别名)一并编译,\n * 跳过产物已新鲜的文件(watcher 已编译或此前请求已编译),只补缺口。\n *\n * mtime 缓存(阶段 4):\n * 1. 内存 Set 命中 → 跳过(最快路径)\n * 2. 产物存在且 mtime ≥ 源码 mtime → 跳过(复用已有产物,如 watcher 已编译)\n * 3. 产物不存在或 stale → 编译(含 stale 的依赖闭包文件) → 加入内存 Set\n *\n * 并发去重(mutex):\n * - 同一 sourceAbsPath 的并发请求共享同一 in-flight Promise\n * - 第二个请求 await 后返回 false(表示「别的请求已触发编译,本次不需要再触发」)\n *\n * @param sourceAbsPath 源码 .ts 绝对路径\n * @param rootDir 项目根目录\n * @param dist 产物目录(dev 模式为 '.faapi')\n * @returns 是否实际触发了编译(false 表示已编译过或产物已最新或源文件不存在)\n * @throws 编译失败时抛错(带原始 cause),调用方可据此给出精确错误信息\n */\nexport async function ensureCompiled(\n sourceAbsPath: string,\n rootDir: string,\n dist: string,\n): Promise<boolean> {\n // mutex: 同一文件正在被别的请求编译 → 等待并返回 false\n const inFlight = state.inFlightCompilations.get(sourceAbsPath);\n if (inFlight) {\n await inFlight.catch(() => {\n // 别的请求编译失败时不在这里抛——让本请求按正常流程自己重试\n });\n return false;\n }\n\n // 内存缓存命中:跳过\n if (state.compiledFiles.has(sourceAbsPath)) {\n return false;\n }\n\n // 源文件不存在:无法编译\n if (!fs.existsSync(sourceAbsPath)) {\n return false;\n }\n\n // mtime 缓存:产物已存在且最新 → 复用(watcher 已编译过的情况)\n const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);\n if (productPath && isProductFresh(sourceAbsPath, productPath)) {\n state.compiledFiles.add(sourceAbsPath);\n return false;\n }\n\n // 触发编译:注册 in-flight Promise 防止并发重复编译\n const compilePromise = (async () => {\n // 依赖闭包:入口本身 + src 内全部传递依赖;产物已新鲜的文件跳过(只补缺口)\n const { insideFiles } = await collectRelativeImports([sourceAbsPath], rootDir);\n const files = [sourceAbsPath];\n for (const dep of insideFiles) {\n if (state.compiledFiles.has(dep)) continue;\n const depProduct = prodSourcePathToProductPath(dep, rootDir, dist);\n if (depProduct && isProductFresh(dep, depProduct)) continue;\n files.push(dep);\n }\n\n await compileDevRoutes({\n rootDir,\n dist,\n files,\n logLevel: 'silent',\n });\n for (const file of files) {\n state.compiledFiles.add(file);\n }\n state.compiledFiles.add(sourceAbsPath);\n })();\n\n state.inFlightCompilations.set(sourceAbsPath, compilePromise);\n try {\n await compilePromise;\n return true;\n } finally {\n state.inFlightCompilations.delete(sourceAbsPath);\n }\n}\n\n/**\n * 确保目录中间件文件已编译(dev 按需模式)\n *\n * middlewarePaths 是产物绝对路径(`.faapi/api/users/middlewares.js`),dev 按需模式下\n * 产物在首次请求前不存在——若不先编译,`loadMergedMiddlewares` import 即\n * ERR_MODULE_NOT_FOUND。通过 `prodPathToSourcePath` 反推源码路径后走 `ensureCompiled`\n * (含依赖闭包)。prod 模式(按需开关关闭)直接返回——产物由 build 固化。\n *\n * @param middlewarePaths 中间件产物绝对路径列表(scanRoutes 收集)\n * @param rootDir 项目根目录\n */\nexport async function ensureMiddlewaresCompiled(\n middlewarePaths: string[],\n rootDir: string,\n): Promise<void> {\n if (!isDevOnDemandEnabled() || middlewarePaths.length === 0) return;\n const dist = getDevDist();\n if (!dist) return;\n\n for (const mwPath of middlewarePaths) {\n const sourcePath = prodPathToSourcePath(mwPath, rootDir, dist);\n try {\n await ensureCompiled(sourcePath, rootDir, dist);\n } catch (err) {\n // 中间件编译失败不阻断请求——loadMiddlewaresFile 对 import 失败已有\n // console.error + 空 bundle 降级语义(鉴权失效由 onError 感知),此处保持一致\n console.error(`[faapi] Failed to compile middleware source ${sourcePath}:`, err);\n }\n }\n}\n\n/**\n * 从源码绝对路径推算产物绝对路径\n *\n * 源码 `<rootDir>/src/api/hello/handler.ts` → 产物 `<rootDir>/<dist>/api/hello/handler.js`\n */\nfunction prodSourcePathToProductPath(\n sourceAbsPath: string,\n rootDir: string,\n dist: string,\n): string | null {\n const rel = path.relative(rootDir, sourceAbsPath).replace(/\\\\/g, '/');\n if (!rel.startsWith('src/')) return null;\n const relWithoutSrc = rel.slice(4); // 去掉 src/\n const jsRel = relWithoutSrc.replace(/\\.ts$/, '.js');\n return path.resolve(rootDir, dist, jsRel);\n}\n\n// ─── zod.js 按需生成 ────────────────────────────────────────────────\n\n/**\n * 清空所有 schema 生成缓存(reloadRoutes 时调用)\n *\n * 同时清空 in-flight mutex Map。\n */\nexport function clearGeneratedSchemas(): void {\n state.generatedSchemas.clear();\n state.inFlightSchemaGenerations.clear();\n}\n\n/**\n * 确保 zod.js 已生成,未生成则触发单文件 schema 生成\n *\n * 调用方(createServer)在 validateInput 之前调用此函数。\n *\n * route.filePath 在 dev/prod 模式下均为产物路径(如 '.faapi/api/hello/handler.js'),\n * 需通过 prodPathToSourcePath 反推源码 .ts 路径用于 AST 分析和 mtime 比较。\n *\n * mtime 缓存(阶段 4):\n * 1. 内存 Set 命中 → 跳过\n * 2. zod.js 存在且 mtime ≥ 源码 mtime → 跳过(复用已有产物)\n * 3. zod.js 不存在或 stale → 生成 → 加入内存 Set\n *\n * 并发去重(mutex):\n * - 同一 schemaPath 的并发请求共享同一 in-flight Promise\n * - 第二个请求 await 后返回 false\n *\n * @param schemaPath zod.js 绝对路径\n * @param routeFilePath route.filePath(产物路径,如 '.faapi/api/hello/handler.js')\n * @param routes 完整路由清单(用于过滤同文件的所有方法)\n * @param rootDir 项目根目录\n * @param dist 产物目录\n * @returns 是否实际触发了生成(false 表示已生成过或产物已最新或源文件不存在)\n * @throws schema 生成失败时抛错(带原始 cause),由 createServer 错误处理链接管\n */\nexport async function ensureSchemaGenerated(\n schemaPath: string,\n routeFilePath: string,\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<boolean> {\n // mutex: 同一 schemaPath 正在被别的请求生成 → 等待并返回 false\n const inFlight = state.inFlightSchemaGenerations.get(schemaPath);\n if (inFlight) {\n await inFlight.catch(() => {\n // 别的请求生成失败时不在这里抛——让本请求按正常流程自己重试\n });\n return false;\n }\n\n // 内存缓存命中:跳过\n if (state.generatedSchemas.has(schemaPath)) {\n return false;\n }\n\n // route.filePath 是产物路径,反推源码绝对路径(用于 mtime 比较和 AST 分析)\n const prodAbsPath = path.resolve(rootDir, routeFilePath);\n const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);\n if (!fs.existsSync(sourceAbsPath)) {\n return false;\n }\n\n // mtime 缓存:zod.js 已存在且最新 → 复用\n if (isProductFresh(sourceAbsPath, schemaPath)) {\n state.generatedSchemas.add(schemaPath);\n return false;\n }\n\n // 过滤同文件的所有路由(一个 handler.ts 可能有 GET/POST/WS 多个方法)\n const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);\n if (fileRoutes.length === 0) {\n return false;\n }\n\n // 把产物 filePath 转为源码 filePath(generateSchemaFiles → collectRouteSchemaSources\n // 需要源码 .ts 路径做 AST 分析)\n const sourceRelPath = path.relative(rootDir, sourceAbsPath).replace(/\\\\/g, '/');\n const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));\n\n // 触发生成:注册 in-flight Promise 防止并发重复生成\n const generatePromise = (async () => {\n // 生成失败时抛错(不吞错误),由 createServer 错误处理链接管\n await generateSchemaFiles(sourceRoutes, rootDir, dist);\n state.generatedSchemas.add(schemaPath);\n })();\n\n state.inFlightSchemaGenerations.set(schemaPath, generatePromise);\n try {\n await generatePromise;\n return true;\n } finally {\n state.inFlightSchemaGenerations.delete(schemaPath);\n }\n}\n\n/**\n * 删除指定路由清单对应的所有 zod.js 文件(reloadRoutes 时调用)\n *\n * watcher 文件变化后,旧 zod.js 可能 stale(类型引用变化等)。\n * 删除后下次请求触发 ensureSchemaGenerated 重新生成。\n *\n * 仅删除文件,不删除目录(faapi-helpers.js 保留,内容确定性不变)。\n */\nexport async function deleteSchemaFiles(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<void> {\n const deleted = new Set<string>();\n for (const route of routes) {\n const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);\n if (deleted.has(schemaPath)) continue;\n deleted.add(schemaPath);\n try {\n await fs.promises.unlink(schemaPath);\n } catch {\n // 文件不存在:忽略\n }\n }\n}\n\n// ─── 路径转换 ────────────────────────────────────────────────────────\n\n/**\n * 从产物绝对路径反推源码绝对路径(带映射缓存)\n *\n * 源码 `<rootDir>/src/api/hello/handler.ts` → 产物 `<rootDir>/<dist>/api/hello/handler.js`\n *\n * dev 按需模式下**每请求**都会调此函数(loadRouteModule / ensureSchemaGenerated),\n * 缓存映射后稳态请求零 fs 访问。映射仅取决于源文件的存在性——源文件新建/删除\n * 通过 `clearCompiledFiles()`(reloadRoutes 时调用)清缓存。\n */\nconst sourcePathCache = new Map<string, string>();\n\nexport function prodPathToSourcePath(prodAbsPath: string, rootDir: string, dist: string): string {\n const cached = sourcePathCache.get(prodAbsPath);\n if (cached) return cached;\n\n const rel = path.relative(rootDir, prodAbsPath).replace(/\\\\/g, '/');\n // 去掉 <dist>/ 前缀\n let relWithoutDist = rel;\n if (relWithoutDist.startsWith(`${dist}/`)) {\n relWithoutDist = relWithoutDist.slice(dist.length + 1);\n }\n // 加 src/ 前缀\n const srcRel = `src/${relWithoutDist}`;\n // .js → .ts(.ts 不存在时回退 .js)\n const tsRel = srcRel.replace(/\\.js$/, '.ts');\n const tsAbs = path.resolve(rootDir, tsRel);\n let result: string;\n if (fs.existsSync(tsAbs)) {\n result = tsAbs;\n } else {\n // .ts 不存在,保持 .js(极少见,可能是用户直接放 .js 源码)\n result = path.resolve(rootDir, srcRel);\n }\n sourcePathCache.set(prodAbsPath, result);\n return result;\n}\n\n// ─── Dev 按需模式标记 ────────────────────────────────────────────────\n\n/**\n * Dev 按需编译模式标记\n *\n * 由 devCommand 在按需模式启动时设置为 true,loadRouteModule / loadWsHandler /\n * createServer 通过此标记判断是否启用按需编译/schema 生成回退。\n *\n * prod 模式(node dist/main)始终为 false——产物在 build 阶段已固化,import 失败即报错。\n */\nexport function setDevOnDemandEnabled(enabled: boolean): void {\n state.enabled = enabled;\n}\n\nexport function isDevOnDemandEnabled(): boolean {\n return state.enabled;\n}\n\n/**\n * Dev 模式产物目录(仅 devOnDemandEnabled 时使用)\n *\n * 由 devCommand 设置,loadRouteModule / loadWsHandler / createServer 通过 getDevDist() 读取。\n */\nexport function setDevDist(dist: string): void {\n state.distDir = dist;\n}\n\nexport function getDevDist(): string | undefined {\n return state.distDir;\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport fg from 'fast-glob';\nimport { buildAliasPlugins } from './aliasPlugin';\nimport { APP_DIR } from '../utils/prodPaths';\n\n/**\n * 编译结果\n */\nexport interface CompileResult {\n /** 已编译的文件列表(绝对路径) */\n compiledFiles: string[];\n}\n\nexport interface CompileSourceFilesOptions {\n /** 项目根目录 */\n rootDir: string;\n /** 输出目录(dev 为 `.faapi`,build 为 `dist`) */\n dist: string;\n /**\n * 增量编译:传入要编译的文件列表(绝对路径)。\n * 不传则全量编译 src 下所有 .ts(排除测试文件和声明文件)。\n */\n files?: string[];\n /** 是否输出 esbuild 日志(默认静默) */\n logLevel?: 'silent' | 'info';\n /**\n * prod 语义:`define` 把 `process.env.NODE_ENV` 编译期替换为 `\"production\"` +\n * `minifySyntax` 删除死分支(两者在 `bundle: false` 下均生效,单文件级别优化)。\n */\n production?: boolean;\n /**\n * dev 语义:原子写(write: false + 临时文件 + rename),避免 watch 模式下\n * esbuild 非原子写期间运行时 import 读到半成品产物。\n */\n atomicWrite?: boolean;\n}\n\n/**\n * dev/prod 共用的逐文件 TypeScript 编译实现\n *\n * 每个 `.ts` 独立编译为 `.js`,不分析 import 关系(`bundle: false`——\n * 保证 config 与 routes 共享同一运行时对象,`instanceof` 跨边界生效)。\n * 产物**打平 src/ 前缀**:`src/api/hello/handler.ts` → `<dist>/api/hello/handler.js`。\n *\n * 别名在编译时重写为相对路径,运行时无需 loader。\n *\n * 通过 `compileDevRoutes` / `compileBuildRoutes` 调用,见 ./compileSourceFiles.md\n * 的选项矩阵。\n */\nexport async function compileSourceFiles(\n options: CompileSourceFilesOptions,\n): Promise<CompileResult> {\n const { rootDir, dist, files, logLevel = 'silent', production, atomicWrite } = options;\n\n // 收集要编译的文件:files 优先,否则全量扫描 src 下所有 .ts\n const entryPoints =\n files ??\n (await fg([`${APP_DIR}/**/*.ts`], {\n cwd: rootDir,\n onlyFiles: true,\n absolute: true,\n ignore: ['**/*.test.ts', '**/*.e2e.test.ts', '**/*.d.ts'],\n }));\n\n if (entryPoints.length === 0) {\n return { compiledFiles: [] };\n }\n\n // 确保输出目录存在\n const absDist = path.resolve(rootDir, dist);\n await fs.promises.mkdir(absDist, { recursive: true });\n\n // 构造别名重写插件(无 tsconfig/paths 时为空,相对路径重写仍生效)\n const plugins = buildAliasPlugins(rootDir);\n\n // 逐文件编译,outbase 设为 src 以打平产物结构:\n // `src/api/hello/handler.ts` → `<dist>/api/hello/handler.js`(去掉 src/ 前缀)\n const esbuild = await import('esbuild');\n const outbase = path.resolve(rootDir, APP_DIR);\n const result = await esbuild.build({\n entryPoints,\n outdir: absDist,\n outbase,\n bundle: false,\n platform: 'node',\n format: 'esm',\n sourcemap: true,\n packages: 'external',\n plugins,\n // build 语义:编译期 NODE_ENV 替换 + 死分支删除(见 AGENTS.md §5.3)\n ...(production\n ? { define: { 'process.env.NODE_ENV': '\"production\"' }, minifySyntax: true }\n : {}),\n logLevel,\n // dev 语义:esbuild 返回内存内容,由下方原子写落盘\n ...(atomicWrite ? { write: false as const } : {}),\n });\n\n // 原子写:逐个写临时文件 + rename。rename 在同一文件系统上是原子的(POSIX),\n // HTTP 请求要么看到旧文件要么看到新文件\n if (atomicWrite && result.outputFiles) {\n await Promise.all(\n result.outputFiles.map(async (file) => {\n await fs.promises.mkdir(path.dirname(file.path), { recursive: true });\n const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n await fs.promises.writeFile(tmp, file.contents);\n await fs.promises.rename(tmp, file.path);\n }),\n );\n }\n\n return { compiledFiles: entryPoints };\n}\n\n/**\n * 全量扫描 src 下的 .ts 文件(排除测试文件和声明文件)\n *\n * 与 compileSourceFiles 的全量分支使用同一 ignore 规则,供 watch 等场景独立扫描。\n */\nexport async function collectSourceFiles(rootDir: string): Promise<string[]> {\n return fg([`${APP_DIR}/**/*.ts`], {\n cwd: rootDir,\n onlyFiles: true,\n absolute: true,\n ignore: ['**/*.test.ts', '**/*.e2e.test.ts', '**/*.d.ts'],\n });\n}\n","import type { Plugin } from 'esbuild';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { resolveAlias } from '../utils/resolveAlias';\nimport { APP_DIR, isInsideDir, toProdExtension, toRealPath } from '../utils/prodPaths';\nimport { readTsconfig, type TsconfigPathsConfig } from '../utils/readTsconfig';\n\n// 兼容 re-export:toProdExtension 此前由本模块导出\nexport { toProdExtension };\n\n/**\n * 把候选源文件路径转为产物 import 路径(相对 importer,POSIX 风格,带 .js 后缀)\n */\nfunction toProdImportPath(sourceFile: string, importer: string): string {\n const importerDir = path.dirname(importer);\n let rel = path.relative(importerDir, sourceFile);\n rel = rel.split(path.sep).join('/');\n if (!rel.startsWith('.')) rel = './' + rel;\n return toProdExtension(rel);\n}\n\n/**\n * 把 src 下的源文件路径转为剥离 src/ 前缀的产物路径(相对 dist 根,POSIX 风格,带 .js 后缀)\n *\n * 用于 src 外 importer(config 文件、本地插件等,位于 rootDir)引用 src 内模块的场景:\n * - 源文件 `<rootDir>/src/lib/errors.ts` → 产物 `dist/lib/errors.js` → 相对 dist 根 `./lib/errors.js`\n *\n * 与 `toProdImportPath` 的区别:后者相对 importer 目录(适用于 importer 也在 src 内的场景,\n * outbase 打平后相对结构不变);本函数相对 src 根(适用于 importer 在 src 外的场景,\n * 需要剥离 src/ 前缀以匹配 compileDevRoutes 的打平产物结构)。\n *\n * 内部用 `toRealPath` 规范化 appDirAbs,兼容 macOS 符号链接(esbuild onLoad 传入的\n * args.path 已是 realpath,未规范化的 appDirAbs 会导致前缀比较失败)。\n */\nfunction toStrippedProdImportPath(sourceFile: string, rootDir: string): string {\n const appDirAbs = toRealPath(path.resolve(rootDir, APP_DIR));\n const sourceReal = toRealPath(sourceFile);\n let rel = path.relative(appDirAbs, sourceReal);\n rel = rel.split(path.sep).join('/');\n if (!rel.startsWith('.')) rel = './' + rel;\n return toProdExtension(rel);\n}\n\n/**\n * 把「相对 dist 根」的产物路径改写为「相对 importer 产物位置」的 import 路径\n *\n * src 外 importer 的产物位置 = importer 源文件相对 rootDir 的路径(.js 后缀),\n * 因为 src 外编译(compileConfig 步骤 1a / compileProjectModules)以 rootDir 为\n * outbase,产物在 `<dist>/` 下保留相对结构。\n *\n * - config `faapi.config.ts` → 产物 `faapi.config.js`(dist 根)→ `./lib/errors.js` 保持不变\n * - 插件 `plugins/db-skills.ts` → 产物 `plugins/db-skills.js`(dist 子目录)\n * → 引用 `./lib/errors.js` 需改写为 `../lib/errors.js`(相对 dist 根的路径在\n * 子目录模块里会解析到 `plugins/lib/errors.js`,错位)\n */\nfunction toProdImportFromImporter(importer: string, rootDir: string, relFromDist: string): string {\n // 两侧 realpath 归一化,兼容 macOS 符号链接(esbuild onLoad 传入的 importer 已是\n // realpath,rootDir 可能仍是 /var 形式——不归一化则 relative 产生绕行路径)\n const importerRel = path\n .relative(toRealPath(path.resolve(rootDir)), toRealPath(importer))\n .split(path.sep)\n .join('/');\n const importerProdDir = path.posix.dirname(toProdExtension(importerRel));\n if (importerProdDir === '.') return relFromDist;\n const target = relFromDist.replace(/^\\.\\//, '');\n let rel = path.posix.relative(importerProdDir, target);\n if (!rel.startsWith('.')) rel = './' + rel;\n return rel;\n}\n\n/**\n * aliasPlugin 扩展选项\n *\n * - `rootDir`:启用 src/ 前缀剥离。当 importer 不在 src 下(如 config 文件)\n * 且引用的源文件在 src 下时,重写为剥离前缀的产物路径(相对 dist 根),以匹配\n * `compileDevRoutes`/`compileBuildRoutes` 的打平产物结构。\n */\nexport interface AliasPluginOptions {\n /** 项目根目录(启用剥离时必填) */\n rootDir?: string;\n}\n\n/**\n * 产物后缀集合(已带这些后缀的 specifier 视为产物路径,不重写)\n */\nconst PROD_EXTS = ['.js', '.mjs', '.cjs'];\n\n/**\n * 源文件后缀集合(重写时尝试这些后缀定位实际文件)\n */\nconst SOURCE_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];\n\n/**\n * index 文件后缀集合(specifier 指向目录时尝试)\n */\nconst INDEX_EXTS = [\n '/index.ts',\n '/index.tsx',\n '/index.js',\n '/index.jsx',\n '/index.mjs',\n '/index.cjs',\n];\n\n/**\n * 解析相对 specifier 到实际源文件绝对路径\n *\n * - 已带产物后缀(.js/.mjs/.cjs)且文件存在 → 返回该路径(不重写)\n * - 已带源后缀(.ts/.tsx/.jsx)且文件存在 → 返回该路径(重写为 .js)\n * - 无后缀 → 尝试各源后缀 + index 文件\n *\n * @param importer 导入文件绝对路径\n * @param specifier 相对 specifier(./xxx 或 ../xxx)\n * @returns 解析到的源文件绝对路径,或 null(未解析到)\n */\nexport function resolveRelativeSpecifier(importer: string, specifier: string): string | null {\n const importerDir = path.dirname(importer);\n const base = path.resolve(importerDir, specifier);\n\n // 已带产物后缀:文件存在则返回(视为产物路径,不重写)\n if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {\n return fs.existsSync(base) ? base : null;\n }\n\n // 已带源后缀:文件存在则返回(将重写为产物后缀)\n if (/\\.(ts|tsx|jsx)$/.test(specifier)) {\n return fs.existsSync(base) ? base : null;\n }\n\n // 无后缀:尝试各源后缀\n for (const ext of SOURCE_EXTS) {\n const file = base + ext;\n if (fs.existsSync(file)) return file;\n }\n\n // 无后缀:尝试 index 文件\n for (const indexExt of INDEX_EXTS) {\n const file = base + indexExt;\n if (fs.existsSync(file)) return file;\n }\n\n return null;\n}\n\n/**\n * esbuild specifier 重写插件(dev/build/config 编译共用)\n *\n * 在 `onLoad` 阶段把源码 import/export 中的 specifier 重写为产物相对路径(带 `.js` 后缀):\n *\n * 1. **相对 specifier**(`./xxx`、`../xxx`):解析到实际源文件,重写为产物相对路径。\n * - 无后缀 → 解析 + 加 `.js`\n * - `.ts`/`.tsx`/`.jsx` → 改为 `.js`\n * - `.js`/`.mjs`/`.cjs` → 不处理(视为产物路径)\n * - 解析失败(文件不存在)→ 原样保留\n *\n * 2. **别名 specifier**(tsconfig.paths,如 `@/xxx`):调 `resolveAlias` 解析候选路径,\n * 命中则重写为产物相对路径。\n *\n * `bundle: false` 模式下 esbuild 不递归解析依赖(`onResolve` 不触发),specifier 会原样\n * 保留到产物 `.js`,运行时 Node.js ESM loader 无法解析(无后缀推断)。本插件通过\n * `onLoad` 介入,确保产物中所有相对 import 都带 `.js` 后缀。\n *\n * **src/ 前缀剥离**(可选,通过 `options.rootDir` 启用):\n * 当 importer 不在 src 下(如 `faapi.config.ts` 位于 rootDir)且引用的源文件在 src 下时,\n * 重写为剥离 src/ 前缀的产物路径(相对 dist 根),以匹配 `compileDevRoutes`/`compileBuildRoutes`\n * 的打平产物结构。典型场景:config 引用项目模块(如 `./src/lib/errors` → `./lib/errors.js`),\n * 使 config 与 routes 共享同一份模块产物,`instanceof` 跨边界生效。\n *\n * 覆盖的 import 形式:\n * - `import { x } from './base'` / `import { x } from '@/base'`\n * - `export { x } from './base'`(含 `from`,被同一正则匹配)\n * - `import('./base')`(动态)\n *\n * 绝对路径(`/`)、`file:` URL、`node:` 协议不处理,交 esbuild 默认。\n */\nexport function createAliasPlugin(\n config: TsconfigPathsConfig,\n options?: AliasPluginOptions,\n): Plugin {\n // 匹配 from '...' / from \"...\" 和 import('...') / import(\"...\")\n const SPEC_RE = /(\\bfrom\\s*|import\\s*\\(\\s*)(['\"])([^'\"]+)\\2/g;\n // 预计算 src 绝对路径(realpath 规范化,用于判断 importer 和 resolved 是否在 src 内)\n // 规范化是为了兼容 macOS 符号链接:esbuild onLoad 传入的 args.path 是 realpath,\n // 未规范化的 appDirAbs 会导致 isInsideDir 比较失败(/var/folders vs /private/var/folders)\n const appDirAbs = options?.rootDir ? toRealPath(path.resolve(options.rootDir, APP_DIR)) : null;\n return {\n name: 'faapi-alias',\n setup(build) {\n build.onLoad({ filter: /\\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {\n let source: string;\n try {\n source = fs.readFileSync(args.path, 'utf8');\n } catch {\n return undefined;\n }\n const importer = args.path;\n // importer 是否在 src 外(启用剥离时才计算,用 isInsideDir 兼容符号链接)\n const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;\n let modified = false;\n const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {\n // 绝对路径 / file: URL / node: 协议不处理\n if (\n specifier.startsWith('/') ||\n specifier.startsWith('file:') ||\n specifier.startsWith('node:')\n ) {\n return full;\n }\n\n // 相对 specifier(./ 或 ../):解析到实际文件并重写\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n const resolved = resolveRelativeSpecifier(importer, specifier);\n if (resolved) {\n // 已带产物后缀的 specifier 不重写(resolveRelativeSpecifier 返回原路径)\n if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {\n return full;\n }\n // src 前缀剥离:importer 在 src 外、resolved 在 src 内\n // → 重写为剥离前缀的产物路径(相对 importer 产物位置)\n if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {\n modified = true;\n return `${prefix}${quote}${toProdImportFromImporter(\n importer,\n options!.rootDir!,\n toStrippedProdImportPath(resolved, options!.rootDir!),\n )}${quote}`;\n }\n modified = true;\n return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;\n }\n return full;\n }\n\n // 别名 specifier:调 resolveAlias 解析\n const candidates = resolveAlias(specifier, config);\n for (const candidate of candidates) {\n for (const ext of SOURCE_EXTS) {\n const file = candidate + ext;\n if (fs.existsSync(file)) {\n modified = true;\n // 别名解析到的文件如果在 src 内且 importer 在 src 外,同样剥离前缀\n if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {\n return `${prefix}${quote}${toProdImportFromImporter(\n importer,\n options!.rootDir!,\n toStrippedProdImportPath(file, options!.rootDir!),\n )}${quote}`;\n }\n return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;\n }\n }\n for (const indexExt of INDEX_EXTS) {\n const file = candidate + indexExt;\n if (fs.existsSync(file)) {\n modified = true;\n if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {\n return `${prefix}${quote}${toProdImportFromImporter(\n importer,\n options!.rootDir!,\n toStrippedProdImportPath(file, options!.rootDir!),\n )}${quote}`;\n }\n return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;\n }\n }\n }\n return full;\n });\n if (!modified) return undefined;\n return { contents: newSource, loader: 'default' };\n });\n },\n };\n}\n\n/**\n * 读取 tsconfig paths 并构造 specifier 重写插件\n *\n * 始终返回含本插件的数组:相对路径重写不依赖 tsconfig(`bundle: false` 下 Node ESM 必需),\n * 别名重写依赖 tsconfig.paths(无 paths 时别名不重写)。\n *\n * @param rootDir 项目根目录。传入时启用 src/ 前缀剥离:importer 在 src 外\n * 且引用 src 内文件时,重写为剥离前缀的产物路径。主要用于 compileConfig\n * 编译 config 文件(位于 rootDir)引用项目模块(位于 src)的场景。\n */\nexport function buildAliasPlugins(rootDir: string): Plugin[] {\n const tsconfig = readTsconfig(rootDir);\n return [createAliasPlugin(tsconfig ?? { baseUrl: '.', paths: {} }, { rootDir })];\n}\n","import type { TsconfigPathsConfig } from './readTsconfig';\n\n/**\n * 按 tsconfig paths 配置把 import specifier 解析为候选绝对路径\n *\n * 纯函数,不检查文件存在性。调用方需自行尝试 .ts/.js/index 等后缀。\n * 支持精确匹配(无 *)和单个 * 通配匹配。\n */\nexport function resolveAlias(specifier: string, config: TsconfigPathsConfig): string[] {\n const candidates: string[] = [];\n\n for (const [pattern, targets] of Object.entries(config.paths)) {\n const wildcardIndex = pattern.indexOf('*');\n\n if (wildcardIndex === -1) {\n // 精确匹配\n if (specifier === pattern) {\n candidates.push(...targets);\n }\n continue;\n }\n\n // 通配匹配:按 * 分割为 prefix / suffix\n const prefix = pattern.slice(0, wildcardIndex);\n const suffix = pattern.slice(wildcardIndex + 1);\n\n if (\n specifier.startsWith(prefix) &&\n specifier.endsWith(suffix) &&\n specifier.length >= prefix.length + suffix.length\n ) {\n const captured = specifier.slice(prefix.length, specifier.length - suffix.length);\n for (const target of targets) {\n candidates.push(target.replace('*', captured));\n }\n }\n }\n\n return candidates;\n}\n","import 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 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 fs from 'node:fs';\nimport { resolveExport } from './resolveExports';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport {\n ensureCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n prodPathToSourcePath,\n} from '../cli/compileOnDemand';\n\n/**\n * 加载后的 tool 模块\n *\n * 与 `RouteModule` 对称——`functionName` 替代 `method`(tool 没有HTTP方法维度)。\n * `handler` 是从模块解析出的 tool 函数,可直接调用。\n */\nexport interface ToolModule {\n /** tool 函数(已校验为 function 类型) */\n handler: (...args: unknown[]) => unknown;\n /** 源码导出名(如 `getWeather`,用于日志/调试,与 `method` 对应) */\n functionName: string;\n}\n\n/**\n * 动态 import tool handler 文件并提取指定函数名的导出\n *\n * Dev 按需编译模式(Vite 风格):先 `ensureCompiled` 确保产物存在再 import,\n * 避免 import 不存在的文件污染 Vite SSR 内部状态(详见 [loadRouteModule](./loadRouteModule.md))。\n * Prod 模式:产物在 build 阶段已固化,直接 import,失败即报错。\n *\n * 错误传递:\n * - 编译失败 → 抛 \"Failed to compile tool module\"\n * - import 失败 → 抛 \"Failed to load tool module\"\n * - 导出不是函数 → 抛 \"does not export a valid function\"\n *\n * @param filePath tool handler 文件的绝对路径(产物形式,如 `dist/tools/weather/handler.js`)\n * @param functionName 源码导出函数名(如 `getWeather`,由 `ToolManifest.functionName` 提供)\n * @param rootDir 项目根目录(按需编译模式用,可选)\n */\nexport async function loadToolModule(\n filePath: string,\n functionName: string,\n rootDir?: string,\n): Promise<ToolModule> {\n // Dev 按需模式:先确保编译再 import\n // 避免\"import 失败 → 编译 → 重试 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 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 tool 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 tool module \"${filePath}\": ${reason}`, { cause: err });\n }\n\n const handler = resolveExport(module, functionName);\n\n // 内联校验(与 validateRouteModule 同构,但错误消息提到 tool 而非 method)\n if (typeof handler !== 'function') {\n throw new Error(\n `Tool module \"${filePath}\" does not export a valid function for \"${functionName}\". ` +\n `Expected a function, got ${handler === undefined ? 'undefined' : typeof handler}.`,\n );\n }\n\n // typeof 缩窄后为 Function 广义类型,断言为具体的函数签名\n return { handler: handler as (...args: unknown[]) => unknown, functionName };\n}\n","import { existsSync } from 'node:fs';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport { isDevOnDemandEnabled, getDevDist } from '../cli/compileOnDemand';\nimport { getRuntimeToolSchemaPath } from '../cli/generateToolArtifacts';\nimport type { ToolMetadata } from '../ast/extractToolMetadata';\n\n/**\n * 加载后的 tool schema 模块\n *\n * 与 [ToolModule](./loadToolModule.md) 对称——`schema` 替代 `handler`。\n *\n * `schema` 是 `unknown` 类型——faapi 核心不依赖 zod(zod 是 peerDep),\n * `zod.js` 由业务方安装的 zod 创建,`@faapi/agent` 负责断言为 zod schema 后\n * 调 `z.toJSONSchema` / `safeParse`。\n */\nexport interface ToolSchemaModule {\n /** zod schema 对象(由业务方安装的 zod 创建) */\n schema: unknown;\n /** schema 导出名(如 `WeatherInputSchema`,用于日志/调试) */\n schemaName: string;\n}\n\n/**\n * 获取当前 dist 目录\n *\n * dev 按需模式:`getDevDist()`(`.faapi`,dev 产物目录固定不可修改)\n * prod 模式:`process.env.FAAPI_DIST`(默认 `dist`,可通过 `--dist` 修改)\n */\nfunction getDist(): string {\n if (isDevOnDemandEnabled()) {\n return getDevDist() ?? '.faapi';\n }\n return process.env.FAAPI_DIST ?? 'dist';\n}\n\n/**\n * 计算 tool 的 zod.js 绝对路径(纯路径计算,无 fs 访问)\n *\n * 与 [loadToolSchema](./loadToolSchema.ts) 内部使用的路径逻辑同源(共享 `getDist()`),\n * 供 `@faapi/agent` 的跨请求 schema 缓存用作缓存键 + mtime 校验目标。\n *\n * @param tool tool 元数据(含 `filePath`)\n * @param rootDir 项目根目录(`tool.filePath` 是相对路径时拼接)\n */\nexport function getToolSchemaPath(tool: ToolMetadata, rootDir?: string): string {\n const dist = getDist();\n return getRuntimeToolSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());\n}\n\n/**\n * 动态加载 tool 的 zod.js schema 模块\n *\n * 与 [loadToolModule](./loadToolModule.md) 对称——一个加载 handler.js(tool 函数),\n * 一个加载 zod.js(tool schema)。\n *\n * 行为:\n * - `tool.inputTypeName` 为 `undefined` → 返回 `undefined`(无 schema,用自由 schema)\n * - zod.js 文件不存在 → 返回 `undefined`(schema 可选,缺失用自由 schema)\n * - import 失败 / 导出名不匹配 → 返回 `undefined`\n *\n * 与 route schema 不同(route schema 缺失抛 `InternalError`),tool schema 是可选的——\n * `@faapi/agent` 的 `resolveToolSchema` 未提供时用自由 schema `{ type: 'object' }`,\n * LLM 自由传参,handler 内部自行处理参数合法性。\n *\n * @param tool tool 元数据(含 `filePath` + `inputTypeName`)\n * @param rootDir 项目根目录(用于计算 zod.js 绝对路径,`tool.filePath` 是相对路径时拼接)\n */\nexport async function loadToolSchema(\n tool: ToolMetadata,\n rootDir?: string,\n): Promise<ToolSchemaModule | undefined> {\n // 无 inputTypeName → 无 zod.js\n if (!tool.inputTypeName) return undefined;\n\n const schemaName = `${tool.inputTypeName}Schema`;\n const zodPath = getToolSchemaPath(tool, rootDir);\n\n // zod.js 文件不存在 → 返回 undefined(schema 可选)\n if (!existsSync(zodPath)) return undefined;\n\n // import zod.js\n try {\n const mod = await importWithCacheBust(zodPath, isDevOnDemandEnabled());\n const schema = mod[`${tool.inputTypeName}Schema`];\n if (!schema) return undefined;\n return { schema, schemaName };\n } catch {\n // import 失败(语法错误等)→ 返回 undefined\n return undefined;\n }\n}\n","import path from 'node:path';\nimport { existsSync } from 'node:fs';\nimport type { ToolManifestList } from '../tools/toolTypes';\nimport type { ToolMetadata } from '../ast/extractToolMetadata';\nimport { extractToolMetadata } from '../ast/extractToolMetadata';\nimport { createPrograms } from '../ast/createProgram';\nimport { toProdFilePath } from '../utils/prodPaths';\nimport { atomicWriteFile } from '../utils/atomicWrite';\nimport { getSchemaOutputPath } from './generateSchemaFiles';\nimport {\n extractTypeInfo,\n createLazyTypeResolver,\n type HandlerTypeInfo,\n type LazyTypeResolver,\n} from '../ast/extractHandlerTypes';\nimport type { RuntimeType } from '../ast/resolveTypeNode';\nimport {\n generateZodSchemaSource,\n generateHelpersFileSource,\n usesCoerceHelpers,\n HELPERS_FILENAME,\n} from '../ast/generateZodSchema';\nimport { getHelpersImportPath } from './generateSchemaFiles';\n\n/**\n * 序列化的 tool manifest 记录(可写入 JS 模块,无函数引用)\n *\n * 与 [ToolMetadata](../ast/extractToolMetadata.md) 字段一一对应,仅 `filePath`\n * 由源码形式(`src/...`)转为产物形式(`<dist>/...`,打平 `src/` 前缀 + dist 前缀 + `.js`)。\n *\n * `undefined` 字段(description / inputTypeName)在 JSON.stringify 时自动省略,\n * 水合时通过 `??` 兜底为 undefined。\n */\nexport interface SerializedToolRecord {\n /** tool 名(`@tool` 覆盖值 或 路径推导值) */\n name: string;\n /** 源码中的导出函数名(供 loadToolModule 在 handler.js 中按名 import) */\n functionName: string;\n /** JSDoc 描述(对 LLM 可见) */\n description?: string;\n /** 第一个参数 interface 名(用于从 zod.js 加载 schema) */\n inputTypeName?: string;\n /** 产物形式路径(如 `dist/tools/weather/handler.js`),供运行时 import tool.js */\n filePath: string;\n}\n\n/**\n * 单个 tool 的 schema 提取结果\n *\n * 与 [RouteSchemaSource](./collectRouteSchemaSources.md) 对称,但 schema key 用 tool 名而非 urlPath。\n */\nexport interface ToolSchemaSource {\n /** tool 名(含 `@tool` 覆盖),用作注释标识 */\n name: string;\n /** 源文件绝对路径(用于 generateToolArtifacts 按文件分组生成 zod.js) */\n filePath: string;\n /** schema 名 = inputTypeName(导出 `${inputTypeName}Schema`) */\n schemaName: string;\n /** AST 提取的类型信息,null 时不导出 Schema */\n typeInfo: HandlerTypeInfo | null;\n}\n\n/**\n * faapi-tools.js 文件名(与 faapi-routes.js 同构)\n */\nconst TOOLS_FILE = 'faapi-tools.js';\n\n/**\n * tool zod.js 输出路径 / 运行时路径计算\n *\n * 与路由 schema 的路径规则完全一致(strip src/ 前缀 + dist + 固定 zod.js 文件名,\n * 见 generateSchemaFiles.ts 的实现),此处别名 re-export 保持 tool 侧语义命名。\n * 此前两份逐字同构实现已合并。\n */\nexport {\n getSchemaOutputPath as getToolSchemaOutputPath,\n getRuntimeSchemaPath as getRuntimeToolSchemaPath,\n} from './generateSchemaFiles';\n/**\n * 序列化 tool 清单为可写入 JS 模块的结构\n *\n * - `filePath` 转为产物形式(打平 `src/` 前缀 + dist 前缀 + `.js`)\n * - 其他字段(name/functionName/description/inputTypeName)直接透传\n * - `undefined` 字段在 JSON.stringify 时自动省略\n *\n * @param tools AST 增强后的 ToolMetadata[](由 generateToolArtifacts 内部从 ToolManifest 增强)\n * @param dist 产物目录(默认 `dist`),用于转换 filePath\n */\nexport function serializeTools(\n tools: ToolMetadata[],\n dist: string = 'dist',\n): SerializedToolRecord[] {\n return tools.map((t) => ({\n name: t.name,\n functionName: t.functionName,\n description: t.description,\n inputTypeName: t.inputTypeName,\n filePath: toProdFilePath(t.filePath, dist),\n }));\n}\n\n/**\n * 把序列化的 tool 清单写入 faapi-tools.js\n *\n * 生成 ESM 模块,运行时 `createAppCore` 通过 `importWithCacheBust` 加载。\n * 用 JSON.stringify 嵌入,保证字符串转义安全(与 `writeRoutesModule` 一致)。\n */\nexport async function writeToolsModule(\n manifest: SerializedToolRecord[],\n outputPath: string,\n): Promise<void> {\n const content = `// 自动生成,请勿手动编辑(faapi build/dev 产物)\nexport const tools = ${JSON.stringify(manifest, null, 2)};\n`;\n await atomicWriteFile(outputPath, content);\n}\n\n/**\n * 从序列化清单水合还原 ToolMetadata[]\n *\n * 字段一一对应(无函数引用需还原)。`undefined` 字段在 JSON.parse 时缺失,\n * 通过 `??` 兜底为 undefined(保证 `ToolMetadata` 类型完整)。\n */\nexport function hydrateTools(manifest: SerializedToolRecord[]): ToolMetadata[] {\n return manifest.map((t) => ({\n name: t.name,\n functionName: t.functionName,\n description: t.description ?? undefined,\n inputTypeName: t.inputTypeName ?? undefined,\n filePath: t.filePath,\n }));\n}\n\n/**\n * 从 ToolMetadata[] 收集 schema 提取所需的原始数据\n *\n * 与 [collectRouteSchemaSources](./collectRouteSchemaSources.ts) 对称,但:\n * - 按 tool handler.ts 文件分组(同一 handler.ts 多个 tool 合并到一个 zod.js)\n * - schema 名 = inputTypeName(导出 `${inputTypeName}Schema`,与路由的 `<METHOD><InputType>Schema` 不同)\n * - coerce=false(tool input 来自 LLM JSON 调用,与 body 一致,无需字符串转换)\n *\n * 跳过 inputTypeName 为 undefined 的 tool(无参数/无类型标注/内联类型字面量),\n * 不生成对应 schema(与路由的\"无类型声明的方法不导出 Schema\"对齐)。\n *\n * 与路由的 collectRouteSchemaSources 一致采用惰性解析:入口类型(inputTypeName)\n * 立即解析且必须严格校验,ref 按需解析并缓存——与 tool 无关的类型零开销。\n *\n * @returns sources(用于生成 zod.js) + resolversByFile(用于解析 ref)\n */\nfunction collectToolSchemaSources(\n tools: ToolMetadata[],\n rootDir: string,\n): {\n sources: ToolSchemaSource[];\n resolversByFile: Map<string, LazyTypeResolver>;\n} {\n // 按文件分组 tool(同一 handler.ts 多个 tool 合并到一个 zod.js)\n const toolsByFile = new Map<string, ToolMetadata[]>();\n for (const tool of tools) {\n if (!tool.inputTypeName) continue; // 跳过无 inputTypeName 的 tool\n const absPath = path.resolve(rootDir, tool.filePath);\n let list = toolsByFile.get(absPath);\n if (!list) {\n list = [];\n toolsByFile.set(absPath, list);\n }\n list.push(tool);\n }\n\n // 批量共享 Program:同一次提取只创建一个 Program,避免逐文件全量解析\n const programByFile = createPrograms([...toolsByFile.keys()]);\n const resolversByFile = new Map<string, LazyTypeResolver>();\n for (const filePath of toolsByFile.keys()) {\n resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath)!, filePath));\n }\n\n // 为每个 tool 提取 schema\n const sources: ToolSchemaSource[] = [];\n for (const [filePath, fileTools] of toolsByFile) {\n const program = programByFile.get(filePath)!;\n for (const tool of fileTools) {\n // inputTypeName 已在外层过滤,这里一定非空(防御性兜底)\n const inputTypeName = tool.inputTypeName!;\n const typeInfo = extractTypeInfo(program, filePath, inputTypeName); // 入口类型,严格解析\n sources.push({\n name: tool.name,\n filePath,\n schemaName: inputTypeName, // schema 名 = inputTypeName\n typeInfo,\n });\n }\n }\n\n return { sources, resolversByFile };\n}\n\n/**\n * 生成单个 tool handler.ts 的 zod.js 源码\n *\n * 自包含:[extractAllTypes](../ast/extractHandlerTypes.md) 在 AST 阶段已通过 TypeScript checker\n * 内联跨文件类型,每个 zod.js 无需 import 其他 zod.js。`ref` 仅用于同文件内的循环引用\n * (通过 `z.lazy` 处理)。\n *\n * 导出格式:\n * - `${inputTypeName}Schema`:zod schema 对象(用于 safeParse 校验)\n *\n * 与 [generateSchemaFileSource](./generateSchemaFiles.ts) 的差异:\n * - schema 名直接取自 `inputTypeName`(无 HTTP 方法前缀)\n * - coerce=false(tool input 来自 LLM JSON 调用,与 body 一致)\n *\n * 无 inputTypeName 的 tool 不导出对应 Schema(collectToolSchemaSources 已过滤)。\n *\n * @param sources 同一文件的 schema 提取结果(含多个 tool)\n * @param resolveType ref 解析函数(解析循环引用中的 ref):名称 → 运行时类型,未声明返回 undefined\n * @param helpersImportPath 到 faapi-helpers.js 的相对 import 路径(如 `../../faapi-helpers.js`)。\n * 传空字符串表示不注入 coerce helpers 的 import(用于无 helpers 引用的文件或测试场景)。\n */\nexport function generateToolSchemaFileSource(\n sources: ToolSchemaSource[],\n resolveType: (name: string) => RuntimeType | undefined,\n helpersImportPath: string,\n): string {\n const lines: string[] = [\"import { z } from 'zod';\"];\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 // tool schema coerce=false(input 来自 LLM JSON 调用,与 body 一致)\n const coerce = false;\n\n const block = [`// ${source.name} → ${schemaName}`];\n // generateZodSchemaSource 自带 import 语句,剥离后由本函数统一管理 import\n // 传入 schemaName 作为 exportName,确保导出名与运行时查找一致(`${inputTypeName}Schema`)\n const schemaCode = generateZodSchemaSource(typeInfo, resolveType, schemaName, coerce).replace(\n /^import \\{ z \\} from 'zod';\\s*\\n\\s*\\n/,\n '',\n );\n block.push(schemaCode);\n block.push('');\n schemaBlocks.push(block.join('\\n'));\n }\n\n // 检测是否有 schema 引用了 coerce 公用函数(Map/Set 还原,即使 coerce=false 也可能引用)\n const allSchemaCode = 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 * 检测并生成 faapi-helpers.js(若不存在且 schema 引用了公用函数)\n *\n * tool schema 在两种场景下会引用 faapi-helpers.js 的公用变量:\n * 1. Map/Set 字段:即使 coerce=false 也引用 `coerceMap` / `coerceSet`(JSON.parse 出来的是数组/对象,需还原)\n * 2. number/boolean 字段:coerce=true 时引用 `coerceNumber` / `coerceBoolean`\n * (tool schema coerce=false,不会触发此场景)\n *\n * 与 [generateSchemaFiles](./generateSchemaFiles.ts) 共享同一份 `faapi-helpers.js`——\n * 若文件已存在则不重复生成(路由 schema 生成阶段可能已生成)。\n */\nasync function maybeGenerateHelpers(allSourceCode: string, distDir: string): Promise<void> {\n if (!usesCoerceHelpers(allSourceCode)) return;\n const helpersPath = path.resolve(distDir, HELPERS_FILENAME);\n if (existsSync(helpersPath)) return;\n await atomicWriteFile(helpersPath, generateHelpersFileSource());\n}\n\n/**\n * 主入口:从 ToolManifest[] 生成 faapi-tools.js + 每个 tool 的 zod.js\n *\n * 内部流程:\n * 1. 对每个 ToolManifest 调 `createProgram` + `extractToolMetadata` → ToolMetadata[]\n * (AST 增强:补全 description / inputTypeName / `@tool` 覆盖后的 name)\n * 2. `serializeTools(metadata, dist)` → SerializedToolRecord[](filePath 转产物形式)\n * 3. `writeToolsModule(serialized, faapiToolsPath)` → 写入 `<dist>/faapi-tools.js`\n * 4. 若 `skipSchema=true`(dev 按需模式)→ 返回,不生成 zod.js\n * 5. `collectToolSchemaSources` 从 ToolMetadata[] 收集 schema 源数据(按文件分组)\n * 6. 为每个文件计算 `helpersImportPath` + 生成 zod.js 源码(暂存)\n * 7. `maybeGenerateHelpers` 检测并生成/复用 `faapi-helpers.js`\n * 8. 并行写入所有 zod.js\n *\n * @param tools scanTools 产出的 ToolManifest[](仅路径推导字段)\n * @param rootDir 项目根目录\n * @param dist 产物目录(`.faapi` 或 `dist`)\n * @param options.skipSchema 跳过 zod.js 生成(dev 按需模式启动时仅生成清单)\n * @returns AST 增强后的 ToolMetadata[](供调用方日志/调试)\n */\nexport async function generateToolArtifacts(\n tools: ToolManifestList,\n rootDir: string,\n dist: string,\n options?: { skipSchema?: boolean },\n): Promise<ToolMetadata[]> {\n // 1. AST 增强:对每个 manifest 调 extractToolMetadata(批量共享 Program)\n const metadata: ToolMetadata[] = [];\n const programByFile = createPrograms(tools.map((m) => path.resolve(rootDir, m.filePath)));\n for (const manifest of tools) {\n const absPath = path.resolve(rootDir, manifest.filePath);\n const program = programByFile.get(absPath)!;\n const result = extractToolMetadata(program, absPath, manifest.functionName, {\n name: manifest.name,\n filePath: manifest.filePath,\n });\n if (result) {\n metadata.push(result);\n }\n }\n\n // 2. 序列化 + 写入 faapi-tools.js\n const serialized = serializeTools(metadata, dist);\n const toolsPath = path.resolve(rootDir, dist, TOOLS_FILE);\n await writeToolsModule(serialized, toolsPath);\n\n // 4. dev 按需模式:跳过 zod.js 生成(首次请求时由 ensureToolSchemaGenerated 按需生成)\n if (options?.skipSchema) {\n return metadata;\n }\n\n // 5-8. 生成 zod.js\n if (metadata.length === 0) {\n return metadata;\n }\n\n const { sources, resolversByFile } = collectToolSchemaSources(metadata, rootDir);\n if (sources.length === 0) {\n return metadata; // 无 inputTypeName 的 tool,不生成 zod.js\n }\n\n // 按文件分组 sources(同一 handler.ts 多个 tool 合并到一个 zod.js)\n const sourcesByFile = new Map<string, ToolSchemaSource[]>();\n for (const source of sources) {\n let list = sourcesByFile.get(source.filePath);\n if (!list) {\n list = [];\n sourcesByFile.set(source.filePath, list);\n }\n list.push(source);\n }\n\n // 为每个文件生成 zod.js 源码(先暂存,用于检测是否需要 helpers)\n const fileEntries: { outputPath: string; source: string }[] = [];\n for (const [filePath, fileSources] of sourcesByFile) {\n 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 的路径(用于 import helpers)\n let relForDir = relFile;\n if (relForDir.startsWith('src/')) {\n relForDir = relForDir.slice(4);\n }\n const dirIdx = relForDir.lastIndexOf('/');\n const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : '';\n const helpersImportPath = getHelpersImportPath(zodRelDir);\n\n const source = generateToolSchemaFileSource(\n fileSources,\n (name) => resolver?.resolve(name)?.runtimeType,\n helpersImportPath,\n );\n fileEntries.push({ outputPath, source });\n }\n\n // 检测是否需要生成 faapi-helpers.js\n const allSourceCode = fileEntries.map((e) => e.source).join('\\n');\n const distDir = path.resolve(rootDir, dist);\n await maybeGenerateHelpers(allSourceCode, distDir);\n\n // 并行写入所有 zod.js\n await Promise.all(\n fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source)),\n );\n\n return metadata;\n}\n\n/**\n * 写入 tool zod.js 文件(确保目录存在)\n */\nasync function writeToolSchemaFile(outputPath: string, source: string): Promise<void> {\n await atomicWriteFile(outputPath, source);\n}\n","import ts from 'typescript';\nimport {\n extractDescription,\n extractJSDocTagValue,\n getJSDocFromNode,\n hasExportModifier,\n} from './jsDocMetadata';\n\n/**\n * Tool 的 LLM 可见核心字段\n *\n * 描述\"tool 是什么\"——LLM 真正需要消费的字段(发往 LLM 的 tool 定义只含\n * `name` / `description` / input schema),**不含**代码本体加载细节\n * (`filePath` / `functionName` / `inputTypeName`)。\n *\n * 与 [AgentCore](./extractAgentMetadata.md) 对称——LLM-facing 字段与代码加载\n * 细节分离,便于未来扩展(如 DB-driven tool 只实现 `ToolCore` 即可)。\n *\n * `toolRegistry` 查询入口 / `@faapi/agent` 子包的 `buildToolDefinitions`\n * 都消费 `ToolCore` 字段组装 LLM tool 列表。\n */\nexport interface ToolCore {\n /** tool 名(`@tool` JSDoc 覆盖值 或 路径推导值) */\n name: string;\n /** JSDoc 描述(tool 描述,对 LLM 可见),无 JSDoc 或 JSDoc 无自由文本时为 `undefined` */\n description?: string;\n}\n\n/**\n * Tool 完整元数据(文件型 tool)\n *\n * 继承 [ToolCore](./extractToolMetadata.md) 的 LLM 字段,额外扩展**代码本体加载细节**:\n * - `filePath` — `loadToolModule` 加载 `handler.js` 产物定位函数用\n * - `functionName` — 源码导出函数名(不受 `@tool` 覆盖影响,AST 定位 + 运行时 resolveExport 用)\n * - `inputTypeName` — 第一个参数的 TypeReference 名(供 [extractTypeInfo](./extractHandlerTypes.md)\n * 生成 zod schema;运行时 `resolveToolSchema` 据此定位 `zod.js`)\n *\n * 由 [extractToolMetadata](./extractToolMetadata.md) 产出,合并路径推导字段\n * (来自 [scanTools](../tools/scanTools.md) 的 `ToolManifest`)与 AST 提取字段\n * (JSDoc 描述、`@tool` 覆盖名、第一个参数 interface 名)。\n *\n * 字段来源:\n * - `name` — `@tool` JSDoc 覆盖值,或 `pathMeta.name`(路径推导)\n * - `description` — JSDoc 注释块自由文本(对 LLM 可见)\n * - `filePath` / `functionName` — 由 `pathMeta` 透传\n * - `inputTypeName` — 第一个参数的 TypeReference 名(供 [extractTypeInfo](./extractHandlerTypes.md) 生成 zod schema)\n */\nexport interface ToolMetadata extends ToolCore {\n /** 第一个参数的 interface/type 名(用于生成 zod schema),\n * 无参数/参数无类型标注/参数为内联类型字面量时为 `undefined` */\n inputTypeName?: string;\n /** 源码相对路径(从 `pathMeta` 透传) */\n filePath: string;\n /** 源码中的导出函数名(从 `pathMeta` 透传,AST 定位用,不受 `@tool` 覆盖影响) */\n functionName: string;\n}\n\n/**\n * 路径推导的 tool 元数据(由 [scanTools](../tools/scanTools.ts) 计算)\n *\n * 透传到 [ToolMetadata](./extractToolMetadata.ts) 输出,与 AST 提取字段合并。\n */\nexport interface ToolPathMeta {\n /** 路径推导的 tool 名(如 `weather.getWeather`) */\n name: string;\n /** 源码相对路径(如 `src/tools/weather/handler.ts`) */\n filePath: string;\n}\n\n/** 函数节点联合类型(FunctionDeclaration / ArrowFunction / FunctionExpression) */\ntype FunctionLike = ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression;\n\n/**\n * 从 tool handler.ts 提取单个 tool 的元数据\n *\n * 提取内容:\n * 1. **JSDoc 描述** — 注释块自由文本(`@tag` 之前的首段),对 LLM 可见\n * 2. **`@tool` 覆盖名** — JSDoc 中 `@tool` 标签后的文本(如 `customName`),覆盖路径推导的 `name`\n * 3. **第一个参数 interface 名** — 供 [extractTypeInfo](./extractHandlerTypes.ts) 生成 zod schema\n *\n * 不提取(由 `pathMeta` 透传): `filePath` / `functionName`\n *\n * 函数查找支持四种导出形式,与 [scanTools](../tools/scanTools.ts) 的 `TOOL_EXPORT_RE` 正则同构:\n * - `export function name() {}`\n * - `export async function name() {}`\n * - `export const name = () => {}`\n * - `export const name = async () => {}`(以及 `= function () {}`)\n *\n * JSDoc 查找对箭头函数/函数表达式自动回溯到外层 `VariableStatement`\n * (JSDoc 通常写在 `export const` 上方,而非箭头函数本身)。\n *\n * @param program TypeScript Program\n * @param filePath 源文件**绝对路径**(AST 用,需与 `program.getSourceFile` 一致)\n * @param functionName 源码导出函数名(在文件中查找的目标)\n * @param pathMeta 路径推导的元数据(scanTools 已计算)\n * @returns `ToolMetadata` 或 `null`(函数未找到或源文件不在 Program 中)\n */\nexport function extractToolMetadata(\n program: ts.Program,\n filePath: string,\n functionName: string,\n pathMeta: ToolPathMeta,\n): ToolMetadata | null {\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) return null;\n\n const found = findExportedFunction(sourceFile, functionName);\n if (!found) return null;\n\n const { fn, jsDocOwner } = found;\n const jsDoc = getJSDocFromNode(jsDocOwner);\n const description = extractDescription(jsDoc);\n const toolNameOverride = extractJSDocTagValue(jsDoc, 'tool');\n const inputTypeName = getFirstParamTypeName(fn, sourceFile);\n\n return {\n name: toolNameOverride ?? pathMeta.name,\n description,\n inputTypeName,\n filePath: pathMeta.filePath,\n functionName,\n };\n}\n\n/** 函数查找结果:函数节点 + JSDoc 持有节点 */\ninterface FoundFunction {\n /** 函数节点(FunctionDeclaration / ArrowFunction / FunctionExpression) */\n fn: FunctionLike;\n /** JSDoc 持有节点(FunctionDeclaration 时为函数本身;箭头/函数表达式时为外层 VariableStatement) */\n jsDocOwner: ts.Node;\n}\n\n/**\n * 在源文件中查找指定名称的导出函数\n *\n * 遍历顶层节点,匹配四种导出形式。返回函数节点和 JSDoc 持有节点:\n * - `export function name` → 两者都是 FunctionDeclaration 本身\n * - `export const name = () => {}` → fn 是 ArrowFunction,jsDocOwner 是外层 VariableStatement\n * (JSDoc 通常写在 `export const` 上方,而非箭头函数本身)\n *\n * 不依赖 `node.parent` 链——`ts.createProgram` 配置 `noEmit: true` 时不会设置父指针,\n * 故改在遍历时直接绑定 JSDoc 持有节点。\n *\n * 非 export 的同名函数不被识别。\n */\nfunction findExportedFunction(\n sourceFile: ts.SourceFile,\n functionName: string,\n): FoundFunction | null {\n let result: FoundFunction | null = null;\n\n ts.forEachChild(sourceFile, (node) => {\n if (result) return;\n\n // export function name() {} / export async function name() {}\n if (\n ts.isFunctionDeclaration(node) &&\n hasExportModifier(node) &&\n node.name?.text === functionName\n ) {\n result = { fn: node, jsDocOwner: node };\n return;\n }\n\n // export const name = () => {} / export const name = function () {}\n if (ts.isVariableStatement(node) && hasExportModifier(node)) {\n for (const decl of node.declarationList.declarations) {\n if (result) break;\n const nameText = ts.isIdentifier(decl.name)\n ? decl.name.text\n : decl.name.getText(sourceFile);\n if (nameText !== functionName || !decl.initializer) continue;\n if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {\n // JSDoc 通常在 VariableStatement 上(写在 `export const` 上方)\n result = { fn: decl.initializer, jsDocOwner: node };\n }\n }\n }\n });\n\n return result;\n}\n\n/**\n * 提取第一个参数的 interface/type 名(TypeReference)\n *\n * - 参数有 TypeReference 类型标注(如 `input: WeatherInput`)→ `'WeatherInput'`\n * - 参数无类型标注 / 内联类型字面量(如 `input: { city: string }`)→ `undefined`\n * - 函数无参数 → `undefined`\n * - 多参数时取第一个参数的类型名\n *\n * 返回 `undefined` 而非抛错——tool 允许无参数输入或无 schema 校验,\n * 是否生成 `zod.js` 由下游 `generateToolArtifacts` 根据 `inputTypeName` 是否为 `undefined` 决定。\n */\nfunction getFirstParamTypeName(fn: FunctionLike, sourceFile: ts.SourceFile): string | undefined {\n const firstParam = fn.parameters[0];\n if (!firstParam) return undefined;\n if (!firstParam.type) return undefined;\n if (!ts.isTypeReferenceNode(firstParam.type)) return undefined;\n return firstParam.type.typeName.getText(sourceFile);\n}\n","import ts from 'typescript';\n\n/**\n * 判断节点是否有 `export` 修饰符\n */\nexport function hasExportModifier(node: ts.Node): boolean {\n if (!ts.canHaveModifiers(node)) return false;\n const modifiers = ts.getModifiers(node);\n return !!modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);\n}\n\n/**\n * 从节点提取第一个 JSDoc 注释块\n *\n * 1. 标准 API(`ts.getJSDocCommentsAndTags`,jsDocCache 已缓存时命中)\n * 2. 回退:直接访问解析器存入的 `node.jsDoc` 数组(jsDocCache 未同步的情况)\n */\nexport function getJSDocFromNode(node: ts.Node): ts.JSDoc | undefined {\n const apiDocs = ts\n .getJSDocCommentsAndTags(node)\n .filter((entry): entry is ts.JSDoc => ts.isJSDoc(entry));\n if (apiDocs.length > 0) return apiDocs[0];\n\n const directDocs = (node as unknown as { jsDoc?: ts.JSDoc[] }).jsDoc;\n if (directDocs && directDocs.length > 0) return directDocs[0];\n\n return undefined;\n}\n\n/**\n * 提取 JSDoc 描述(注释块自由文本,`@tag` 之前的首段)\n *\n * 多行描述保留换行(TypeScript 已自动剥离每行前缀 ` * `)。\n * 无 JSDoc / JSDoc 无自由文本 / 描述仅空白 / comment 为富文本结构 → `undefined`。\n */\nexport function extractDescription(jsDoc: ts.JSDoc | undefined): string | undefined {\n if (!jsDoc) return undefined;\n if (typeof jsDoc.comment !== 'string') return undefined;\n const trimmed = jsDoc.comment.trim();\n return trimmed || undefined;\n}\n\n/**\n * 提取 `@<tagName>` 标签的覆盖名\n *\n * - `@tool weather.current` / `@agent researcher` → 标签值文本\n * - 花括号包裹(`@tool {name}`)去除花括号\n * - 无指定标签 / 标签无 comment 文本 → `undefined`(调用方回退到路径推导值,不报错)\n */\nexport function extractJSDocTagValue(\n jsDoc: ts.JSDoc | undefined,\n tagName: string,\n): string | undefined {\n if (!jsDoc || !jsDoc.tags) return undefined;\n for (const tag of jsDoc.tags) {\n if (tag.tagName.text !== tagName) continue;\n // tag.comment 类型为 string | false | undefined;false 表示该 tag 无 comment 文本\n if (typeof tag.comment !== 'string') return undefined;\n const text = tag.comment.trim();\n if (!text) return undefined;\n const cleaned = text.replace(/^\\{|\\}$/g, '').trim();\n return cleaned || undefined;\n }\n return undefined;\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 { FaapiMiddleware } from './middlewareTypes';\n\nexport interface CorsOptions {\n origin?: string | string[] | true;\n methods?: string[];\n allowedHeaders?: string[];\n exposeHeaders?: string[];\n credentials?: boolean;\n maxAge?: number;\n}\n\nconst DEFAULT_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];\n\n/**\n * 创建 CORS 中间件(洋葱模型)\n *\n * - origin=true: 允许所有来源(反射请求的 Origin)\n * - origin=string: 允许指定来源\n * - origin=string[]: 允许多个来源\n *\n * OPTIONS 预检请求直接返回 204,不调用 next()。\n */\nexport function cors(options: CorsOptions = {}): FaapiMiddleware {\n const {\n origin = true,\n methods = DEFAULT_METHODS,\n allowedHeaders,\n exposeHeaders,\n credentials = false,\n maxAge,\n } = options;\n\n return async (ctx, next) => {\n const reqOrigin = ctx.headers.get('origin');\n if (!reqOrigin) {\n await next();\n return;\n }\n\n // 确定允许的来源\n let allowOrigin: string | null = null;\n if (origin === true) {\n allowOrigin = reqOrigin; // 反射 Origin\n } else if (typeof origin === 'string') {\n allowOrigin = reqOrigin === origin ? origin : null;\n } else if (Array.isArray(origin)) {\n allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;\n }\n\n // 当 origin 为动态值(true 或数组)时,响应内容(是否带 ACAO)随 Origin 头变化\n // ——不匹配被拒的响应同样必须带 Vary: Origin。缺了它,CDN/浏览器按 URL 缓存\n // \"无 ACAO 的拒绝响应\"后,可能服务给后续合法 Origin 的请求(缓存污染面)\n if (origin === true || Array.isArray(origin)) {\n const existingVary = ctx.headers.get('vary');\n if (existingVary) {\n if (!existingVary.toLowerCase().includes('origin')) {\n ctx.setHeader('Vary', `${existingVary}, Origin`);\n }\n } else {\n ctx.setHeader('Vary', 'Origin');\n }\n }\n\n if (!allowOrigin) {\n await next();\n return;\n }\n\n // 设置 CORS 响应头\n ctx.setHeader('Access-Control-Allow-Origin', allowOrigin);\n\n ctx.setHeader('Access-Control-Allow-Methods', methods.join(', '));\n\n if (allowedHeaders) {\n ctx.setHeader('Access-Control-Allow-Headers', allowedHeaders.join(', '));\n } else {\n // 反射请求头\n const requestHeaders = ctx.headers.get('access-control-request-headers');\n if (requestHeaders) {\n ctx.setHeader('Access-Control-Allow-Headers', requestHeaders);\n }\n }\n\n if (exposeHeaders && exposeHeaders.length > 0) {\n ctx.setHeader('Access-Control-Expose-Headers', exposeHeaders.join(', '));\n }\n\n if (credentials) {\n ctx.setHeader('Access-Control-Allow-Credentials', 'true');\n }\n\n if (maxAge !== undefined) {\n ctx.setHeader('Access-Control-Max-Age', String(maxAge));\n }\n\n // 处理 preflight 请求:直接返回 204,不调用 next()\n if (ctx.method === 'OPTIONS') {\n return new Response(null, { status: 204 });\n }\n\n await next();\n };\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\n\nexport type LoggerFn = (messageOrObj: string | Record<string, unknown>, message?: string) => void;\n\nexport interface LoggerOptions {\n /**\n * 自定义日志函数\n *\n * - 传入 `console.log`(默认):纯文本格式 `GET /api/users 200 12ms`\n * - 传入 pino logger:结构化日志 `logger.info({ method, path, status, durationMs }, 'request completed')`\n * - 传入 winston logger:`logger.info('GET /api/users 200 12ms', { method, path })`\n */\n log?: LoggerFn;\n}\n\n/**\n * 创建请求日志中间件(洋葱模型)\n *\n * 日志格式(文本模式):GET /api/users 200 12ms\n * 错误格式(文本模式):POST /api/users 400 45ms - Error: ...\n *\n * 结构化模式:传入 pino/winston 等 logger 实例时,会自动传递结构化字段。\n *\n * before/after 一体,闭包变量共享开始时间,无需污染 ctx。\n * 错误用 try/catch 捕获,记录后重新抛出(让上层处理)。\n * 成功时从 next() 返回的 Response 读取状态码。\n *\n * log 函数每次请求时读取(options.log ?? console.log),运行时替换 console.log 会生效。\n */\nexport function logger(options: LoggerOptions = {}): FaapiMiddleware {\n return async (ctx, next) => {\n const log = options.log ?? console.log;\n const start = Date.now();\n try {\n const response = await next();\n const duration = Date.now() - start;\n const entry = {\n method: ctx.method,\n path: ctx.path,\n status: response.status,\n durationMs: duration,\n };\n log(entry, `${ctx.method} ${ctx.path} ${response.status} ${duration}ms`);\n return response;\n } catch (err) {\n const duration = Date.now() - start;\n const message = err instanceof Error ? err.message : String(err);\n const status = (err as { statusCode?: number })?.statusCode ?? 500;\n const entry = {\n method: ctx.method,\n path: ctx.path,\n status,\n durationMs: duration,\n error: message,\n };\n log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);\n throw err;\n }\n };\n}\n","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 path from 'node:path';\nimport fs from 'node:fs';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport type { FaapiConfig } from './configTypes';\n\n/**\n * 配置产物文件名(compileConfig 生成)\n */\nconst CONFIG_PRODUCT_FILE = 'faapi-config.js';\n\n/**\n * 加载 faapi 配置文件\n *\n * 统一读取 `<dist>/faapi-config.js` 产物:\n * - dev 模式:`faapi dev` 启动时由 `compileConfig` 生成 `.faapi/faapi-config.js`\n * - prod 模式:`faapi build` 时由 `compileConfig` 生成 `dist/faapi-config.js`\n *\n * 产物由 `compileConfig` 在构建阶段编译,运行时不读源码、不现场编译。\n * 环境变量由 `loadEnv` 从 `.env` 系列文件加载到 `process.env`,配置文件中通过 `process.env.XXX` 读取。\n *\n * - 产物存在 → import 并返回 default\n * - 产物不存在但源码有配置文件 → 抛错(强制 rebuild)\n * - 源码也无配置文件 → 返回 `null`(配置可选)\n *\n * @param rootDir 项目根目录\n * @param dist 产物目录(如 'dist' 或 '.faapi')\n * @returns 配置对象,无配置文件时返回 null\n */\nexport async function loadConfig(\n rootDir: string,\n dist: string,\n): Promise<Partial<FaapiConfig> | null> {\n const configProductPath = path.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);\n\n if (fs.existsSync(configProductPath)) {\n const module = (await importWithCacheBust(configProductPath)) as {\n default?: Partial<FaapiConfig>;\n };\n return module.default ?? {};\n }\n\n // 产物不存在:检查源码是否有配置文件\n const hasSourceConfig =\n fs.existsSync(path.join(rootDir, 'faapi.config.ts')) ||\n fs.existsSync(path.join(rootDir, 'faapi.config.js'));\n\n if (hasSourceConfig) {\n throw new Error(\n `[faapi] ${dist}/${CONFIG_PRODUCT_FILE} 不存在,请先执行 \\`faapi build\\`(或 \\`faapi dev\\`)生成产物。`,\n );\n }\n\n return null;\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * env 决定规则:NODE_ENV || 'development'\n *\n * 调用方应在调 loadEnv 之前自行兜底 NODE_ENV(dev 设 'development',prod 设 'production')。\n */\nfunction resolveEnv(): string {\n return process.env.NODE_ENV || 'development';\n}\n\n/**\n * .env 文件加载顺序(从低到高优先级,后加载的覆盖先加载的)\n *\n * 1. .env — 所有环境共享\n * 2. .env.local — 本地覆盖(不提交 git)\n * 3. .env.{env} — 按环境覆盖\n * 4. .env.{env}.local — 按环境本地覆盖\n */\nfunction getEnvFiles(env: string): string[] {\n return ['.env', '.env.local', `.env.${env}`, `.env.${env}.local`];\n}\n\n/**\n * 解析 .env 文件内容为键值对象\n *\n * 格式:\n * - `KEY=VALUE` 基本格式\n * - `# 注释` 行首注释\n * - `export KEY=VALUE` 支持 export 前缀\n * - 单引号:字面量,不展开变量,不处理转义\n * - 双引号:支持变量展开($VAR / ${VAR})和转义(\\n \\t \\r \\\\ \\\")\n * - 无引号:字面量,` #` 后为行内注释\n *\n * @param content .env 文件内容\n * @param fileVars 本文件之前已解析的变量(用于变量展开)\n * @returns 解析出的键值对象\n */\nfunction parseEnvFile(content: string, fileVars: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n // 兼容 CRLF / LF\n const lines = content.replace(/\\r\\n/g, '\\n').split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n // 空行 / 注释行跳过\n if (!trimmed || trimmed.startsWith('#')) continue;\n\n // 匹配 KEY=VALUE 或 export KEY=VALUE\n // KEY 必须符合 [A-Za-z_][A-Za-z0-9_]*\n const match = /^(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*(.*)$/.exec(trimmed);\n if (!match) continue;\n\n // 正则有两个捕获组,匹配成功则必非空\n const key = match[1]!;\n const rawValue = match[2]!;\n const value = parseValue(rawValue, { ...fileVars, ...result });\n result[key] = value;\n }\n\n return result;\n}\n\n/**\n * 解析单个值的引号/展开/转义\n *\n * - 单引号:字面量(去除首尾单引号)\n * - 双引号:处理转义 + 展开变量\n * - 无引号:字面量,去除行内注释(` #` 后为注释)\n */\nfunction parseValue(raw: string, env: Record<string, string>): string {\n if (raw === '') return '';\n\n // 单引号:字面量(raw 非空已由调用方保证)\n if (raw[0] === \"'\") {\n const end = raw.indexOf(\"'\", 1);\n return end === -1 ? raw.slice(1) : raw.slice(1, end);\n }\n\n // 双引号:转义 + 展开变量(支持 \\\" 转义引号,不作为结束符)\n if (raw[0] === '\"') {\n const match = /^\"((?:\\\\.|[^\"\\\\])*)\"/.exec(raw);\n // 匹配失败时 slice(1) 兜底(无闭合引号)\n const inner = match ? match[1]! : raw.slice(1);\n return expandEscapesAndVars(inner, env);\n }\n\n // 无引号:字面量,处理行内注释\n // ` #` 前必须有空白才算注释;# 紧贴值(如 url#anchor)不算注释\n const commentMatch = /^(.*?)(\\s+#.*)$/.exec(raw);\n const value = commentMatch ? commentMatch[1]! : raw;\n return value.trim();\n}\n\n/**\n * 双引号值:处理转义字符 + 展开变量\n *\n * 转义:\\n \\r \\t \\\\ \\\" (未知转义保留字符本身)\n * 展开:$VAR / ${VAR},优先 env 参数,其次 process.env,未定义返回空字符串\n */\nfunction expandEscapesAndVars(str: string, env: Record<string, string>): string {\n // 先处理转义(避免变量值中的 $ 被二次展开)\n const escaped = str.replace(/\\\\(.)/g, (_, ch: string) => {\n switch (ch) {\n case 'n':\n return '\\n';\n case 'r':\n return '\\r';\n case 't':\n return '\\t';\n case '\\\\':\n return '\\\\';\n case '\"':\n return '\"';\n default:\n return ch; // 未知转义保留字符\n }\n });\n\n // 展开变量:$VAR 或 ${VAR}\n return escaped.replace(\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}|\\$([A-Za-z_][A-Za-z0-9_]*)/g,\n (_, braced, plain) => {\n const varName = braced || plain;\n return env[varName] ?? process.env[varName] ?? '';\n },\n );\n}\n\n/**\n * 加载 `.env` 系列文件到 `process.env`\n *\n * 按 Next.js 约定加载四级文件(从低到高):\n * 1. `.env` — 所有环境共享\n * 2. `.env.local` — 本地覆盖\n * 3. `.env.{env}` — 按环境覆盖\n * 4. `.env.{env}.local` — 按环境本地覆盖\n *\n * env 由 `NODE_ENV || 'development'` 决定。调用方应在调 loadEnv 之前自行兜底 NODE_ENV\n * (dev 设 'development',prod 设 'production')。\n *\n * 合并规则:\n * - 后加载的文件覆盖先加载的同名变量\n * - **shell 已设置的变量不被覆盖**(`process.env` 已有的值优先)\n *\n * @param rootDir 项目根目录(`.env` 文件所在目录)\n */\nexport function loadEnv(rootDir: string): void {\n const env = resolveEnv();\n const files = getEnvFiles(env);\n\n // 收集所有文件解析结果,按优先级合并(后面的覆盖前面的)\n const merged: Record<string, string> = {};\n for (const file of files) {\n const filePath = path.join(rootDir, file);\n if (!fs.existsSync(filePath)) continue;\n const content = fs.readFileSync(filePath, 'utf-8');\n const parsed = parseEnvFile(content, merged);\n Object.assign(merged, parsed);\n }\n\n // 写入 process.env(不覆盖已有的 shell 变量)\n for (const [key, value] of Object.entries(merged)) {\n if (process.env[key] === undefined) {\n process.env[key] = value;\n }\n }\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 { Server } from 'node:http';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { PassThrough, Readable } from 'node:stream';\nimport type { RouteManifest, WsRouteManifest, RoutesRef } from '../router/routeTypes';\nimport { sortRoutes } from '../router/sortRoutes';\nimport { detectRouteConflicts } from '../router/detectRouteConflicts';\nimport { createServer } from '../server/createServer';\nimport { applyPluginWrappers } from '../server/startServer';\nimport { loadConfig } from '../config/loadConfig';\nimport { hydrateRoutes, type SerializedRouteManifest } from './generateRoutes';\nimport { hydrateTools, type SerializedToolRecord } from './generateToolArtifacts';\nimport { hydrateAgents, type SerializedAgentRecord } from './generateAgentArtifacts';\nimport { loadPlugins } from './loadPlugins';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport {\n createAppRegistries,\n defaultRegistries,\n type AppRegistries,\n} from '../injection/registries';\nimport type { ToolMetadata } from '../ast/extractToolMetadata';\nimport type { AgentMetadata } from '../ast/extractAgentMetadata';\nimport type { FaapiConfig } from '../config/configTypes';\nimport { ROUTE_PATTERNS } from '../utils/prodPaths';\n\nexport interface InjectOptions {\n method?: string;\n path?: string;\n headers?: Record<string, string>;\n query?: Record<string, string>;\n body?: unknown;\n}\n\nexport interface InjectResponse {\n status: number;\n headers: Headers;\n body: unknown;\n}\n\n/** 默认产物目录(prod 模式,对应 `faapi build` 默认输出到 `dist`) */\nconst DEFAULT_DIST = 'dist';\n/** 默认端口 */\nconst DEFAULT_PORT = 3000;\n/** 路由清单文件名(build/dev 启动时生成,必需产物) */\nconst ROUTES_FILE = 'faapi-routes.js';\n/** tool 清单文件名(build/dev 启动时生成,可选产物——无 tool 的项目不生成) */\nconst TOOLS_FILE = 'faapi-tools.js';\n/** agent 清单文件名(build/dev 启动时生成,可选产物——无 agent 的项目不生成) */\nconst AGENTS_FILE = 'faapi-agents.js';\n/** 路由源码目录(写死为 src,路由 .ts 文件位于 src/api/ 下) */\n\n/**\n * 加载 faapi-tools.js 并水合到 toolRegistry(单例)\n *\n * 与路由清单不同,tool 是可选能力——纯 API 项目可能没有 `faapi-tools.js`,\n * 此时不报错,返回空数组,toolRegistry 保持空。\n *\n * dev 按需模式下 `reloadTools` 也会调用此函数重新水合(`setLoadTimestamp` 已在外层设置)。\n *\n * @returns 水合后的 ToolMetadata[](供调用方日志/调试)\n */\nexport async function loadAndHydrateTools(\n rootDir: string,\n dist: string,\n registries: AppRegistries = defaultRegistries,\n): Promise<ToolMetadata[]> {\n const toolsPath = path.resolve(rootDir, dist, TOOLS_FILE);\n if (!fs.existsSync(toolsPath)) {\n return [];\n }\n const serialized = (await importWithCacheBust(toolsPath)) as unknown as {\n tools: SerializedToolRecord[];\n };\n const hydrated = hydrateTools(serialized.tools ?? []);\n registries.tool.hydrate(hydrated);\n return hydrated;\n}\n\n/**\n * 加载 faapi-agents.js 并水合到 agentRegistry(单例)\n *\n * 与 `loadAndHydrateTools` 对称——agent 是可选能力,纯 API 项目可能没有 `faapi-agents.js`,\n * 此时不报错,返回空数组,agentRegistry 保持空。\n *\n * dev 按需模式下 `reloadAgents` 也会调用此函数重新水合(`setLoadTimestamp` 已在外层设置)。\n *\n * @returns 水合后的 AgentMetadata[](供调用方日志/调试)\n */\nexport async function loadAndHydrateAgents(\n rootDir: string,\n dist: string,\n registries: AppRegistries = defaultRegistries,\n): Promise<AgentMetadata[]> {\n const agentsPath = path.resolve(rootDir, dist, AGENTS_FILE);\n if (!fs.existsSync(agentsPath)) {\n return [];\n }\n const serialized = (await importWithCacheBust(agentsPath)) as unknown as {\n agents: SerializedAgentRecord[];\n };\n const hydrated = hydrateAgents(serialized.agents ?? []);\n registries.agent.hydrate(hydrated);\n return hydrated;\n}\n\n/**\n * 当前 app 单例的 globalThis key\n *\n * 用 `Symbol.for` 创建全局 symbol,确保跨模块实例共享同一个 key——\n * Next.js 16 默认用 Turbopack 作为 `next dev` 的 bundler,Turbopack dev server runtime\n * 与主进程的 Node.js 原生 module cache 是两套独立缓存,用模块级变量无法跨实例共享,\n * 必须借助 `globalThis`。\n *\n * 注:生产模式(`node dist/main` + `next build`)不受此问题影响——`next build` 产物是\n * 普通 JS 文件,运行时通过 Node.js 原生 require 加载,与主进程共享 module cache。\n * 此机制主要解决 dev 模式下 RSC 调用 `getApp()` 的问题,对生产模式无副作用。\n */\nconst APP_INSTANCE_KEY = Symbol.for('faapi.app.instance');\n\n/**\n * 读取当前 app 单例(从 globalThis 取,跨模块实例共享)\n *\n * 注意:单例仅指向\"最近一次创建且未关闭的 app\"。测试场景下创建多个临时 app 时,\n * 单例会被覆盖,但 close 时只有当单例仍指向当前 app 才置 null,避免被后续 app 误清。\n */\nfunction getCurrentApp(): AppBase | null {\n return (globalThis as Record<symbol, AppBase | undefined>)[APP_INSTANCE_KEY] ?? null;\n}\n\n/** 设置/清除当前 app 单例(写入 globalThis,跨模块实例共享) */\nfunction setCurrentApp(app: AppBase | null): void {\n if (app === null) {\n delete (globalThis as Record<symbol, AppBase | undefined>)[APP_INSTANCE_KEY];\n } else {\n (globalThis as Record<symbol, AppBase | undefined>)[APP_INSTANCE_KEY] = app;\n }\n}\n\n/** 默认优雅关闭信号 handler 是否已安装(写入 globalThis,跨模块实例共享) */\nconst SHUTDOWN_INSTALLED_KEY = Symbol.for('faapi.defaultShutdownInstalled');\n\n/**\n * 注册默认优雅关闭信号(SIGTERM/SIGINT,进程级仅注册一次)\n *\n * 收到信号时关闭当前 app 单例(app.close() 内部完成 drain + onClose 钩子 +\n * 注册表清理)后退出。faapi 单进程单 app 设计——单例即唯一运行中的 app;\n * 测试场景多次 listen 不会堆积 process 监听器(globalThis 标记防重装)。\n */\nfunction registerDefaultShutdownHandlers(): void {\n const g = globalThis as Record<symbol, unknown>;\n if (g[SHUTDOWN_INSTALLED_KEY]) return;\n g[SHUTDOWN_INSTALLED_KEY] = true;\n\n const shutdown = (signal: string): void => {\n console.log(`\\n- Received ${signal}, shutting down...`);\n void (async () => {\n const app = getCurrentApp();\n if (app) await app.close();\n process.exit(0);\n })();\n };\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n process.on('SIGINT', () => shutdown('SIGINT'));\n}\n\n/**\n * 获取当前 faapi app 单例\n *\n * 用于在无法直接拿到 app 引用的场景(如 Next.js Server Component)中访问 app。\n *\n * 通过 `globalThis` 共享单例,确保 Next.js 16 Turbopack dev server runtime 加载的\n * `@faapi/faapi` 模块实例与主进程 `faapi dev` 设置单例的实例能读到同一个 app 引用。\n *\n * @returns 当前 app 实例\n * @throws 未初始化时抛错(需先调 `createProdApp()` / `createDevApp()`,或 `faapi dev` / `node dist/main` 启动)\n *\n * @example\n * ```ts\n * // Next.js RSC 中调用 faapi API(同进程,跳过 HTTP loopback)\n * import { getApp } from '@faapi/faapi';\n * import { headers } from 'next/headers';\n *\n * const app = getApp();\n * const h = await headers();\n * const res = await app.inject({\n * method: 'GET',\n * path: '/api/user',\n * headers: { cookie: h.get('cookie') ?? '', authorization: h.get('authorization') ?? '' },\n * });\n * const data = res.body; // 已解析\n * ```\n */\nexport function getApp(): AppBase {\n const app = getCurrentApp();\n if (!app) {\n throw new Error(\n '[faapi] No app instance. Call createProdApp() / createDevApp() first, or run `faapi dev` / `node dist/main`.',\n );\n }\n return app;\n}\n\n/** FaapiConfig 的内置 key 集合(排除自定义业务配置) */\nconst FAAPI_CONFIG_KEYS = new Set([\n 'cors',\n 'lifecycle',\n 'middlewares',\n 'injectors',\n 'extendContext',\n 'plugins',\n 'helmet',\n 'compression',\n 'etag',\n 'bodyLimit',\n 'logger',\n 'http2',\n 'trustedProxy',\n 'response',\n]);\n\nfunction isFaapiConfigKey(key: string): boolean {\n return FAAPI_CONFIG_KEYS.has(key);\n}\n\nexport interface CreateAppOptions {\n /** 项目根目录,默认 process.cwd() */\n rootDir?: string;\n /** 产物输出目录(如 dist 或 .faapi),覆盖环境变量 FAAPI_DIST,默认 'dist' */\n dist?: string;\n /** 端口号,也可在 listen() 时传入;默认环境变量 PORT 或 3000 */\n port?: number;\n}\n\n/** 应用基础接口(dev/prod 共用,不含 reloadRoutes) */\nexport interface AppBase {\n /** Node.js Server 实例(listen 后可用,close 后置 null) */\n server: Server | null;\n /** app 级注册表(tool/agent/skill/agentHandle 实例,close 时清理) */\n registries: AppRegistries;\n /** 排序后的路由清单 */\n routes: RouteManifest;\n /** WebSocket 路由清单 */\n wsRoutes: WsRouteManifest;\n /** 项目根目录 */\n rootDir: string;\n /** 启动 HTTP server,打印路由表,执行 onReady 钩子 */\n listen(port?: number): Promise<Server>;\n /** 关闭 server,执行 onClose 钩子 */\n close(): Promise<void>;\n /**\n * 无服务器测试注入\n *\n * 构建一个模拟请求直接走完整请求链路(CORS / helmet / logger / 全局中间件 / 路由匹配 /\n * schema 校验 / 目录中间件 / handler),不绑定端口,返回已解析的 `{ status, headers, body }`。\n *\n * `listen()` 前后均可调用——`listen()` 后调用常用于 Next.js Server Component 等同进程场景\n * (配合 `getApp()` 拿到 app 实例)。\n */\n inject(options?: InjectOptions): Promise<InjectResponse>;\n}\n\n/**\n * 内部上下文(供 dev 扩展 reloadRoutes 使用)\n *\n * prod 模式不使用此上下文——createProdApp 直接返回 AppBase。\n */\nexport interface AppContext {\n /** 项目根目录 */\n rootDir: string;\n /** app 级注册表(tool/agent/skill/agentHandle 实例,随 app 生命周期) */\n registries: AppRegistries;\n /** 产物目录 */\n dist: string;\n /** 扫描 patterns(scanRoutes 用) */\n patterns: string[];\n /** Node.js Server 实例(未 listen) */\n server: Server;\n /** 路由可变引用容器(createServer 闭包和 reloadRoutes 共享) */\n routesRef: RoutesRef;\n /** 配置(原始 FaapiConfig 或 null) */\n config: FaapiConfig | null;\n /**\n * 更新路由引用(app.routes/wsRoutes + routesRef + 闭包变量)\n *\n * dev reloadRoutes 调用此方法把新扫描的路由同步到 app 和 server。\n */\n updateRoutes(routes: RouteManifest, wsRoutes: WsRouteManifest): void;\n}\n\n/**\n * 创建应用基础实例(dev/prod 共用逻辑)\n *\n * 完成:配置加载 → 路由清单水合 → 创建 server → 插件加载。\n * 返回 AppBase(listen/close)+ AppContext(供 dev 扩展 reloadRoutes)。\n *\n * dist 由 `process.env.FAAPI_DIST` 决定:\n * - `faapi dev` 启动时固定设为 `.faapi` → 读 dev 产物\n * - `node <dist>/main` 不设 → 默认 `dist`,读 prod 产物\n *\n * 不负责编译 TypeScript——编译由 `faapi dev`(esbuild → `.faapi/`)和 `faapi build`(→ `dist/`)负责。\n * 不负责生成路由清单——`faapi dev`/`faapi build` 启动时生成 `faapi-routes.js`,createAppBase 直接水合。\n */\nexport async function createAppBase(options?: CreateAppOptions): Promise<{\n app: AppBase;\n ctx: AppContext;\n}> {\n const rootDir = options?.rootDir ?? process.cwd();\n const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;\n\n // 校验产物存在性\n const routesPath = path.resolve(rootDir, dist, ROUTES_FILE);\n if (!fs.existsSync(routesPath)) {\n throw new Error(\n `[faapi] ${dist}/${ROUTES_FILE} 不存在,请先执行 \\`faapi build\\`(或 \\`faapi dev\\`)生成产物。`,\n );\n }\n\n // 加载配置(统一读 <dist>/faapi-config.js)\n const config = await loadConfig(rootDir, dist);\n\n // 水合路由清单(统一路径,无 dev/prod 分支)\n const serialized = (await importWithCacheBust(routesPath)) as unknown as SerializedRouteManifest;\n const hydrated = await hydrateRoutes(serialized);\n let sorted = sortRoutes(hydrated.routes);\n let wsRoutes = hydrated.wsRoutes;\n\n // 检测路由冲突\n const conflicts = detectRouteConflicts(sorted);\n if (conflicts.length > 0) {\n for (const conflict of conflicts) {\n console.warn(`! 路由冲突: ${conflict.method} ${conflict.urlPath}`);\n for (const file of conflict.files) {\n console.warn(` - ${file}`);\n }\n }\n }\n\n // app 级注册表(方案 A 实例化):每个 app 持有独立实例,随 app 创建/销毁,\n // 多 app 同进程互不串台。框架链路(请求注入 / 插件 / lifecycle)只读写此实例\n const registries = createAppRegistries();\n\n // 水合 tool 清单(可选产物——无 tool 的项目跳过,tool 注册表保持空)\n const tools = await loadAndHydrateTools(rootDir, dist, registries);\n\n // 水合 agent 清单(可选产物——无 agent 的项目跳过,agent 注册表保持空)\n const agents = await loadAndHydrateAgents(rootDir, dist, registries);\n\n // 自定义业务配置(排除内置 key)\n const pluginConfig: Record<string, unknown> = config\n ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k)))\n : {};\n\n // 创建 server(不 listen)\n const { server, routesRef } = createServer({\n routes: sorted,\n rootDir,\n dist,\n cors: config?.cors ?? true,\n onError: config?.lifecycle?.onError,\n config: (config as Record<string, unknown> | null) ?? undefined,\n wsRoutes,\n middlewares: config?.middlewares,\n injectors: config?.injectors,\n helmet: config?.helmet,\n compression: config?.compression,\n etag: config?.etag,\n logger: config?.logger,\n bodyLimit: config?.bodyLimit,\n http2: config?.http2,\n trustedProxy: config?.trustedProxy,\n registries,\n });\n\n // 加载插件 + 应用 handler/upgrade 包装器(dist 供本地 TS 插件按需编译/产物复用)\n const { handlerWrappers, upgradeWrappers } = await loadPlugins(\n config?.plugins,\n {\n rootDir,\n registries,\n routes: sorted,\n getRoutes: () => sorted,\n server,\n config: pluginConfig,\n },\n rootDir,\n dist,\n );\n applyPluginWrappers(server, handlerWrappers, upgradeWrappers);\n\n // 关闭状态标记(避免重复关闭)\n let closed = false;\n\n const app: AppBase = {\n server: null,\n registries,\n routes: sorted,\n wsRoutes,\n rootDir,\n\n async listen(listenPort?: number): Promise<Server> {\n // 端口优先级:listen() 参数 > options.port > 环境变量 PORT > 默认 3000\n const envPort = process.env.PORT ? Number(process.env.PORT) : undefined;\n const actualPort = listenPort ?? options?.port ?? envPort ?? DEFAULT_PORT;\n\n // onBoot 生命周期钩子(listen 前):启动校验 / DB 迁移等\"失败即不该暴露端口\"的逻辑。\n // 此时 server 已创建但未监听、路由/tool/agent 清单已水合、插件已加载。\n // 抛错 → listen() 以原始错误 reject,server.listen 不会被调用,端口不暴露。\n if (config?.lifecycle?.onBoot) {\n try {\n await config.lifecycle.onBoot({ rootDir, routes: sorted, server, registries });\n console.log('- onBoot hook executed');\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`[faapi] onBoot hook failed: ${message}`);\n throw err;\n }\n }\n\n return new Promise<Server>((resolve, reject) => {\n // listen 阶段错误(端口占用等)转为 Promise reject,避免未监听 'error'\n // 事件以裸堆栈崩掉进程;成功后解除,运行期错误语义不变\n const onListenError = (err: Error): void => {\n if ((err as NodeJS.ErrnoException).code === 'EADDRINUSE') {\n reject(\n new Error(\n `Port ${actualPort} is already in use. ` +\n `Is another faapi instance running? Change the port via the PORT env var.`,\n ),\n );\n return;\n }\n reject(err);\n };\n server.once('error', onListenError);\n server.listen(actualPort, async () => {\n server.off('error', onListenError);\n const address = server.address();\n const p = typeof address === 'object' && address !== null ? address.port : actualPort;\n\n console.log('faapi server started');\n console.log(`- Local: http://localhost:${p}`);\n console.log('- Loaded routes:');\n for (const route of sorted) {\n console.log(` ${route.method.padEnd(6)}${route.urlPath} ${route.filePath}`);\n }\n if (wsRoutes.length > 0) {\n console.log('- WebSocket routes:');\n for (const route of wsRoutes) {\n console.log(` WS ${route.urlPath} ${route.filePath}`);\n }\n }\n if (tools.length > 0) {\n console.log(`- Loaded ${tools.length} tool(s):`);\n for (const tool of tools) {\n console.log(` ${tool.name} ${tool.filePath}`);\n }\n }\n if (agents.length > 0) {\n console.log(`- Loaded ${agents.length} agent(s):`);\n for (const agent of agents) {\n const exports = agent.hasRun ? 'run' : '-';\n console.log(` ${agent.name} [${exports}] ${agent.filePath}`);\n }\n }\n\n // 注册默认优雅关闭信号(进程级仅注册一次,faapi 单进程单 app 设计)\n registerDefaultShutdownHandlers();\n\n // onReady 生命周期钩子\n if (config?.lifecycle?.onReady) {\n await config.lifecycle.onReady({ rootDir, routes: sorted, server, registries });\n console.log('- onReady hook executed');\n }\n\n app.server = server;\n resolve(server);\n });\n });\n },\n\n async inject(injectOpts?: InjectOptions): Promise<InjectResponse> {\n const {\n method = 'GET',\n path: reqPath = '/',\n headers: reqHeaders = {},\n query,\n body,\n } = injectOpts ?? {};\n\n const queryStr = query\n ? '?' +\n new URLSearchParams(Object.entries(query).map(([k, v]) => [k, String(v)])).toString()\n : '';\n\n return new Promise<InjectResponse>((resolve, reject) => {\n // mockRes 需为 Writable Stream(PassThrough)以支持 sendNodeResponse 的 pipe 路径\n // (handler 返回值被自动包裹后产生 JSON body,走 nodeStream.pipe(res))\n const chunks: Buffer[] = [];\n const mockRes = new PassThrough() as PassThrough & {\n statusCode: number;\n _headers: Record<string, string>;\n setHeader(name: string, value: string): void;\n appendHeader(name: string, value: string): void;\n writeHead(status: number, headers?: Record<string, string>): void;\n };\n mockRes.statusCode = 200;\n mockRes._headers = {};\n mockRes.setHeader = function (name: string, value: string) {\n this._headers[name.toLowerCase()] = value;\n };\n mockRes.appendHeader = function (name: string, value: string) {\n const key = name.toLowerCase();\n const existing = this._headers[key];\n this._headers[key] = existing ? `${existing}, ${value}` : value;\n };\n mockRes.writeHead = function (status: number, headers?: Record<string, string>) {\n this.statusCode = status;\n if (headers) {\n Object.assign(this._headers, headers);\n }\n };\n\n mockRes.on('data', (chunk: Buffer) => chunks.push(chunk));\n mockRes.on('error', reject);\n mockRes.on('finish', () => {\n const body = Buffer.concat(chunks);\n let parsed: unknown;\n try {\n parsed = JSON.parse(body.toString());\n } catch {\n parsed = body.toString();\n }\n resolve({\n status: mockRes.statusCode,\n headers: new Headers(mockRes._headers as Record<string, string>),\n body: parsed,\n });\n });\n\n const listeners = server.listeners('request');\n const handler = listeners[listeners.length - 1];\n if (typeof handler !== 'function') {\n reject(new Error('No request handler found'));\n return;\n }\n\n const mockReq: Readable & {\n method?: string;\n url?: string;\n headers?: Record<string, string | undefined>;\n socket?: { remoteAddress?: string };\n } = Readable.from(body !== undefined ? [Buffer.from(JSON.stringify(body))] : []);\n mockReq.method = method;\n mockReq.url = `${reqPath}${queryStr}`;\n mockReq.headers = {\n ...reqHeaders,\n host: 'localhost',\n 'content-type': body !== undefined ? 'application/json' : undefined,\n };\n mockReq.socket = { remoteAddress: '127.0.0.1' };\n\n handler(\n mockReq as unknown as import('node:http').IncomingMessage,\n mockRes as unknown as import('node:http').ServerResponse,\n );\n });\n },\n\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n\n const s = server as unknown as {\n closeIdleConnections?: () => void;\n closeAllConnections?: () => void;\n };\n\n // 停止接受新连接 + 断开空闲 keep-alive 连接;在途请求继续执行直至完成(drain)。\n // 此前 closeAllConnections 在等待之前调用,会直接掐断在途请求(硬关,非优雅停机)\n s.closeIdleConnections?.();\n\n if (config?.lifecycle?.onClose) {\n await config.lifecycle.onClose({ rootDir, routes: sorted, server, registries });\n }\n\n // 注册表(方案 A 实例化):清理的是 app 自己的实例——多 app 同进程\n // 天然隔离,无需所有权守卫;全局默认实例不被 app 生命周期触碰\n registries.tool.clear();\n registries.agent.clear();\n registries.skill.clear();\n registries.agentHandle.clear();\n\n // server 未 listen 时直接清理状态(避免 ERR_SERVER_NOT_RUNNING 错误)\n if (!server.listening) {\n app.server = null;\n // 清理单例(仅当单例仍指向当前 app 时,避免被后续 app 误清)\n if (getCurrentApp() === app) setCurrentApp(null);\n return;\n }\n\n const drained = new Promise<void>((resolve) => {\n server.close((err) => {\n if (err) console.error('Error closing server:', err);\n resolve();\n });\n });\n\n // drain:等在途请求完成。SSE/WS 长连接永不主动结束,超时后强制断开\n // (默认 10s,FAAPI_SHUTDOWN_TIMEOUT_MS 环境变量可调)\n const drainTimeoutMs = Number(process.env.FAAPI_SHUTDOWN_TIMEOUT_MS ?? 10_000);\n if (\n typeof s.closeAllConnections === 'function' &&\n Number.isFinite(drainTimeoutMs) &&\n drainTimeoutMs >= 0\n ) {\n const forceClose = new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n s.closeAllConnections?.();\n resolve();\n }, drainTimeoutMs);\n // 停机定时器不应阻止进程自然退出\n timer.unref?.();\n });\n await Promise.race([drained, forceClose]);\n // 强制断开后 close 回调随即触发;短兜底等待收尾完成\n const tail = new Promise<void>((resolve) => setTimeout(resolve, 250).unref?.());\n await Promise.race([drained, tail]);\n } else {\n await drained;\n }\n\n app.server = null;\n // 清理单例(仅当单例仍指向当前 app 时,避免被后续 app 误清)\n if (getCurrentApp() === app) setCurrentApp(null);\n },\n };\n\n // 设置单例(覆盖之前的实例;测试场景下多次创建会覆盖,close 时只清自己)\n // 通过 globalThis 存储,确保 Next.js Turbopack dev server runtime 加载的模块实例也能读到\n setCurrentApp(app);\n\n /** 更新路由引用(app + routesRef + 闭包变量) */\n const ctx: AppContext = {\n rootDir,\n registries,\n dist,\n patterns: ROUTE_PATTERNS,\n server,\n routesRef,\n config,\n updateRoutes(newRoutes: RouteManifest, newWsRoutes: WsRouteManifest): void {\n sorted = newRoutes;\n wsRoutes = newWsRoutes;\n app.routes = newRoutes;\n app.wsRoutes = newWsRoutes;\n routesRef.current = newRoutes;\n routesRef.wsCurrent = newWsRoutes;\n },\n };\n\n return { app, ctx };\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 type { RouteManifest } from './routeTypes';\n\nexport interface RouteConflict {\n method: string;\n urlPath: string;\n files: string[];\n}\n\n/**\n * 检测路由冲突(相同 method + urlPath 的多个文件)\n */\nexport function detectRouteConflicts(routes: RouteManifest): RouteConflict[] {\n const map = new Map<string, RouteConflict>();\n\n for (const route of routes) {\n const key = `${route.method} ${route.urlPath}`;\n const existing = map.get(key);\n if (existing) {\n existing.files.push(route.filePath);\n } else {\n map.set(key, {\n method: route.method,\n urlPath: route.urlPath,\n files: [route.filePath],\n });\n }\n }\n\n // 只返回有冲突的(文件数 > 1)\n const conflicts: RouteConflict[] = [];\n for (const conflict of map.values()) {\n if (conflict.files.length > 1) {\n conflicts.push(conflict);\n }\n }\n\n return conflicts;\n}\n","import {\n createServer as createHttpServer,\n type Server,\n type IncomingMessage,\n type ServerResponse,\n} from 'node:http';\nimport { createSecureServer as createHttp2SecureServer } from 'node:http2';\nimport { readFileSync } from 'node:fs';\nimport { Readable } from 'node:stream';\nimport path from 'node:path';\nimport type { RouteManifest, RouteMatch, WsRouteManifest, RoutesRef } from '../router/routeTypes';\nimport { matchRoute, findAllowedMethods } from '../router/matchRoute';\nimport { loadRouteModule } from '../loader/loadRouteModule';\nimport { createContextFromUrl } from '../runtime/createContext';\nimport { resolveInputFromUrl, resolveBodyForQueryMethod } from '../runtime/resolveInput';\nimport { invokeHandler, compose, mergeMeta } from '../runtime/invokeHandler';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { sendNodeResponse } from '../response/sendNodeResponse';\nimport {\n RouteNotFoundError,\n MethodNotAllowedError,\n ValidationError,\n PayloadTooLargeError,\n} from '../errors/httpErrors';\nimport { validateInput } from '../validator/validateInput';\nimport { getInputTypeForMethod, hasBody } from '../runtime/inputType';\nimport { getClientIp } from '../utils/getClientIp';\nimport { cors, type CorsOptions } from '../middleware/cors';\nimport { helmet, type HelmetOptions } from '../middleware/helmet';\nimport { compression, type CompressionOptions } from '../middleware/compression';\nimport { etag, type EtagOptions } from '../middleware/etag';\nimport type { AppRegistries } from '../injection/registries';\nimport { logger as loggerMiddleware, type LoggerOptions } from '../middleware/logger';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { attachWebSocket } from './handleWsUpgrade';\nimport { nodeHttpToWebHeaders, buildErrorResponse } from './serverUtils';\nimport { getRuntimeSchemaPath } from '../cli/generateSchemaFiles';\nimport {\n ensureSchemaGenerated,\n ensureMiddlewaresCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n} from '../cli/compileOnDemand';\nimport { loadMergedMiddlewares } from '../middleware/loadMiddlewares';\n\n/**\n * 将 Node.js IncomingMessage 转为 Web Request 对象\n *\n * 协议判断:\n * 1. 优先使用 X-Forwarded-Proto 头(反向代理场景)\n * 2. 回退到 http(HTTPS 由外部代理处理)\n */\nconst DEFAULT_BODY_LIMIT = 10 * 1024 * 1024; // 10MB\n\nfunction toWebRequest(\n req: IncomingMessage,\n bodyLimit: number = DEFAULT_BODY_LIMIT,\n requestSignal?: AbortSignal,\n): {\n request: Request;\n /** 已解析的 URL(pathname/searchParams 由调用方复用,免重复 new URL) */\n url: URL;\n} {\n // 协议判断:优先 X-Forwarded-Proto(反向代理),否则 http\n const forwardedProto = req.headers['x-forwarded-proto'];\n const protocol = Array.isArray(forwardedProto)\n ? (forwardedProto[0]?.split(',')[0]?.trim() ?? 'http')\n : (forwardedProto?.split(',')[0]?.trim() ?? 'http');\n const host = req.headers.host ?? 'localhost';\n // 全请求唯一一次 URL 解析——pathname/searchParams 由 ctx/routePipeline 复用\n const url = new URL(req.url ?? '/', `${protocol}://${host}`);\n\n const headers = nodeHttpToWebHeaders(req);\n\n const method = req.method ?? 'GET';\n\n // GET/HEAD 不应该有 body\n if (method === 'GET' || method === 'HEAD') {\n return {\n request: new Request(url.toString(), { method, headers, signal: requestSignal }),\n url,\n };\n }\n\n // content-length 快速判定:声明长度超限直接抛 PayloadTooLargeError,\n // 免去流包装 + 逐 chunk 读取(chunked 无此头,仍走 limitStreamSize 流式限流)\n const contentLength = req.headers['content-length'];\n if (contentLength !== undefined) {\n const declared = Number(Array.isArray(contentLength) ? contentLength[0] : contentLength);\n if (Number.isFinite(declared) && declared > bodyLimit) {\n throw new PayloadTooLargeError(bodyLimit);\n }\n }\n\n // 将 Node.js IncomingMessage 转为 Web ReadableStream\n // 并限制请求体大小(防止 DoS)\n const stream = Readable.toWeb(req) as ReadableStream<Uint8Array>;\n const limitedStream = limitStreamSize(stream, bodyLimit);\n return {\n request: new Request(url.toString(), {\n method,\n headers,\n body: limitedStream,\n duplex: 'half',\n signal: requestSignal,\n } as RequestInit),\n url,\n };\n}\n\n/**\n * 限制 ReadableStream 的总字节数,超过限制时通过 controller.error 抛 PayloadTooLargeError\n *\n * 错误传播路径:controller.error → Request body 读取方(resolveInput)抛 →\n * handleRequest catch → formatErrorResponse(命中 PayloadTooLargeError 分支)→ 413 响应\n *\n * 健壮性处理:\n * - reader.read() 抛错(客户端断开等)→ controller.error + releaseLock,避免泄漏\n * - 超限或异常后释放 reader lock,避免悬挂引用\n * - 取消时同步 cancel 上游 reader\n */\nfunction limitStreamSize(\n stream: ReadableStream<Uint8Array>,\n maxSize: number,\n): ReadableStream<Uint8Array> {\n let totalSize = 0;\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n let errored = false;\n\n const releaseReader = (): void => {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // 锁已释放或 reader 已 closed,忽略\n }\n reader = undefined;\n }\n };\n\n const failStream = (\n controller: ReadableStreamDefaultController<Uint8Array>,\n err: unknown,\n ): void => {\n if (errored) return;\n errored = true;\n controller.error(err instanceof Error ? err : new Error(String(err)));\n releaseReader();\n };\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (!reader) reader = stream.getReader();\n try {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n releaseReader();\n return;\n }\n totalSize += value.byteLength;\n if (totalSize > maxSize) {\n // error 让流进入 errored 状态,下游 read() 会 reject\n failStream(controller, new PayloadTooLargeError(maxSize));\n return;\n }\n controller.enqueue(value);\n } catch (err) {\n // reader.read() 抛错(客户端断开、底层流异常等)\n failStream(controller, err);\n }\n },\n cancel(reason) {\n if (reader) {\n try {\n reader.cancel(reason);\n } catch {\n // 忽略上游 cancel 失败\n }\n releaseReader();\n }\n },\n });\n}\n\n/**\n * 查找路径的所有允许方法(用于 405 响应)\n *\n * 实现移至 [matchRoute](../router/matchRoute.ts)(共享路由索引:静态段 O(1) 直查,\n * 仅动态段线性扫描)。404 非热路径,但扫描器/探活探测的 404 高频场景下\n * 索引化仍有收益。\n */\n\nexport interface CreateServerOptions {\n routes: RouteManifest;\n rootDir: string;\n /** 产物输出目录(如 '.faapi' 或 'dist'),用于计算 schema 路径 */\n dist: string;\n cors?: CorsOptions | boolean;\n /** 请求错误钩子(在错误响应生成后调用,用于副作用;不修改已发出的响应) */\n onError?: (error: unknown, ctx: FaapiContext) => Promise<void> | void;\n /** 自定义业务配置(来自 faapi.config.ts,注入到 ctx.config) */\n config?: Record<string, unknown>;\n /** WebSocket 路由清单(空数组则不挂载 WS 支持) */\n wsRoutes?: WsRouteManifest;\n /** 全局中间件(来自 faapi.config.ts,对所有路由生效,最外层) */\n middlewares?: FaapiMiddleware[];\n /** 全局注入器(来自 faapi.config.ts,对所有路由 handler 参数注入生效) */\n injectors?: InjectorMap;\n /** 安全头配置 */\n helmet?: HelmetOptions | boolean;\n /** 响应压缩(gzip/deflate/br 协商),默认关闭,详见 middleware/compression.md */\n compression?: CompressionOptions | boolean;\n /** ETag/304 条件请求协商,默认关闭,详见 middleware/etag.md */\n etag?: EtagOptions | boolean;\n /** app 级注册表(tool/agent/skill/agentHandle)——经 FaapiContext 进入请求链路 */\n registries?: AppRegistries;\n /** 请求日志配置,默认启用(与 cors 一致) */\n logger?: LoggerOptions | boolean;\n /** 请求体大小限制(字节) */\n bodyLimit?: number;\n /** HTTP/2 配置,启用时需提供 SSL 证书路径 */\n http2?: Http2Options | boolean;\n /**\n * 是否信任反向代理头(X-Forwarded-For),默认 false\n *\n * true 时 `ctx.ip` 取 `x-forwarded-for` 第一个 IP(nginx/CDN 场景);\n * false(默认)时直取 socket 地址——直连部署下 XFF 可被伪造,安全默认不信任。\n */\n trustedProxy?: boolean;\n}\n\nexport interface Http2Options {\n key?: string;\n cert?: string;\n}\n\n/**\n * 创建 faapi HTTP server\n *\n * @param options 路由清单、根目录\n * @returns Node.js Server 实例\n */\nexport function createServer(options: CreateServerOptions): {\n server: Server;\n routesRef: RoutesRef;\n} {\n const {\n routes,\n rootDir,\n dist,\n cors: corsOption,\n onError,\n config,\n wsRoutes,\n middlewares: globalMiddlewares,\n injectors: globalInjectors,\n helmet: helmetOption,\n compression: compressionOption,\n etag: etagOption,\n registries,\n logger: loggerOption,\n bodyLimit = DEFAULT_BODY_LIMIT,\n http2: http2Option,\n trustedProxy = false,\n } = options;\n\n // 路由可变引用容器(watch 模式热替换时 reloadRoutes 更新 .current/.wsCurrent)\n const routesRef: RoutesRef = { current: routes, wsCurrent: wsRoutes ?? [] };\n\n // Build middleware chain from config options\n const configMiddlewares: FaapiMiddleware[] = [];\n\n // Compression — 显式启用;链最外层,包住完整链路使最终响应被压缩\n if (compressionOption) {\n const compOpts = typeof compressionOption === 'object' ? compressionOption : {};\n configMiddlewares.push(compression(compOpts));\n }\n\n // CORS\n const corsMiddleware: FaapiMiddleware | null =\n corsOption === false\n ? null\n : corsOption === true || corsOption === undefined\n ? cors()\n : cors(corsOption);\n if (corsMiddleware) configMiddlewares.push(corsMiddleware);\n\n // Helmet — enabled only when explicitly configured\n if (helmetOption) {\n const helmOpts = typeof helmetOption === 'object' ? helmetOption : {};\n configMiddlewares.push(helmet(helmOpts));\n }\n\n // Logger — 默认启用(与 cors 一致),false 禁用,LoggerOptions 自定义\n const loggerMiddlewareInst: FaapiMiddleware | null =\n loggerOption === false\n ? null\n : loggerOption === true || loggerOption === undefined\n ? loggerMiddleware()\n : loggerMiddleware(loggerOption);\n if (loggerMiddlewareInst) configMiddlewares.push(loggerMiddlewareInst);\n\n // ETag — 显式启用;位于 compression 内层:先算 ETag/304 再压缩,\n // 弱 ETag 基于未压缩表示计算(304 无 body 时压缩自动跳过)\n if (etagOption) {\n const etagOpts = typeof etagOption === 'object' ? etagOption : {};\n configMiddlewares.push(etag(etagOpts));\n }\n\n // 外层中间件链启动期组装一次(CORS → helmet → logger → 全局),\n // 每请求不再重复 spread 重组数组\n const outerMiddlewares: FaapiMiddleware[] = [...configMiddlewares];\n if (globalMiddlewares && globalMiddlewares.length > 0) {\n outerMiddlewares.push(...globalMiddlewares);\n }\n\n const server = ((): Server => {\n if (http2Option) {\n const h2Opts = typeof http2Option === 'object' ? http2Option : {};\n return createHttp2SecureServer({\n key: h2Opts.key ? readFileSync(h2Opts.key) : undefined,\n cert: h2Opts.cert ? readFileSync(h2Opts.cert) : undefined,\n allowHTTP1: true,\n }) as unknown as Server;\n }\n return createHttpServer();\n })();\n\n server.on('request', (req: IncomingMessage, res: ServerResponse) => {\n // 每次请求读取最新的路由状态(支持 watch 模式热更新)\n const currentRoutes = routesRef.current;\n\n handleRequest(\n currentRoutes,\n rootDir,\n dist,\n req,\n res,\n outerMiddlewares,\n onError,\n config,\n globalInjectors,\n bodyLimit,\n trustedProxy,\n registries,\n ).catch(() => {\n res.statusCode = 500;\n res.end();\n });\n });\n\n // 挂载 WebSocket 升级处理(无条件挂载:upgrade 处理器内部对无匹配路由返回 404,\n // 空清单成本一次函数调用。若按初始清单条件挂载,dev watch 中新增第一个 WS 路由后\n // upgrade 监听器不会补挂,WS 路由永远 404)\n attachWebSocket({\n server,\n routesRef,\n rootDir,\n config,\n globalMiddlewares,\n trustedProxy,\n registries,\n });\n\n return { server, routesRef };\n}\n\n/**\n * 准备请求上下文:Node IncomingMessage → Web Request + FaapiContext\n *\n * 提取为独立函数,让 handleRequest 主流程聚焦于路由 + 中间件调度,\n * 便于单测与未来扩展(如自定义 context 字段来源)。\n *\n * URL 全请求只解析一次(toWebRequest 内),pathname/searchParams\n * 由 ctx / routePipeline 共享,避免一次请求重复 new URL 3~4 次。\n */\nfunction prepareRequest(\n req: IncomingMessage,\n config: Record<string, unknown> | undefined,\n bodyLimit: number,\n trustedProxy: boolean,\n registries?: AppRegistries,\n requestSignal?: AbortSignal,\n): {\n request: Request;\n url: URL;\n ctx: FaapiContext;\n meta: ResponseMeta;\n method: string;\n urlPath: string;\n} {\n const { request, url } = toWebRequest(req, bodyLimit, requestSignal);\n const method = request.method.toUpperCase();\n const urlPath = url.pathname;\n const ctx = createContextFromUrl(\n request,\n url,\n {},\n config,\n getClientIp(req, trustedProxy),\n registries,\n );\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n return { request, url, ctx, meta, method, urlPath };\n}\n\n/**\n * 路由匹配——命中返回 MatchResult,未命中抛 RouteNotFoundError / MethodNotAllowedError\n *\n * 抛错语义让调用方 try/catch 即可,无需在主流程里分支处理 null。\n */\nfunction resolveRouteOrThrow(routes: RouteManifest, method: string, urlPath: string): RouteMatch {\n const match = matchRoute(routes, method, urlPath);\n if (match) return match;\n\n // 检查是否有其他方法匹配该路径 → 405\n const allowedMethods = findAllowedMethods(routes, urlPath);\n if (allowedMethods.length > 0) {\n throw new MethodNotAllowedError(method, urlPath, allowedMethods);\n }\n\n // 路由未匹配 → 404\n throw new RouteNotFoundError(urlPath);\n}\n\n/**\n * 路由执行管线:作为外层中间件链的 finalHandler\n *\n * 包含:路由匹配 → handler.js 加载 → 参数解析 → zod.js 按需生成 + 校验 →\n * 中间件按需加载 → 注入器合并 → handler 调用。\n *\n * 错误抛出由外层 handleRequest 的 try/catch 接管,经 formatErrorResponse 转换为响应。\n */\n// 路由静态派生路径缓存(绝对路径 / schema 路径对固定 route 恒定,免每请求字符串运算)。\n// WeakMap 按 route 对象弱键——reloadRoutes 整体替换清单后旧对象可 GC,无需手动失效\nconst routePathCache = new WeakMap<\n RouteManifest[number],\n { absFilePath: string; schemaPath: string }\n>();\n\nfunction getRoutePaths(\n route: RouteManifest[number],\n rootDir: string,\n dist: string,\n): { absFilePath: string; schemaPath: string } {\n let cached = routePathCache.get(route);\n if (!cached) {\n cached = {\n absFilePath: path.resolve(rootDir, route.filePath),\n schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir),\n };\n routePathCache.set(route, cached);\n }\n return cached;\n}\n\nfunction createRoutePipeline(opts: {\n routes: RouteManifest;\n method: string;\n urlPath: string;\n url: URL;\n ctx: FaapiContext;\n request: Request;\n rootDir: string;\n dist: string;\n globalInjectors: InjectorMap | undefined;\n}): () => Promise<Response> {\n const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;\n return async () => {\n // 1. 路由匹配(未命中抛 RouteNotFound / MethodNotAllowed)\n const match = resolveRouteOrThrow(routes, method, urlPath);\n ctx.params = match.params;\n const { route } = match;\n const { absFilePath, schemaPath } = getRoutePaths(route, rootDir, dist);\n\n // 2. 加载 handler.js(dev 按需编译 + import,prod 直接 import)\n const routeModule = await loadRouteModule(absFilePath, route.method, rootDir);\n\n // 3. 参数解析(query / body / form / files 等)——复用已解析的 URL\n const input = await resolveInputFromUrl(route.method, request, url);\n\n // 4. schema 校验(运行时按 route.filePath 计算 zod.js 路径 + safeParse)\n const inputType = getInputTypeForMethod(route.method);\n // schemaPath 已在 getRoutePaths 中缓存\n\n // Dev 按需模式:zod.js 不存在或 stale 时触发按需生成\n if (isDevOnDemandEnabled()) {\n const devDist = getDevDist();\n if (devDist) {\n await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist);\n }\n }\n\n const result = await validateInput(schemaPath, route.method, inputType, input);\n if (!result.valid) {\n throw new ValidationError('参数校验失败', result.issues);\n }\n // body 计算与主输入分流:\n // - POST/PUT/PATCH:主输入就是 body,用校验后的值\n // - DELETE:主输入是 query(校验 DELETEQuery),body 单独解析注入——\n // 若把校验后的 query 当 body 传入,handler 声明 body 会静默拿到 query;\n // 同时请求体流不被消费,keep-alive 连接无法复用\n // - GET/HEAD:无 body\n const body =\n inputType === 'query' && hasBody(route.method)\n ? await resolveBodyForQueryMethod(request)\n : hasBody(route.method)\n ? result.data\n : undefined;\n\n // 5. 中间件按需加载(Vite 风格):route.middlewares 为 undefined 时从 middlewarePaths 加载\n // 首次请求加载后缓存到 route 上,后续请求直接复用\n if (route.middlewares === undefined && route.injectors === undefined && route.middlewarePaths) {\n // dev 按需模式:先编译中间件源码(含依赖闭包)再 import\n await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);\n const bundle = await loadMergedMiddlewares(route.middlewarePaths);\n if (bundle) {\n route.middlewares = bundle.middlewares;\n route.injectors = bundle.injectors;\n } else {\n // 标记为已加载(空中间件),避免重复加载\n route.middlewares = [];\n route.injectors = {};\n }\n }\n\n // 6. 注入器合并:全局注入器为基线,目录注入器覆盖同名\n const mergedInjectors = globalInjectors\n ? { ...globalInjectors, ...route.injectors }\n : route.injectors;\n\n // 7. handler 调用(含目录中间件洋葱模型 + 自动响应包装)\n return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);\n };\n}\n\n/**\n * 发送成功响应\n */\nasync function sendSuccessResponse(response: Response, res: ServerResponse): Promise<void> {\n await sendNodeResponse(response, res);\n}\n\n/**\n * 发送错误响应 + 触发 onError 副作用\n *\n * 错误处理兜底链(参考 Fastify 语义):\n * 1. 框架内置 formatErrorResponse 兜底(handler 抛错时)——读 ctx.config.response.fail\n * 自定义包装函数,确保错误格式与 ctx.fail() 主动错误响应一致\n * 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n * 3. 响应发出后 → onError 触发副作用(不修改已发出的响应)\n * 注:业务方如需进一步自定义错误响应,在全局中间件中 try/catch next() 即可\n *\n * `ctx` 可为 undefined——请求准备阶段抛错(如 content-length 超限)时尚未构造 ctx,\n * 此时错误格式用默认 fail 包装,onError 不触发(与原\"裸 500\"路径一致)。\n */\nasync function sendErrorResponse(\n err: unknown,\n meta: ResponseMeta,\n res: ServerResponse,\n onError: ((error: unknown, ctx: FaapiContext) => Promise<void> | void) | undefined,\n ctx: FaapiContext | undefined,\n): Promise<void> {\n await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx?.config), meta), res);\n\n // 响应已发出,触发 onError 副作用(日志/告警/链路追踪),自身抛错被忽略\n if (onError && ctx) {\n try {\n await onError(err, ctx);\n } catch {\n // onError 自身抛错不影响已发出的响应\n }\n }\n}\n\nasync function handleRequest(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n req: IncomingMessage,\n res: ServerResponse,\n outerMiddlewares: FaapiMiddleware[],\n onError: ((error: unknown, ctx: FaapiContext) => Promise<void> | void) | undefined,\n config: Record<string, unknown> | undefined,\n globalInjectors: InjectorMap | undefined,\n bodyLimit: number,\n trustedProxy: boolean,\n registries?: AppRegistries,\n): Promise<void> {\n // meta/ctx 兜底:请求准备阶段抛错(如 content-length 超限的 413)时尚无 ctx\n let meta: ResponseMeta = { headers: {}, setCookies: [] };\n let ctx: FaapiContext | undefined;\n // 客户端断连信号:每请求一个 AbortController,signal 进入 Request(ctx.request.signal)\n // res 'close' 在响应正常完成(keep-alive)时也会触发,用 writableEnded 区分——\n // 响应未写完连接就断开(客户端提前断连)才 abort,正常完成不误触发\n const abortController = new AbortController();\n res.on('close', () => {\n if (!res.writableEnded) abortController.abort();\n });\n try {\n // 1. 准备请求上下文(toWebRequest + createContext)——URL 全请求只解析一次\n const prepared = prepareRequest(\n req,\n config,\n bodyLimit,\n trustedProxy,\n registries,\n abortController.signal,\n );\n ctx = prepared.ctx;\n meta = prepared.meta;\n const { request, url, method, urlPath } = prepared;\n\n // 2. 创建路由执行管线(路由匹配 + 校验 + 中间件加载 + handler 调用)\n const routePipeline = createRoutePipeline({\n routes,\n method,\n urlPath,\n url,\n ctx,\n request,\n rootDir,\n dist,\n globalInjectors,\n });\n\n // 3. 执行外层中间件链(CORS → helmet → logger → 全局 → routePipeline,\n // 数组已在 createServer 启动期组装,此处仅按需 compose)\n const response =\n outerMiddlewares.length > 0\n ? await compose(outerMiddlewares, ctx, routePipeline)\n : await routePipeline();\n // 4. 发送响应\n await sendSuccessResponse(response, res);\n } catch (err: unknown) {\n // 客户端已断开或响应已完成:网络中断/连接销毁不是服务端错误,\n // 不向已销毁的连接写 500(写入无效),也不触发 onError 误报\n if (res.destroyed || res.writableEnded) return;\n await sendErrorResponse(err, meta, res, onError, ctx);\n }\n}\n","import type { RouteManifest, RouteMatch, WsRouteManifest, WsRouteMatch } from './routeTypes';\n\n/**\n * 路由匹配索引——把 O(n) 线性扫描降为静态路由 O(1) 命中\n *\n * 每个请求都要做路由匹配,原实现逐条遍历清单(静态路由字符串比较、动态路由\n * split('/') 比对),大清单下是热路径开销。索引把清单分为两部分:\n * - **静态路由**:`method|urlPath` → route 的 Map,一次查找命中\n * - **动态路由**:保持清单顺序的数组(`sortRoutes` 保证静态恒在动态之前,\n * 且同一 `(method, path)` 至多一条静态路由,静态优先查与原 first-match\n * 语义完全等价)\n *\n * 索引按**清单数组身份**缓存(WeakMap):`reloadRoutes` / `hydrateRoutes`\n * 整体替换清单数组(`routesRef.current = newRoutes`),旧数组被替换后索引\n * 自动失效可被 GC,无需手动清理——无生命周期侵入。\n *\n * 详见 [matchRoute.md](./matchRoute.md)。\n */\n\n/**\n * HTTP 路由匹配索引\n */\ninterface HttpRoutesIndex {\n /** 静态路由:`method|urlPath` → route */\n static: Map<string, RouteManifest[number]>;\n /** 静态路径 → 方法集合(405 findAllowedMethods 反查用,免再扫静态段) */\n methodsByStaticPath: Map<string, Set<string>>;\n /** 动态路由(含 catch-all),保持清单顺序,pattern 已预编译 */\n dynamics: DynamicEntry[];\n}\n\n/** 动态路由条目(pattern segments 预编译,免去每请求对固定 pattern 重复 split) */\ninterface DynamicEntry {\n route: RouteManifest[number];\n /** 预编译的模式段(pattern.split('/').filter(Boolean),对固定 route 恒定) */\n segments: string[];\n}\n\n/** WS 路由匹配索引(无 HTTP 方法维度) */\ninterface WsRoutesIndex {\n /** 静态路由:urlPath → route */\n static: Map<string, WsRouteManifest[number]>;\n /** 动态路由(含 catch-all),保持清单顺序 */\n dynamics: WsRouteManifest;\n}\n\nconst httpIndexCache = new WeakMap<RouteManifest, HttpRoutesIndex>();\nconst wsIndexCache = new WeakMap<WsRouteManifest, WsRoutesIndex>();\n\n/** 构建 HTTP 路由索引(惰性,每个清单数组只构建一次) */\nfunction getHttpIndex(routes: RouteManifest): HttpRoutesIndex {\n let index = httpIndexCache.get(routes);\n if (index) return index;\n\n index = { static: new Map(), methodsByStaticPath: new Map(), dynamics: [] };\n for (const route of routes) {\n if (route.isDynamic) {\n index.dynamics.push({\n route,\n segments: route.urlPath.split('/').filter(Boolean),\n });\n } else {\n index.static.set(`${route.method}|${route.urlPath}`, route);\n let methods = index.methodsByStaticPath.get(route.urlPath);\n if (!methods) {\n methods = new Set();\n index.methodsByStaticPath.set(route.urlPath, methods);\n }\n methods.add(route.method);\n }\n }\n\n httpIndexCache.set(routes, index);\n return index;\n}\n\n/** 构建 WS 路由索引(惰性,每个清单数组只构建一次) */\nfunction getWsIndex(routes: WsRouteManifest): WsRoutesIndex {\n let index = wsIndexCache.get(routes);\n if (index) return index;\n\n index = { static: new Map(), dynamics: [] };\n for (const route of routes) {\n if (route.isDynamic) {\n index.dynamics.push(route);\n } else {\n index.static.set(route.urlPath, route);\n }\n }\n\n wsIndexCache.set(routes, index);\n return index;\n}\n\n/**\n * 根据请求路径和方法匹配路由\n *\n * HEAD 回退:无显式 HEAD 路由时复用 GET handler(Node ServerResponse 对 HEAD\n * 自动丢弃 body)。探活、CDN 健康检查、HTTP 客户端预检常用 HEAD——没有回退时\n * 只定义 GET 的路由对 HEAD 返回 405,监控大面积误报。\n *\n * @param routes 已排序的路由清单\n * @param method HTTP 方法\n * @param path 请求路径\n * @returns 匹配结果,包含路由记录和参数;无匹配返回 null\n */\nexport function matchRoute(routes: RouteManifest, method: string, path: string): RouteMatch | null {\n const index = getHttpIndex(routes);\n const upper = method.toUpperCase();\n\n const hit = matchByMethod(index, upper, path);\n if (hit) return hit;\n\n if (upper === 'HEAD') {\n return matchByMethod(index, 'GET', path);\n }\n\n return null;\n}\n\n/** 按方法匹配(静态 O(1) → 动态线性),matchRoute 内部复用(HEAD 回退 GET) */\nfunction matchByMethod(index: HttpRoutesIndex, method: string, path: string): RouteMatch | null {\n // 静态路由 O(1) 命中(sortRoutes 保证静态优先,等价于原遍历的首个命中)\n const staticHit = index.static.get(`${method}|${path}`);\n if (staticHit) {\n return { route: staticHit, params: {} };\n }\n\n // 动态路由按清单顺序匹配(pattern segments 已预编译)\n for (const entry of index.dynamics) {\n const route = entry.route;\n if (route.method !== method) {\n continue;\n }\n const params = matchSegments(entry.segments, path, route.paramNames, route.isCatchAll);\n if (params !== null) {\n return { route, params };\n }\n }\n\n return null;\n}\n\n/**\n * 匹配 WebSocket 路由(无 HTTP 方法维度)\n *\n * WS 路由只按路径匹配,协议升级时调用。\n * @param wsRoutes WebSocket 路由清单\n * @param path 请求路径\n */\nexport function matchWsRoute(wsRoutes: WsRouteManifest, path: string): WsRouteMatch | null {\n const index = getWsIndex(wsRoutes);\n\n const staticHit = index.static.get(path);\n if (staticHit) {\n return { route: staticHit, params: {} };\n }\n\n for (const route of index.dynamics) {\n const params = matchDynamicPath(route.urlPath, path, route.paramNames, route.isCatchAll);\n if (params !== null) {\n return { route, params };\n }\n }\n\n return null;\n}\n\n/**\n * 查找路径的所有允许方法(用于 405 响应)\n *\n * 静态段直接查索引(O(1)),仅动态段线性扫描——原实现对全部路由做\n * O(n) 遍历 + 静态路径比较,404/405 高频场景(扫描器、探活探测)开销显著。\n */\nexport function findAllowedMethods(routes: RouteManifest, path: string): string[] {\n const index = getHttpIndex(routes);\n const methods = new Set<string>();\n\n // 静态路由:索引直查\n const staticMethods = index.methodsByStaticPath.get(path);\n if (staticMethods) {\n for (const method of staticMethods) {\n methods.add(method);\n }\n }\n\n // 动态路由:线性扫描(segments 已预编译)\n for (const entry of index.dynamics) {\n const params = matchSegments(\n entry.segments,\n path,\n entry.route.paramNames,\n entry.route.isCatchAll,\n );\n if (params !== null) {\n methods.add(entry.route.method);\n }\n }\n\n // RFC 9110:GET 允许时 HEAD 必然允许(matchRoute 有 HEAD→GET 回退),\n // 405 的 Allow 头如实反映,避免客户端误判 HEAD 不可用\n if (methods.has('GET')) {\n methods.add('HEAD');\n }\n\n return Array.from(methods);\n}\n\n/**\n * 动态路径匹配\n * 将路由模式(如 /user/:id)与请求路径(如 /user/123)匹配\n * 支持 catch-all 路由(如 /shop/:...slug 匹配 /shop/clothes/tops)\n * 返回提取的参数对象,不匹配返回 null\n */\nexport function matchDynamicPath(\n pattern: string,\n path: string,\n paramNames: string[],\n isCatchAll?: boolean,\n): Record<string, string> | null {\n return matchSegments(pattern.split('/').filter(Boolean), path, paramNames, isCatchAll);\n}\n\n/**\n * 基于**预编译 segments** 的动态匹配(热路径内部用)\n *\n * 与 matchDynamicPath 语义一致,但模式段由索引构建期一次性 split,\n * 免去每请求对固定 pattern 重复字符串分割\n */\nfunction matchSegments(\n patternSegments: string[],\n path: string,\n paramNames: string[],\n isCatchAll?: boolean,\n): Record<string, string> | null {\n const pathSegments = path.split('/').filter(Boolean);\n\n // catch-all 路由:最后一个模式段为 :...slug\n if (isCatchAll) {\n // catch-all 前面的静态/动态段必须匹配\n // catch-all 段至少匹配一个路径段\n const nonCatchAllCount = patternSegments.length - 1;\n if (pathSegments.length <= nonCatchAllCount) {\n return null;\n }\n\n const params: Record<string, string> = {};\n\n for (let i = 0; i < nonCatchAllCount; i++) {\n // i < nonCatchAllCount ≤ 两数组长度,索引必然存在\n const patternSeg = patternSegments[i]!;\n const pathSeg = pathSegments[i]!;\n\n if (patternSeg.startsWith(':')) {\n const paramName = patternSeg.slice(1);\n params[paramName] = pathSeg;\n } else if (patternSeg !== pathSeg) {\n return null;\n }\n }\n\n // catch-all 段:剩余所有路径段用 / 连接\n const catchAllValue = pathSegments.slice(nonCatchAllCount).join('/');\n // 前置检查 pathSegments.length > nonCatchAllCount 保证 catch-all 段存在\n const catchAllParamName = patternSegments[nonCatchAllCount]!.slice(4); // 去掉 ':...'\n params[catchAllParamName] = catchAllValue;\n\n if (Object.keys(params).length !== paramNames.length) {\n return null;\n }\n\n return params;\n }\n\n // 普通动态路由:段数必须一致\n if (patternSegments.length !== pathSegments.length) {\n return null;\n }\n\n const params: Record<string, string> = {};\n\n for (let i = 0; i < patternSegments.length; i++) {\n // 段数一致性检查后索引必然存在\n const patternSeg = patternSegments[i]!;\n const pathSeg = pathSegments[i]!;\n\n if (patternSeg.startsWith(':')) {\n // 动态段,提取参数值\n const paramName = patternSeg.slice(1);\n params[paramName] = pathSeg;\n } else if (patternSeg !== pathSeg) {\n // 静态段不匹配\n return null;\n }\n }\n\n // 确保所有参数名都被提取到\n if (Object.keys(params).length !== paramNames.length) {\n return null;\n }\n\n return params;\n}\n","/**\n * 校验导出值是否为合法的 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","/**\n * SSE(Server-Sent Events)支持\n *\n * 让 handler 能向客户端推送流式事件,用于 LLM token 流、进度通知等场景。\n *\n * 核心导出:\n * - `encodeSseEvent(event)`:把 SSE 事件对象编码为符合 HTML5 SSE 规范的字符串\n * - `createSseWriter()`:创建一个 SseWriter,封装 ReadableStream + Response,提供 send/close/sendError API\n * - `SseWriter`:writer 类型,ctx.sse() 返回此类型\n *\n * 设计要点:\n * - writer 内部用 ReadableStream + TextEncoder,send 时 enqueue,close 时 close controller\n * - response 预设 text/event-stream、no-cache、keep-alive 头,状态码默认 200\n * - close 后再 send 静默忽略,避免 handler 异步流程中误写已关闭的流\n * - sendError 向流写入 event: error 后关闭,用于流式输出中报错的优雅终止\n *\n * 与 ctx 的集成:\n * - ctx.sse() 调用 createSseWriter(),并把 response 缓存到 ctx 内部字段\n * - invokeHandler 在 handler 返回后检查 ctx 是否持有 SSE response,有则优先使用\n */\n\n/**\n * SSE 事件字段\n *\n * 遵循 HTML5 SSE 规范:\n * - `data`:消息数据,多行时每行加 `data: ` 前缀;对象自动 JSON.stringify\n * - `event`:事件类型,客户端可用 addEventListener(event) 监听\n * - `id`:事件 ID,客户端断线重连时通过 Last-Event-ID 头发送\n * - `retry`:重连等待时间(毫秒)\n * - `comment`:注释行(以 `:` 开头),用于 keep-alive 心跳,不传递给客户端消息\n */\nexport interface SseEvent {\n /** 消息数据。字符串原样输出;对象自动 JSON.stringify;多行时每行加 data: 前缀 */\n data?: unknown;\n /** 事件类型,客户端可用 addEventListener 监听 */\n event?: string;\n /** 事件 ID,客户端断线重连时通过 Last-Event-ID 头发送 */\n id?: string | number;\n /** 重连等待时间(毫秒) */\n retry?: number;\n /** 注释行(以 : 开头),用于 keep-alive 心跳 */\n comment?: string;\n}\n\n/**\n * 把 SSE 事件对象编码为符合 HTML5 SSE 规范的字符串\n *\n * 字段顺序固定:comment > event > id > retry > data,末尾空行分隔事件。\n *\n * @param event SSE 事件对象\n * @returns 编码后的字符串(含末尾空行)\n */\nexport function encodeSseEvent(event: SseEvent): string {\n let out = '';\n\n // comment(注释行,以 : 开头)\n if (event.comment !== undefined) {\n out += `: ${event.comment}\\n`;\n }\n\n // event\n if (event.event !== undefined) {\n out += `event: ${event.event}\\n`;\n }\n\n // id\n if (event.id !== undefined) {\n out += `id: ${event.id}\\n`;\n }\n\n // retry\n if (event.retry !== undefined) {\n out += `retry: ${event.retry}\\n`;\n }\n\n // data:对象 JSON.stringify,多行每行加前缀\n if (event.data !== undefined) {\n let dataStr: string;\n if (typeof event.data === 'string') {\n dataStr = event.data;\n } else if (event.data === null) {\n dataStr = 'null';\n } else {\n dataStr = JSON.stringify(event.data);\n }\n // 多行 data:每行加 data: 前缀\n const lines = dataStr.split('\\n');\n for (const line of lines) {\n out += `data: ${line}\\n`;\n }\n }\n\n // 空行结束事件\n out += '\\n';\n return out;\n}\n\n/**\n * SSE writer:封装流式推送 API\n *\n * 通过 `ctx.sse()` 创建,handler 调用 `send` 推送事件,`close` 关闭流。\n * 框架在 handler 返回后,自动使用 writer.response 作为 HTTP 响应。\n */\nexport interface SseWriter {\n /** 推送一个 SSE 事件 */\n send(event: SseEvent): void;\n /**\n * 直接写入原始字节/字符串,不做任何 SSE 序列化\n *\n * 用于透传上游已有的 SSE 原文(如 LLM 中转平台逐 chunk 转发 OpenAI 响应)。\n * 调用方负责保证内容符合 HTML5 SSE 规范;`send` 会再次加 `data: ` 前缀,\n * 不适用于原文透传场景。\n *\n * 接受 string 或 Uint8Array(Buffer 是 Uint8Array 子类,自然兼容)。\n * 与 `send` 一致:close/aborted 后静默忽略,不抛错。\n */\n sendRaw(chunk: string | Uint8Array): void;\n /** 推送一个 error 事件并关闭流(用于流式输出中报错的优雅终止) */\n sendError(error: unknown): void;\n /** 关闭流(多次调用安全) */\n close(): void;\n /** 流是否已关闭(handler 主动 close 或框架自动 close) */\n readonly closed: boolean;\n /** 客户端是否已断开(ReadableStream 被 cancel) */\n readonly aborted: boolean;\n /** 对应的 HTTP Response(由框架使用,用户一般不需要直接访问) */\n readonly response: Response;\n}\n\n/**\n * 创建一个 SSE writer\n *\n * 内部用 ReadableStream + TextEncoder 实现,send 时把编码后的事件 enqueue 到流,\n * close 时关闭 controller。response 预设标准 SSE 头。\n *\n * aborted 检测:监听 ReadableStream 的 cancel 钩子,客户端断开(cancel)时置为 true。\n * 此时 send 静默忽略,handler 可通过 writer.aborted 退出循环。\n */\nexport function createSseWriter(): SseWriter {\n const encoder = new TextEncoder();\n let controller: ReadableStreamDefaultController<Uint8Array> | null = null;\n let closed = false;\n let aborted = false;\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c;\n },\n cancel() {\n // 客户端断开连接(cancel ReadableStream)\n aborted = true;\n closed = true;\n controller = null;\n },\n });\n\n const response = new Response(stream, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n\n const writer: SseWriter = {\n send(event: SseEvent): void {\n if (closed || !controller) return;\n const text = encodeSseEvent(event);\n controller.enqueue(encoder.encode(text));\n },\n\n sendRaw(chunk: string | Uint8Array): void {\n if (closed || !controller) return;\n const bytes = typeof chunk === 'string' ? encoder.encode(chunk) : chunk;\n controller.enqueue(bytes);\n },\n\n sendError(error: unknown): void {\n if (closed || !controller) return;\n const message = error instanceof Error ? error.message : String(error);\n const text = encodeSseEvent({ event: 'error', data: message });\n try {\n controller.enqueue(encoder.encode(text));\n } finally {\n writer.close();\n }\n },\n\n close(): void {\n if (closed) return;\n closed = true;\n if (controller) {\n try {\n controller.close();\n } catch {\n // controller 可能已关闭,忽略\n }\n controller = null;\n }\n },\n\n get closed(): boolean {\n return closed;\n },\n\n get aborted(): boolean {\n return aborted;\n },\n\n get response(): Response {\n return response;\n },\n };\n\n return writer;\n}\n","import type { ResponseConfig } from '../config/configTypes';\nimport { FaapiError } from '../errors/FaapiError';\nimport { ValidationError, MethodNotAllowedError, PayloadTooLargeError } from '../errors/httpErrors';\n\n/**\n * 统一响应格式化中心\n *\n * 集中所有响应包装规则,确保 handler return 值的自动包裹、ctx.fail() 主动错误响应、\n * formatErrorResponse 抛错兜底 三条路径共享同一套 ok/fail 函数,避免响应格式漂移。\n *\n * 路径分工:\n * - 成功路径: handler `return data` → wrapOkResult → toResponse 多类型序列化\n * - 显式成功: handler `return ctx.ok(data)` → wrapOkResult → jsonOk 构造 Response\n * - 主动错误: handler `return ctx.fail({...})` → formatFailResponse → jsonOk\n * - 抛错兜底: handler throw → formatErrorResponse(err, config) → jsonOk\n *\n * 业务方通过 faapi.config.ts 的 `response.ok` / `response.fail` 自定义包装函数,\n * 三条路径自动一致生效。\n */\n\n/** 默认 ok 包装:(data) => ({ data }) */\nexport function defaultOk(data: unknown): Record<string, unknown> {\n return { data };\n}\n\n/**\n * 默认 fail 包装:省略的字段不放入 error 对象\n *\n * `code` 为 undefined 时不放入响应,保持 body 紧凑。\n */\nexport function defaultFail(e: {\n status?: number;\n code?: string;\n message: string;\n}): Record<string, unknown> {\n const error: Record<string, unknown> = { message: e.message };\n if (e.code !== undefined) error.code = e.code;\n return { error };\n}\n\n/** 从 ctx.config 读取 ResponseConfig(业务方在 faapi.config.ts 中配置的 response 字段) */\nexport function getResponseConfig(\n config: Record<string, unknown> | undefined,\n): ResponseConfig | undefined {\n return (config as { response?: ResponseConfig } | undefined)?.response;\n}\n\n/** 解析 ok 函数:业务方自定义 ?? 框架默认 */\nexport function resolveOkFn(config: Record<string, unknown> | undefined) {\n return getResponseConfig(config)?.ok ?? defaultOk;\n}\n\n/** 解析 fail 函数:业务方自定义 ?? 框架默认 */\nexport function resolveFailFn(config: Record<string, unknown> | undefined) {\n return getResponseConfig(config)?.fail ?? defaultFail;\n}\n\n/**\n * 构造 JSON 成功响应(不包裹,原样序列化)\n *\n * 用于 ctx.json / ctx.ok 等显式响应场景。\n */\nexport function jsonOk(body: unknown, status = 200, extraHeaders?: HeadersInit): Response {\n return jsonRaw(body, status, extraHeaders);\n}\n\n/** 内部:构造 JSON Response,不包外层 */\nfunction jsonRaw(body: unknown, status: number, extraHeaders?: HeadersInit): Response {\n const headers = new Headers({ 'Content-Type': 'application/json' });\n if (extraHeaders) {\n const extra = new Headers(extraHeaders);\n extra.forEach((value, key) => headers.set(key, value));\n }\n return new Response(JSON.stringify(body), { status, headers });\n}\n\n/**\n * 成功响应自动包装:handler return 值 → okFn(data) 包裹\n *\n * 规则:\n * - Response 对象:不包裹(ctx.ok/ctx.fail/ctx.json 等返回的 Response 原样透传)\n * - 其他值(含 null/undefined):用 config.response.ok(或默认 (data) => ({ data })) 包裹\n *\n * 注:null/undefined 也会被包裹为 { data: null } / { data: undefined }。\n *\n * 不构造 Response,只返回包装后的值——最终序列化交给 toResponse 处理(支持多类型分发 +\n * 合并 ctx.meta 的 setStatus/setHeader/setCookie)。\n */\nexport function wrapOkResult(\n result: unknown,\n config: Record<string, unknown> | undefined,\n): unknown {\n if (result instanceof Response) return result;\n return resolveOkFn(config)(result);\n}\n\n/**\n * 业务方主动错误响应:FailOptions → Response\n *\n * handler `return ctx.fail({ status, code, message })` 的实现。\n * 用 config.response.fail(或默认实现)包装为 `{ error: { ... } }` 结构。\n *\n * `status` 省略时默认 500;`code` 省略时由 fail 函数决定是否放入 body。\n */\nexport function formatFailResponse(\n options: { status?: number; code?: string; message: string },\n config: Record<string, unknown> | undefined,\n): Response {\n const failFn = resolveFailFn(config);\n const body = failFn({\n status: options.status,\n code: options.code,\n message: options.message,\n });\n return jsonOk(body, options.status ?? 500);\n}\n\n/**\n * 抛错兜底响应:把 handler 抛出的错误转换为统一格式的 Response\n *\n * 覆盖的 FaapiError 子类:\n * - ValidationError → 400/422 + issues\n * - MethodNotAllowedError → 405 + Allow header\n * - PayloadTooLargeError → 413\n * - 其他 FaapiError 子类 → 用 error.statusCode\n * - 未知 Error / 非 Error → 500 INTERNAL_ERROR\n *\n * 与 [formatFailResponse] 共享同一套 fail 函数:业务方在 faapi.config.ts 自定义的\n * `response.fail` 会同时应用到「主动错误响应」和「抛错兜底响应」,确保错误格式一致。\n *\n * @param error handler 抛出的错误\n * @param config 业务方配置(用于读取 response.fail 自定义包装函数,可选)\n */\nexport function formatErrorResponse(\n error: unknown,\n config?: Record<string, unknown> | undefined,\n): Response {\n const failFn = resolveFailFn(config);\n\n if (error instanceof ValidationError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n // ValidationError 需要附加 issues 字段。业务方 failFn 可能返回共享/复用对象\n // (性能优化写法)或冻结对象——就地改写会让 issues 跨请求残留污染或静默失败,\n // 因此浅拷贝后再扩展,不改写业务方返回的原对象\n const bodyObj =\n typeof body === 'object' && body !== null\n ? ({ ...(body as Record<string, unknown>) } as Record<string, unknown>)\n : { error: body };\n const existingError = bodyObj.error as Record<string, unknown> | undefined;\n if (existingError && typeof existingError === 'object') {\n bodyObj.error = { ...existingError, issues: error.issues };\n } else if (typeof body === 'object' && body !== null) {\n // 无 error 包装层:body 本身就是错误对象\n bodyObj.issues = error.issues;\n } else {\n bodyObj.error = { issues: error.issues };\n }\n return jsonOk(bodyObj, error.statusCode);\n }\n\n if (error instanceof MethodNotAllowedError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode, { Allow: error.allowedMethods.join(', ') });\n }\n\n if (error instanceof PayloadTooLargeError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode);\n }\n\n if (error instanceof FaapiError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode);\n }\n\n // 未知错误:500 INTERNAL_ERROR\n const message = error instanceof Error ? error.message : 'An unknown error occurred';\n const body = failFn({ status: 500, code: 'INTERNAL_ERROR', message });\n return jsonOk(body, 500);\n}\n","import type { FaapiContext, ResponseMeta, CookieOptions, FailOptions } from './contextTypes';\nimport { createSseWriter, type SseWriter } from './sse';\nimport { wrapOkResult, formatFailResponse, jsonOk } from '../response/responseFormatter';\n\n/**\n * 解析 Cookie 请求头为 Map\n */\nfunction parseCookies(cookieHeader: string): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n for (const pair of cookieHeader.split(';')) {\n const [name, ...rest] = pair.split('=');\n const trimmed = name?.trim();\n if (trimmed) {\n cookies.set(trimmed, rest.join('=').trim());\n }\n }\n return cookies;\n}\n\n/**\n * 格式化 Set-Cookie 值\n */\nfunction formatSetCookie(name: string, value: string, options?: CookieOptions): string {\n let cookie = `${name}=${value}`;\n if (options?.domain) cookie += `; Domain=${options.domain}`;\n if (options?.path) cookie += `; Path=${options.path}`;\n if (options?.maxAge !== undefined) cookie += `; Max-Age=${options.maxAge}`;\n if (options?.expires) cookie += `; Expires=${options.expires.toUTCString()}`;\n if (options?.httpOnly) cookie += `; HttpOnly`;\n if (options?.secure) cookie += `; Secure`;\n if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;\n return cookie;\n}\n\n/**\n * 从 Request 对象创建 FaapiContext\n * @param request Web Request 对象\n * @param params 动态路由参数\n * @param config 自定义业务配置(来自 faapi.config.ts)\n * @param ip 客户端 IP(由调用方从 IncomingMessage 提取,HTTP/WS 握手均通过 utils/getClientIp)\n *\n * ua 不作为参数传入:User-Agent 是标准 HTTP 请求头,createContext 内部直接从\n * request.headers 读取(与 ip 不同,ip 需要从 IncomingMessage 提取故由调用方传入)。\n */\nexport function createContext(\n request: Request,\n params: Record<string, string>,\n config: Record<string, unknown> = {},\n ip: string = '',\n registries?: FaapiContext['registries'],\n): FaapiContext {\n return createContextFromUrl(request, new URL(request.url), params, config, ip, registries);\n}\n\n/**\n * createContext 的热路径变体:URL 由调用方解析一次后传入\n *\n * createServer 每请求需要 pathname/searchParams,若各自 `new URL(request.url)`\n * 一次请求要重复解析 3~4 次。此变体接收已解析的 URL 对象,searchParams 与\n * 调用方共享(只读)。普通场景用 {@link createContext}。\n */\nexport function createContextFromUrl(\n request: Request,\n url: URL,\n params: Record<string, string>,\n config: Record<string, unknown> = {},\n ip: string = '',\n registries?: FaapiContext['registries'],\n): FaapiContext {\n const meta: ResponseMeta = { headers: {}, setCookies: [] };\n const parsedCookies = parseCookies(request.headers.get('cookie') ?? '');\n const cookiesObj: Record<string, string> = {};\n for (const [key, val] of parsedCookies) {\n cookiesObj[key] = val;\n }\n\n const ctx = {\n request,\n params,\n query: url.searchParams,\n headers: request.headers,\n method: request.method,\n path: url.pathname,\n ip,\n ua: request.headers.get('user-agent') ?? '',\n cookies: cookiesObj,\n config,\n meta,\n\n setStatus(status: number) {\n meta.status = status;\n },\n\n setHeader(key: string, value: string) {\n meta.headers[key] = value;\n },\n\n setETag(value: string) {\n meta.headers['etag'] = value;\n },\n\n redirect(url: string, status = 302): Response {\n return new Response(null, {\n status,\n headers: { Location: url },\n });\n },\n\n json(data: unknown, status?: number): Response {\n return jsonOk(data, status ?? 200);\n },\n\n html(html: string, status?: number): Response {\n const headers: Record<string, string> = { 'Content-Type': 'text/html; charset=utf-8' };\n return new Response(html, {\n status: status ?? 200,\n headers,\n });\n },\n\n getCookie(name: string): string | undefined {\n return parsedCookies.get(name);\n },\n\n setCookie(name: string, value: string, options?: CookieOptions): void {\n meta.setCookies.push(formatSetCookie(name, value, options));\n },\n\n deleteCookie(name: string): void {\n meta.setCookies.push(formatSetCookie(name, '', { maxAge: 0 }));\n },\n\n /**\n * 创建 SSE writer,用于流式推送事件\n *\n * handler 调用此方法后,通过返回的 writer 推送事件,框架自动把 writer.response\n * 作为 HTTP 响应(Content-Type: text/event-stream)。\n *\n * 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。\n */\n sse(): SseWriter {\n const writer = createSseWriter();\n const ctxWithSse = ctx as FaapiContext & {\n __sseResponse?: Response;\n __sseWriter?: SseWriter;\n };\n ctxWithSse.__sseResponse = writer.response;\n ctxWithSse.__sseWriter = writer;\n return writer;\n },\n\n /**\n * 显式包装成功响应(返回 Response,不会被自动包裹再次包装)\n *\n * 实现委托给 [responseFormatter.wrapOkResult](../response/responseFormatter.ts),\n * 与 handler `return data` 走的自动包裹路径共享同一套 ok 函数。\n *\n * 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。\n * handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。\n */\n ok(data: unknown): Response {\n const body = wrapOkResult(data, config);\n // wrapOkResult 对 Response 对象直接透传(不会包裹),ctx.ok 接收到的是 okFn(data) 的对象\n // 业务方不应在 ctx.ok 里传 Response,这里 fallback 调 jsonOk 序列化\n return jsonOk(body, 200);\n },\n\n /**\n * 返回错误响应(对象形式参数,status 和 code 均可省略)\n *\n * 实现委托给 [responseFormatter.formatFailResponse](../response/responseFormatter.ts),\n * 与 formatErrorResponse(handler 抛错兜底)共享同一套 fail 函数,确保错误格式一致。\n *\n * - status 省略时 HTTP 状态码默认 500\n * - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)\n * - status 和 code 独立无关联\n *\n * body 用 config.response.fail(或默认实现)包装。\n */\n fail(options: FailOptions): Response {\n return formatFailResponse(options, config);\n },\n } as FaapiContext & { meta: ResponseMeta; __sseResponse?: Response; __sseWriter?: SseWriter };\n\n // app 级注册表(方案 A 实例化)——框架请求链路的 tool/agent/agentHandle 读取来源\n if (registries) {\n ctx.registries = registries;\n }\n\n // 执行用户自定义的 ctx 扩展钩子(config.extendContext)\n const extend = config?.extendContext;\n if (typeof extend === 'function') {\n extend(ctx);\n }\n\n return ctx;\n}\n\n/**\n * 测试专用:从选项对象创建 FaapiContext,免去手写 `new Request(url)` 的样板代码\n *\n * 与 createContext 的关系:createTestContext 内部构造 Request 后调 createContext,\n * 语义完全一致,仅是测试场景的语法糖——不写无意义的 host、query 用对象形式、headers 直接传对象。\n *\n * 为什么不合并进 createContext:createContext 运行时也从真实 HTTP 请求构造 Request,\n * 保持 `(request: Request)` 签名使运行时与测试同构;createTestContext 是纯测试便捷封装,\n * 不引入运行时分支。\n *\n * body 不在此处理:createContext 本身不读 `request.body`,body 注入由 `invokeHandler`\n * 的第 3 个参数负责。POST/PUT/PATCH 测试时 body 单独传给 invokeHandler,避免在两处传 body 产生混淆。\n *\n * @param options 请求选项(path 必填,其余可选)\n * @returns FaapiContext\n */\nexport function createTestContext(options: CreateTestContextOptions): FaapiContext {\n const {\n method = 'GET',\n path,\n query,\n headers,\n params = {},\n config = {},\n ip = '',\n registries,\n } = options;\n\n // 用 URL 解析 + 拼 query,避免手动拼接字符串的转义问题\n const url = new URL(`http://localhost${path}`);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (Array.isArray(value)) {\n for (const item of value) {\n url.searchParams.append(key, String(item));\n }\n } else {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const request = new Request(url.toString(), {\n method,\n headers: headers as HeadersInit | undefined,\n });\n\n return createContext(request, params, config, ip, registries);\n}\n\n/**\n * createTestContext 的选项\n */\nexport interface CreateTestContextOptions {\n /** app 级注册表(测试 handler 声明 agent/agents 参数时注入用,可选) */\n registries?: FaapiContext['registries'];\n /** 请求方法,默认 'GET' */\n method?: string;\n /** 请求路径,必填,如 '/api/user'(无需写 host) */\n path: string;\n /** 查询参数,对象形式,自动拼接到 URL(值会被 String() 转换;数组生成同名多值参数) */\n query?: Record<string, string | number | boolean | Array<string | number | boolean>>;\n /** 请求头 */\n headers?: Record<string, string>;\n /** 动态路由参数,默认 {} */\n params?: Record<string, string>;\n /** 业务配置(来自 faapi.config.ts),默认 {} */\n config?: Record<string, unknown>;\n /** 客户端 IP,默认 '' */\n ip?: string;\n}\n","/**\n * URLSearchParams → query 对象\n *\n * 重复 key 聚合为数组(对齐 Express qs / Hono getAll 语义):\n * - `?page=1` → `{ page: '1' }`\n * - `?tag=a&tag=b` → `{ tag: ['a', 'b'] }`\n *\n * 此前 last-wins 静默丢弃前面的值(`?ids=1&ids=2` 得 `'2'`),且 query schema\n * 的 coerce 管线对 array 元素同样生成 preprocess——声明 `ids: number[]` 的字段\n * 此前永远校验失败(解析端从未产出数组),属于校验管线输入端漏洞。\n */\nexport function queryToObject(params: URLSearchParams): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of params) {\n const existing = result[key];\n if (existing === undefined) {\n result[key] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n result[key] = [existing, value];\n }\n }\n return result;\n}\n","export type JsonParseResult = { success: true; data: unknown } | { success: false; error: string };\n\nexport function parseJsonBody(text: string): JsonParseResult {\n try {\n const data = JSON.parse(text);\n return { success: true, data };\n } catch {\n return { success: false, error: 'Invalid JSON body' };\n }\n}\n","/**\n * 解析 multipart/form-data 请求\n * 使用 Web 标准 Request.formData() API\n */\n\nexport interface UploadedFile {\n name: string; // 字段名\n filename: string; // 原始文件名\n type: string; // MIME 类型\n size: number; // 文件大小(字节)\n arrayBuffer: () => Promise<ArrayBuffer>; // 获取文件内容\n}\n\nexport interface MultipartResult {\n /** 表单字段(同名字段用数组收集,单值保持 string) */\n fields: Record<string, string | string[]>;\n files: UploadedFile[];\n}\n\n/**\n * 解析 multipart/form-data\n *\n * 同名字段的处理:\n * - 第一次出现:string\n * - 第二次出现:string[](包含所有同名字段的值)\n * - 文件始终收集到 files 数组(通过 name 字段标识归属)\n */\nexport async function parseMultipart(request: Request): Promise<MultipartResult> {\n const formData = await request.formData();\n const fields: Record<string, string | string[]> = {};\n const files: UploadedFile[] = [];\n\n for (const [key, value] of formData.entries()) {\n if (value instanceof File) {\n files.push({\n name: key,\n filename: value.name,\n type: value.type,\n size: value.size,\n arrayBuffer: () => value.arrayBuffer(),\n });\n } else {\n // 同名字段收集为数组\n if (key in fields) {\n const existing = fields[key];\n if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n // in 检查已证明存在\n fields[key] = [existing!, value];\n }\n } else {\n fields[key] = value;\n }\n }\n }\n\n return { fields, files };\n}\n","import { queryToObject } from '../utils/queryToObject';\nimport { parseJsonBody } from '../utils/parseJsonBody';\nimport { parseMultipart } from '../utils/parseMultipart';\nimport { getInputTypeForMethod } from './inputType';\nimport { ValidationError } from '../errors/httpErrors';\n\n/**\n * 根据 HTTP 方法解析主输入\n *\n * - GET / DELETE / HEAD:主输入是 query(URL 查询参数)\n * - POST / PUT / PATCH:主输入是 body(请求体)\n *\n * 注意:DELETE 也可能有 body,但主输入(用于校验)是 query。\n * body 由 createServer 调 resolveBodyForQueryMethod 单独解析后注入\n * (同时消费请求体流,保证 keep-alive 连接可复用)。\n *\n * HTTP 传输语义:\n * - 空请求体(或纯空白)视为无 body,返回 null(handler 可不声明 body 参数)\n * - 请求体非空但 JSON 格式非法,抛 ValidationError(code=INVALID_FORMAT),\n * 不再静默返回 null 导致后续报\"字段缺失\"\n *\n * @param method HTTP 方法\n * @param request Request 对象\n * @returns 解析后的输入值(query 对象或 body 对象)\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveInput(method: string, request: Request): Promise<unknown> {\n return resolveInputFromUrl(method, request, new URL(request.url));\n}\n\n/**\n * resolveInput 的热路径变体:URL 由调用方解析一次后传入\n *\n * createServer 每请求已持有解析好的 URL(pathname/searchParams 复用),\n * 通过此变体避免 query 分支的重复 `new URL(request.url)`。\n */\nexport async function resolveInputFromUrl(\n method: string,\n request: Request,\n url: URL,\n): Promise<unknown> {\n const inputType = getInputTypeForMethod(method);\n\n // 主输入是 body 的方法:解析请求体\n if (inputType === 'body') {\n const contentType = request.headers.get('content-type') ?? '';\n\n // multipart/form-data:字段 + 文件\n if (contentType.includes('multipart/form-data')) {\n return parseMultipart(request);\n }\n\n // application/x-www-form-urlencoded:表单字段\n if (contentType.includes('application/x-www-form-urlencoded')) {\n const text = await request.text();\n if (isBlankText(text)) return null;\n const params = new URLSearchParams(text);\n const obj: Record<string, string> = {};\n for (const [key, value] of params) {\n obj[key] = value;\n }\n return obj;\n }\n\n // 默认按 JSON 解析(application/json 及其它未明确类型)\n const text = await request.text();\n\n // 空请求体:视为无 body(handler 可不声明 body 参数)\n if (isBlankText(text)) {\n return null;\n }\n\n // 非空请求体:必须能解析为 JSON,否则是格式错误\n const result = parseJsonBody(text);\n if (!result.success) {\n throw new ValidationError('请求体不是合法的 JSON', [\n {\n path: 'body',\n code: 'INVALID_FORMAT',\n expected: 'JSON',\n received: 'text',\n message: '请求体不是合法的 JSON',\n },\n ]);\n }\n return result.data;\n }\n\n // 主输入是 query 的方法(GET / DELETE / HEAD):从 URL 提取 query\n return queryToObject(url.searchParams);\n}\n\n/** 空白文本判断(length 短路 + 正则扫描,避免 trim 的全量字符串拷贝) */\nfunction isBlankText(text: string): boolean {\n return text.length === 0 || !/\\S/.test(text);\n}\n\n/**\n * 解析\"主输入是 query 但允许携带 body\"的方法(DELETE)的请求体\n *\n * DELETE 的主输入(用于校验)是 query,但请求体流必须被消费(keep-alive 连接\n * 上有未读 body 时 Node 只能断开连接,无法复用),且 handler 声明 `body` 参数\n * 时应注入真正的请求体而非 query。\n *\n * 与主输入 body 的方法不同:DELETE body 无对应 schema(schema 提取只产出\n * DELETEQuery),不做校验,原样注入。空请求体返回 undefined(handler 未声明\n * body 参数时注入 undefined,行为一致)。\n *\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveBodyForQueryMethod(request: Request): Promise<unknown> {\n const text = await request.text();\n if (isBlankText(text)) return undefined;\n const result = parseJsonBody(text);\n if (!result.success) {\n throw new ValidationError('请求体不是合法的 JSON', [\n {\n path: 'body',\n code: 'INVALID_FORMAT',\n expected: 'JSON',\n received: 'text',\n message: '请求体不是合法的 JSON',\n },\n ]);\n }\n return result.data;\n}\n","export function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') {\n return false;\n }\n\n // 数组不是普通对象\n if (Array.isArray(value)) {\n return false;\n }\n\n // 检查原型链:普通对象的 prototype 是 Object.prototype 或 null(Object.create(null))\n const proto = Object.getPrototypeOf(value);\n return proto === null || proto === Object.prototype;\n}\n","import type { ResponseMeta } from '../runtime/contextTypes';\n\n/**\n * 仅 headers 型 meta 的延迟落头通道\n *\n * Web Response 一旦构造不可变——此前 `ctx.ok()` / `ctx.fail()` 等返回 Response\n * 的场景,即使 meta 只有 headers(helmet 开启后每请求 ~13 个静态头),也要\n * `new Headers()` 拷贝 + `new Response()` 重建一次。改为把 headers 记入\n * WeakMap(按响应对象弱键,响应被 GC 自动清理),由 Node 发送层\n * (sendNodeResponse)最后一次性 `setHeader`——热路径零重建。\n *\n * 中间件若重建 Response(如 compression / etag 的大 body 分支),需用\n * `consumePendingMetaHeaders` 把延迟头并入新 Response 的 headers,避免丢失。\n */\n\nconst pending = new WeakMap<Response, Record<string, string>>();\n\n/**\n * 记录延迟落头(headers-only meta 场景)\n *\n * 同一响应多次记录按 key 合并(后写覆盖,与 mergeMeta 的 headers.set 语义一致)。\n */\nexport function deferMetaHeaders(response: Response, headers: Record<string, string>): void {\n const existing = pending.get(response);\n if (existing) {\n Object.assign(existing, headers);\n return;\n }\n pending.set(response, { ...headers });\n}\n\n/**\n * 取出并清除响应的延迟头(发送层 / 重建方消费)\n */\nexport function consumePendingMetaHeaders(response: Response): Record<string, string> | undefined {\n const headers = pending.get(response);\n if (headers) pending.delete(response);\n return headers;\n}\n\n/** meta 是否仅含 headers(无 status / setCookies,可走延迟通道) */\nexport function isHeadersOnlyMeta(meta: ResponseMeta): boolean {\n return meta.status === undefined && meta.setCookies.length === 0;\n}\n","import { isPlainObject } from '../utils/isPlainObject';\nimport type { ResponseMeta } from '../runtime/contextTypes';\nimport { deferMetaHeaders, isHeadersOnlyMeta } from './pendingMeta';\n\n/**\n * 将 handler 返回值统一转换为 Response\n *\n * 规则:\n * - Response 对象:原样返回\n * - ReadableStream:直接作为 body\n * - Buffer / Uint8Array:作为二进制 body\n * - 普通对象/数组:JSON.stringify,Content-Type: application/json\n * - string:text/plain\n * - number/boolean:text/plain,String(value)\n * - null/undefined:204 No Content\n * - Promise:await 后再处理\n *\n * 如果传入了 meta,会将 ctx.setStatus / ctx.setHeader 的设置合并到 Response 中\n */\nexport async function toResponse(value: unknown, meta?: ResponseMeta): Promise<Response> {\n // Promise:await 后再处理\n if (value instanceof Promise) {\n return toResponse(await value, meta);\n }\n\n // 辅助函数:将 meta headers 和 setCookies 合并到 Headers 对象\n const applyMeta = (headers: Headers): void => {\n if (!meta) return;\n for (const [key, val] of Object.entries(meta.headers)) {\n headers.set(key, val);\n }\n for (const cookie of meta.setCookies ?? []) {\n headers.append('set-cookie', cookie);\n }\n };\n\n // Response 对象:原样返回(但合并 meta headers)\n if (value instanceof Response) {\n if (\n meta &&\n (meta.status !== undefined ||\n Object.keys(meta.headers).length > 0 ||\n meta.setCookies.length > 0)\n ) {\n // 仅 headers 时延迟到发送层落头(避免每请求 Headers 拷贝 + Response 重建,\n // helmet 开启后 meta.headers 恒非空)。status/cookie 需要 Response 级变更,仍重建\n if (isHeadersOnlyMeta(meta)) {\n deferMetaHeaders(value, meta.headers);\n return value;\n }\n const headers = new Headers(value.headers);\n applyMeta(headers);\n return new Response(value.body, {\n status: meta.status ?? value.status,\n headers,\n });\n }\n return value;\n }\n\n // null/undefined:204 No Content\n if (value === null || value === undefined) {\n const status = meta?.status ?? 204;\n const headers = new Headers();\n applyMeta(headers);\n return new Response(null, { status, headers });\n }\n\n // ReadableStream:直接作为 body\n if (typeof ReadableStream !== 'undefined' && value instanceof ReadableStream) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // Buffer / Uint8Array:作为二进制 body\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n if (value instanceof Uint8Array) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // 普通对象/数组:JSON.stringify,Content-Type: application/json\n if (isPlainObject(value) || Array.isArray(value)) {\n const body = JSON.stringify(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return new Response(body, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // string:text/plain\n if (typeof value === 'string') {\n const headers = new Headers({ 'Content-Type': 'text/plain' });\n applyMeta(headers);\n return new Response(value, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // number/boolean:text/plain,String(value)\n if (typeof value === 'number' || typeof value === 'boolean') {\n const headers = new Headers({ 'Content-Type': 'text/plain' });\n applyMeta(headers);\n return new Response(String(value), {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // 其他类型 fallback:JSON.stringify\n const body = JSON.stringify(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return new Response(body, {\n status: meta?.status ?? 200,\n headers,\n });\n}\n","import type { FaapiContext } from '../runtime/contextTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { resolveInjection, type InjectionType } from './resolveInjection';\nimport { queryToObject } from '../utils/queryToObject';\nimport type { MultipartResult } from '../utils/parseMultipart';\nimport { listAgents } from './agentRegistry';\nimport { getAgentHandle } from './agentHandle';\n\n/**\n * 根据注入类型获取对应的值(内置)\n *\n * Phase 2.3 扩展 `agent` / `agents`:\n * - `agents` → `listAgents()`(所有已注册 agent 元数据列表)\n * - `agent` → `getAgentHandle(ctx)`(Phase 3.5:调 `@faapi/agent` 插件注册的工厂)\n *\n * Phase 3.5 的 `@faapi/agent` 插件通过 [agentHandle](./agentHandle.md) 工厂注册机制\n * 提供 `AgentHandle`(含可调用 `run` / `stream`)。未注册工厂时返回 `undefined`。\n */\nfunction getBuiltinInjectionValue(type: InjectionType, ctx: FaapiContext, body?: unknown): unknown {\n switch (type) {\n case 'query':\n return queryToObject(ctx.query);\n case 'params':\n return ctx.params;\n case 'headers':\n return ctx.headers;\n case 'context':\n return ctx;\n case 'cookies':\n return ctx.cookies;\n case 'ip':\n return ctx.ip;\n case 'ua':\n return ctx.ua;\n case 'body':\n return body;\n // form 与 body 共享解析结果(resolveInput 已按 Content-Type 解析 form-urlencoded)\n // 差异仅在 schema 校验(form coerce=true,由 collectRouteSchemaSources 标记)\n case 'form':\n return body;\n case 'files':\n if (body && typeof body === 'object' && 'files' in body) {\n return (body as MultipartResult).files;\n }\n return [];\n case 'fields':\n if (body && typeof body === 'object' && 'fields' in body) {\n return (body as MultipartResult).fields;\n }\n return {};\n // Phase 2.3:注入所有已注册 agent 元数据列表\n // 方案 A:优先读 app 实例注册表,无实例(编程式直调 ctx)回退默认全局实例\n case 'agents':\n return ctx.registries ? ctx.registries.agent.listAgents() : listAgents();\n // Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle\n case 'agent':\n return ctx.registries ? ctx.registries.agentHandle.get(ctx) : getAgentHandle(ctx);\n default:\n return undefined;\n }\n}\n\n/**\n * 根据注入信息,准备参数值并调用 handler(异步版本)\n *\n * 支持 async handler。内置注入优先于注入器(避免 query/body 等被覆盖)。\n * 非内置参数从注入器注册表按参数名查找,按需执行。\n */\nexport async function injectParamsAsync(\n handler: (...args: unknown[]) => unknown,\n ctx: FaapiContext,\n body?: unknown,\n injectors?: InjectorMap,\n): Promise<unknown> {\n const injections = resolveInjection(handler);\n\n if (injections.length === 0) {\n return await handler();\n }\n\n const args = await Promise.all(\n injections.map(async (injection) => {\n // 内置注入优先\n if (injection.type !== 'unknown') {\n return getBuiltinInjectionValue(injection.type, ctx, body);\n }\n // 注入器按参数名匹配\n if (injectors && injection.name in injectors) {\n // in 检查已证明存在\n return await injectors[injection.name]!(ctx);\n }\n return undefined;\n }),\n );\n\n return await handler(...args);\n}\n","import type { FaapiContext } from './contextTypes';\nimport type { ResponseMeta } from './contextTypes';\nimport type { SseWriter } from './sse';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { toResponse } from '../response/toResponse';\nimport { deferMetaHeaders, isHeadersOnlyMeta } from '../response/pendingMeta';\nimport { wrapOkResult } from '../response/responseFormatter';\nimport { injectParamsAsync } from '../injection/injectParams';\n\n/**\n * 自动包裹 handler 返回值(统一响应包装)\n *\n * 实现已移至 [response/responseFormatter.ts](../response/responseFormatter.ts) 的\n * wrapOkResult,与 ctx.ok() / formatFailResponse / formatErrorResponse 共享同一套\n * ok/fail 函数,确保响应格式在所有路径一致。\n *\n * 规则:\n * - Response 对象:不包裹(ctx.ok/ctx.fail/ctx.json 等返回的 Response 原样透传)\n * - 其他值(含 null/undefined):用 config.response.ok(或默认 (data) => ({ data })) 包裹\n *\n * ctx.ok() 返回的是 Response 对象,所以 handler 用 `return ctx.ok(data)` 或 `return data`\n * 最终响应一致(都是 ok(data) 的结果),不会双重包裹。\n *\n * 注意:null/undefined 也会被包裹为 { data: null } / { data: undefined }(JSON.stringify\n * 后者得到 \"{}\"),不再返回 204 No Content。如需 204,显式返回 Response 对象。\n */\nfunction wrapResult(result: unknown, ctx: FaapiContext): unknown {\n return wrapOkResult(result, ctx.config);\n}\n\n/**\n * 将 ctx.meta(setStatus/setHeader/setCookie 设置的响应元数据)合并到 Response\n *\n * 用于中间件返回 Response 时,确保用户通过 ctx.setStatus/setHeader 设置的\n * 状态码、响应头、cookie 能正确附加到中间件返回的 Response 上。\n */\nexport function mergeMeta(response: Response, meta: ResponseMeta): Response {\n const hasMeta =\n meta.status !== undefined || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;\n if (!hasMeta) return response;\n\n // 仅 headers 时延迟到发送层落头(零 Response 重建,见 pendingMeta.ts)\n if (isHeadersOnlyMeta(meta)) {\n deferMetaHeaders(response, meta.headers);\n return response;\n }\n\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(meta.headers)) {\n headers.set(key, value);\n }\n for (const cookie of meta.setCookies) {\n headers.append('set-cookie', cookie);\n }\n return new Response(response.body, {\n status: meta.status ?? response.status,\n headers,\n });\n}\n\n/**\n * 洋葱模型调度:将中间件链包装成 next 函数\n *\n * 每个中间件的 `await next()` 会执行下一个中间件并返回其 Response;\n * 最内层的 next 执行 handler(含参数注入)。\n *\n * 中间件返回 Response 的语义:\n * - 在 await next() 之前返回:拦截请求(不执行后续中间件和 handler)\n * - 在 await next() 之后返回:替换内层返回的响应\n * - 在 catch 块中返回:作为错误响应\n *\n * 中间件返回 void/undefined:使用 await next() 返回的内层响应。\n * 中间件不调用 next() 也不返回 Response:抛错(语义模糊,禁止使用)。\n *\n * 中间件返回 Response 时会合并 ctx.meta(setStatus/setHeader/setCookie 的设置),\n * 确保 ctx 便捷方法在中间件拦截场景下也生效。\n */\nexport async function compose(\n middlewares: FaapiMiddleware[],\n ctx: FaapiContext,\n finalHandler: () => Promise<Response>,\n): Promise<Response> {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n let index = -1;\n\n async function dispatch(i: number): Promise<Response> {\n if (i <= index) {\n throw new Error('next() called multiple times');\n }\n index = i;\n\n // 所有中间件执行完,运行 handler\n if (i >= middlewares.length) {\n return await finalHandler();\n }\n\n // i >= middlewares.length 已早退,此处索引必然存在\n const mw = middlewares[i]!;\n // next() 返回内层 Response,中间件可选择使用或替换\n let innerResponse: Response | undefined;\n const next = async (): Promise<Response> => {\n innerResponse = await dispatch(i + 1);\n return innerResponse;\n };\n\n const result = await mw(ctx, next);\n\n // 中间件返回 Response:使用它(拦截或替换),并合并 ctx.meta\n if (result instanceof Response) {\n return mergeMeta(result, meta);\n }\n // 中间件返回 void:使用 next() 返回的内层响应\n if (innerResponse !== undefined) {\n return innerResponse;\n }\n // 中间件既没返回 Response 也没调用 next():语义模糊,禁止\n throw new Error('中间件必须 await next() 或返回 Response');\n }\n\n return await dispatch(0);\n}\n\n/**\n * 调用路由 handler 并将返回值转为 Response\n *\n * 流程(洋葱模型):\n * 1. 中间件按洋葱模型执行:mw1.before → mw2.before → ... → handler → ... → mw2.after → mw1.after\n * 2. 中间件不调用 next() 即拦截请求(必须返回 Response)\n * 3. 中间件可用 try/catch 捕获内层错误\n * 4. 最内层执行注入器(按需)→ handler\n *\n * 注入器与中间件解耦:\n * - 注入器按 handler 参数名匹配,只执行需要的\n * - 注入器可读取中间件塞进 ctx 的值\n */\nexport async function invokeHandler(\n handler: (...args: unknown[]) => unknown,\n ctx: FaapiContext,\n body?: unknown,\n middlewares?: FaapiMiddleware[],\n injectors?: InjectorMap,\n): Promise<Response> {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n\n /**\n * 若 handler 调用了 ctx.sse(),优先使用 SSE Response\n * (handler 返回值被忽略,SSE 流由 writer 控制)\n *\n * 同时执行自动 close 兜底:若 handler 返回时 writer 仍未关闭,自动调用 close(),\n * 避免连接泄漏。handler 异步循环未退出、忘记 close、或抛错的场景由这里兜底。\n */\n const pickSseAndAutoClose = (): Response | null => {\n const sseWriter = (ctx as FaapiContext & { __sseWriter?: SseWriter }).__sseWriter;\n if (!sseWriter) return null;\n // 兜底:handler 未显式 close 时自动关闭\n if (!sseWriter.closed && !sseWriter.aborted) {\n sseWriter.close();\n }\n return mergeMeta(sseWriter.response, meta);\n };\n\n // 抛错时也保证 writer 被关闭(避免流泄漏)\n const autoCloseSseOnError = (): void => {\n const sseWriter = (ctx as FaapiContext & { __sseWriter?: SseWriter }).__sseWriter;\n if (sseWriter && !sseWriter.closed && !sseWriter.aborted) {\n sseWriter.close();\n }\n };\n\n // 无中间件:直接执行 handler\n if (!middlewares || middlewares.length === 0) {\n try {\n const result = await injectParamsAsync(handler, ctx, body, injectors);\n const sseResponse = pickSseAndAutoClose();\n if (sseResponse) return sseResponse;\n return toResponse(wrapResult(result, ctx), meta);\n } catch (err) {\n // handler 抛错时关闭未完成的 SSE 流,避免泄漏\n autoCloseSseOnError();\n throw err;\n }\n }\n\n // 有中间件:洋葱模型调度\n const finalHandler = async (): Promise<Response> => {\n try {\n const result = await injectParamsAsync(handler, ctx, body, injectors);\n const sseResponse = pickSseAndAutoClose();\n if (sseResponse) return sseResponse;\n return toResponse(wrapResult(result, ctx), meta);\n } catch (err) {\n // handler 抛错时关闭未完成的 SSE 流,避免泄漏\n autoCloseSseOnError();\n throw err;\n }\n };\n\n return await compose(middlewares, ctx, finalHandler);\n}\n","import type { ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { consumePendingMetaHeaders } from './pendingMeta';\n\n/**\n * 将 Web Response 写入 Node.js ServerResponse\n *\n * 使用 stream pipe 处理背压,避免高并发下内存膨胀。\n *\n * 断连语义:客户端中断(res 'error' / 提前 'close')时销毁源流并按正常完成收尾——\n * 网络中断不是服务端错误,不进入 500 兜底、不触发 onError;destroy 使底层\n * web ReadableStream 触发 cancel(),上游生产者(如 SseWriter)感知 aborted\n * 停止推送,避免数据堆积在无消费者的流 buffer 中(内存泄漏)。\n * 源流自身错误(handler 流式写失败)仍 reject,走正常错误响应路径。\n */\nexport async function sendNodeResponse(response: Response, res: ServerResponse): Promise<void> {\n // 设置状态码\n res.statusCode = response.status;\n\n // 设置 headers\n for (const [key, value] of response.headers) {\n if (key.toLowerCase() === 'set-cookie') {\n // Set-Cookie 使用 appendHeader 支持多个值\n res.appendHeader(key, value);\n } else {\n res.setHeader(key, value);\n }\n }\n // 延迟落头(headers-only meta 的零重建通道,见 pendingMeta.ts)——\n // 后写覆盖,与 Headers.set 语义一致\n const deferred = consumePendingMetaHeaders(response);\n if (deferred) {\n for (const [key, value] of Object.entries(deferred)) {\n res.setHeader(key, value);\n }\n }\n\n // 写入 body(使用 pipe 处理背压)\n if (response.body) {\n // TS 5.7 lib.dom 的 ReadableStream 与 node:stream/web 的 ReadableStream 是不同类型,cast 绕过\n const nodeStream = Readable.fromWeb(response.body as never);\n await new Promise<void>((resolve, reject) => {\n // 源流错误:真实错误 → reject(handleRequest catch 走错误响应)\n nodeStream.on('error', reject);\n\n // 客户端断开:销毁源流(触发底层 cancel)+ resolve(不当作服务端错误)\n const abort = () => {\n nodeStream.destroy();\n resolve();\n };\n res.on('error', abort);\n res.on('close', () => {\n // 正常完成也会触发 'close'(在 'finish' 之后,此时 writableEnded=true),不视为断连\n if (!res.writableEnded) abort();\n });\n\n res.on('finish', resolve);\n nodeStream.pipe(res);\n });\n return;\n }\n\n res.end();\n}\n","import { 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 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 zlib from 'node:zlib';\nimport { promisify } from 'node:util';\nimport type { FaapiMiddleware } from './middlewareTypes';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\n\nconst gzipAsync = promisify(zlib.gzip);\nconst deflateAsync = promisify(zlib.deflate);\nconst brotliAsync = promisify(zlib.brotliCompress);\n\nexport interface CompressionOptions {\n /**\n * 最小压缩字节数,body 低于该值不压缩(小 payload 压缩后反而变大)\n * @default 1024\n */\n threshold?: number;\n}\n\nconst DEFAULT_THRESHOLD = 1024;\n\n/** 可压缩的 Content-Type(text/event-stream 除外——流式语义不应缓冲) */\nconst COMPRESSIBLE_CHECKS: Array<(contentType: string) => boolean> = [\n (ct) => ct === 'application/json',\n (ct) => ct === 'application/javascript',\n (ct) => ct === 'image/svg+xml',\n (ct) => ct.startsWith('text/') && ct !== 'text/event-stream',\n];\n\nfunction isCompressible(contentType: string): boolean {\n const ct = contentType.split(';')[0]!.trim().toLowerCase();\n if (ct === '') return false;\n return COMPRESSIBLE_CHECKS.some((check) => check(ct));\n}\n\ninterface AcceptedEncoding {\n encoding: string;\n quality: number;\n}\n\n/**\n * 解析 Accept-Encoding 头(含 q 值)\n *\n * `gzip, br;q=0.8, identity;q=0, *;q=0.5` → [{gzip,1},{br,0.8},{identity,0},{*,0.5}]\n */\nexport function parseAcceptEncoding(header: string): AcceptedEncoding[] {\n return header\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part !== '')\n .map((part) => {\n const [encoding, ...params] = part.split(';');\n let quality = 1;\n for (const param of params) {\n const trimmed = param.trim();\n if (trimmed.startsWith('q=')) {\n const q = Number(trimmed.slice(2));\n if (Number.isFinite(q)) quality = q;\n }\n }\n return { encoding: (encoding ?? '').trim().toLowerCase(), quality };\n })\n .filter((e) => e.encoding !== '' && e.quality > 0);\n}\n\n/**\n * 从已接受的编码中选择压缩算法\n *\n * 服务器偏好 br > gzip > deflate(同等 q 值下);`*` 通配按 gzip 处理。\n * 未命中返回 null(客户端不接受压缩)。\n */\nexport function selectEncoding(accepted: AcceptedEncoding[]): 'br' | 'gzip' | 'deflate' | null {\n const byName = new Map<string, number>();\n for (const { encoding, quality } of accepted) {\n // 同一编码出现多次取最高 q\n byName.set(encoding, Math.max(byName.get(encoding) ?? 0, quality));\n }\n // q=0 表示明确不接受\n for (const [encoding, quality] of byName) {\n if (quality === 0) byName.delete(encoding);\n }\n\n for (const candidate of ['br', 'gzip', 'deflate'] as const) {\n if ((byName.get(candidate) ?? 0) > 0) return candidate;\n }\n if ((byName.get('*') ?? 0) > 0) return 'gzip';\n return null;\n}\n\nasync function compressBody(\n encoding: 'br' | 'gzip' | 'deflate',\n body: string,\n): Promise<Uint8Array> {\n const buf = Buffer.from(body, 'utf-8');\n switch (encoding) {\n case 'br':\n return brotliAsync(buf);\n case 'gzip':\n return gzipAsync(buf);\n case 'deflate':\n return deflateAsync(buf);\n }\n}\n\n/** 向 meta.headers 合并 Vary 值(CORS 等中间件可能已设置 Vary: Origin,不能覆盖) */\nfunction mergeVary(meta: ResponseMeta, value: string): void {\n const existing = meta.headers['Vary'] ?? meta.headers['vary'];\n if (!existing) {\n meta.headers['Vary'] = value;\n return;\n }\n // 大小写不敏感地检查是否已包含\n if (!existing.toLowerCase().includes(value.toLowerCase())) {\n const key = meta.headers['Vary'] !== undefined ? 'Vary' : 'vary';\n meta.headers[key] = `${existing}, ${value}`;\n }\n}\n\n/**\n * 创建响应压缩中间件(内建,默认关闭,`config.compression` 显式启用)\n *\n * 位于外层中间件链最前(包住 CORS/helmet/logger/全局),`await next()` 后拿到\n * 最终 Response:满足压缩条件(可压缩类型 + 大小达标 + 客户端接受)时缓冲 body\n * 异步压缩后构造新 Response 替换;否则透传。`Vary: Accept-Encoding` 通过\n * meta.headers 合并(与 CORS 的 Vary: Origin 共存),两种路径都生效。\n */\nexport function compression(options: CompressionOptions = {}): FaapiMiddleware {\n const threshold = options.threshold ?? DEFAULT_THRESHOLD;\n\n return async (ctx, next) => {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n const response = await next();\n if (!response) return response;\n\n // 无条件协商 Vary(响应内容随 Accept-Encoding 变化,与是否压缩本响应无关)\n mergeVary(meta, 'Accept-Encoding');\n\n // 跳过条件:客户端不接受 / 不可压缩类型 / 已编码 / no-transform / 204、304 等\n const acceptEncoding = ctx.request.headers.get('accept-encoding') ?? '';\n const encoding = selectEncoding(parseAcceptEncoding(acceptEncoding));\n const contentType = response.headers.get('content-type') ?? '';\n if (\n !encoding ||\n !isCompressible(contentType) ||\n response.headers.has('content-encoding') ||\n (response.headers.get('cache-control') ?? '').includes('no-transform') ||\n response.status === 204 ||\n response.status === 304 ||\n response.body === null\n ) {\n return response;\n }\n\n // 缓冲 body(toResponse 的 JSON body 本就是字符串);SSE 已被 content-type 排除\n // 延迟落头(headers-only meta)需并入重建的 Response,否则发送层取不到\n const deferredHeaders = consumePendingMetaHeaders(response);\n\n const bodyText = await response.text();\n if (bodyText.length < threshold) {\n // 未达阈值:body 已被 text() 消费,需重建等价 Response\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n const compressed = await compressBody(encoding, bodyText);\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n headers.set('Content-Encoding', encoding);\n // Content-Length 由 Response 构造时按压缩后字节数自动设置(先删避免重复)\n headers.delete('Content-Length');\n return new Response(compressed as unknown as BodyInit, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n","import { createHash } from 'node:crypto';\nimport type { FaapiMiddleware } from './middlewareTypes';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\n\nexport interface EtagOptions {\n /**\n * 生成弱 ETag(`W/\"<hash>\"`)。弱校验器允许压缩等表示差异下的 304 协商,\n * 与 compression 中间件配合正确;强 ETag 需对每个表示(编码)单独生成\n * @default true\n */\n weak?: boolean;\n}\n\nconst DEFAULTS: Required<EtagOptions> = { weak: true };\n\n/** 计算 ETag:SHA-1 指纹 base64;weak=true 生成弱校验器(W/ 前缀) */\nfunction computeEtag(body: string, weak: boolean): string {\n const hash = createHash('sha1').update(body).digest('base64');\n return weak ? `W/\"${hash}\"` : `\"${hash}\"`;\n}\n\n/** If-None-Match 比对:weak 模式用 RFC 7232 弱比较(忽略 W/ 前缀);strong 模式精确比对 */\nfunction ifNoneMatchMatches(ifNoneMatch: string, etag: string, weak: boolean): boolean {\n const normalize = (tag: string) => tag.trim().replace(/^W\\//i, '');\n if (weak) {\n const target = normalize(etag);\n return ifNoneMatch.split(',').some((tag) => {\n const trimmed = tag.trim();\n return trimmed === '*' || normalize(trimmed) === target;\n });\n }\n // 强比较:ETag 原样相等(含 W/ 前缀语义,弱 ETag 永不匹配强比较)\n return ifNoneMatch.split(',').some((tag) => tag.trim() === etag || tag.trim() === '*');\n}\n\n/**\n * 创建 ETag/304 协商中间件(内建,默认关闭,`config.etag` 显式启用)\n *\n * `await next()` 后对 GET/HEAD 2xx 响应计算弱 ETag 并写入 meta(handler 显式\n * `ctx.setETag()` 时不覆盖);请求 `If-None-Match` 弱比较命中时返回 304(无 body)。\n *\n * 中间件位置在 compression 内层:先算 ETag/304 再压缩——304 无 body 压缩自动跳过;\n * 弱 ETag 基于未压缩表示计算,语义正确。\n */\nexport function etag(options: EtagOptions = {}): FaapiMiddleware {\n const opts = { ...DEFAULTS, ...options };\n\n return async (ctx, next) => {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n const response = await next();\n if (!response) return response;\n\n // 仅协商幂等方法的 2xx 响应\n const method = ctx.request.method.toUpperCase();\n if (method !== 'GET' && method !== 'HEAD') return response;\n if (response.status < 200 || response.status >= 300) return response;\n\n // handler 已显式设置 ETag(ctx.setETag 写 meta.headers.etag)→ 控制权归 handler\n if (meta.headers['etag'] !== undefined || response.headers.has('etag')) {\n return response;\n }\n // 流式响应(SSE 等)不缓冲\n if (\n response.body === null ||\n (response.headers.get('content-type') ?? '').includes('text/event-stream')\n ) {\n return response;\n }\n\n const bodyText = await response.text();\n const etagValue = computeEtag(bodyText, opts.weak);\n\n // If-None-Match 弱比较命中 → 304(无 body,携带 ETag)\n const ifNoneMatch = ctx.request.headers.get('if-none-match');\n if (ifNoneMatch && ifNoneMatchMatches(ifNoneMatch, etagValue, opts.weak)) {\n return new Response(null, {\n status: 304,\n statusText: response.statusText,\n headers: { ETag: etagValue },\n });\n }\n\n // 200 路径:body 已被 text() 消费,重建响应;ETag 经 meta 传递(mergeMeta 兜底应用)\n meta.headers['etag'] = etagValue;\n const headers = new Headers(response.headers);\n // 延迟落头并入重建的 Response(etag 中间件位于 compression 内层,先消费)\n const deferredHeaders = consumePendingMetaHeaders(response);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n","/**\n * WebSocket 协议升级处理\n *\n * 监听 HTTP server 的 upgrade 事件,匹配 WS 路由后调用 ws 库完成协议升级,\n * 加载 handler.ts 的 WS 导出,绑定事件回调。\n *\n * 握手阶段复用洋葱中间件链(鉴权/CORS/限流/日志等),与同目录 HTTP 路由共享中间件。\n * 中间件塞入 ctx 的值(如 ctx.user)传入 WS handler。连接建立后切到事件模型,不走中间件。\n *\n * @see wsHandler.md WS handler 约定与握手中间件链设计\n */\nimport type { IncomingMessage } from 'node:http';\nimport type { Server } from 'node:http';\nimport type { Socket } from 'node:net';\nimport fs from 'node:fs';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport type { RawData } from 'ws';\nimport path from 'node:path';\nimport type { WsRouteMatch, RoutesRef } from '../router/routeTypes';\nimport { matchWsRoute } from '../router/matchRoute';\nimport { createContext } from '../runtime/createContext';\nimport { compose, mergeMeta } from '../runtime/invokeHandler';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport { getClientIp } from '../utils/getClientIp';\nimport { nodeHttpToWebHeaders, buildErrorResponse } from './serverUtils';\nimport {\n ensureCompiled,\n ensureMiddlewaresCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n prodPathToSourcePath,\n} from '../cli/compileOnDemand';\nimport { loadMergedMiddlewares } from '../middleware/loadMiddlewares';\nimport type { AppRegistries } from '../injection/registries';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\nimport {\n wrapWsSocket,\n type WsContext,\n type WsEventHandlers,\n type WsHandler,\n} from '../runtime/wsHandler';\n\n/**\n * 从 IncomingMessage 提取 URL pathname(不含 query)\n */\nfunction getPathname(req: IncomingMessage): string {\n const url = req.url ?? '/';\n const idx = url.indexOf('?');\n return idx >= 0 ? url.slice(0, idx) : url;\n}\n\n/**\n * 加载 WS handler 并执行,返回事件回调对象(可能为空)\n *\n * 直接动态 import handler.ts 并取 `WS` 导出(不经过 loadRouteModule,\n * 因为 WS 不是 HTTP 方法,loadRouteModule 的 method 维度不适用)。\n * watch 模式下通过时间戳 query string 绕过 ESM 缓存。\n *\n * Dev 按需编译模式:先确保编译再 import,避免 import 不存在的文件污染 Vite SSR 状态。\n */\nasync function loadWsHandler(\n filePath: string,\n ctx: WsContext,\n rootDir?: string,\n): Promise<WsEventHandlers | void> {\n // Dev 按需模式:先确保编译再 import(同 loadRouteModule 逻辑)\n if (isDevOnDemandEnabled() && rootDir) {\n const dist = getDevDist();\n if (dist) {\n const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);\n if (sourcePath && fs.existsSync(sourcePath)) {\n await ensureCompiled(sourcePath, rootDir, dist);\n }\n }\n }\n\n const module = (await importWithCacheBust(filePath, isDevOnDemandEnabled())) as Record<\n string,\n unknown\n >;\n\n const handler = module['WS'];\n if (typeof handler !== 'function') {\n throw new Error(`WS export not found in ${filePath}`);\n }\n return (handler as WsHandler)(ctx);\n}\n\n/**\n * 绑定 ws 库原生事件到 faapi 的 WsEventHandlers\n *\n * 注意:wss.handleUpgrade 回调触发时 socket 已经处于 OPEN 状态,\n * 'open' 事件不会再触发,需要直接同步调用 onOpen。\n */\nfunction bindEvents(rawSocket: WebSocket, handlers: WsEventHandlers | void): void {\n if (!handlers) return;\n const ws = wrapWsSocket(rawSocket);\n\n if (handlers.onOpen) {\n // handleUpgrade 回调触发时 socket 已是 OPEN,直接同步调用\n if (rawSocket.readyState === WebSocket.OPEN) {\n handlers.onOpen!(ws);\n } else {\n rawSocket.once('open', () => handlers.onOpen!(ws));\n }\n }\n if (handlers.onMessage) {\n rawSocket.on('message', (data: RawData, isBinary: boolean) => {\n // ws 库 message 事件传 Buffer | ArrayBuffer | Buffer[],归一化为 Buffer;\n // 二进制帧透传 Buffer(utf8 解码不可逆,protobuf/msgpack 等二进制协议会损坏),\n // 文本帧转 utf8 字符串——与 onMessage 类型声明 string | Buffer 一致\n const buf = toWsBuffer(data);\n handlers.onMessage!(ws, isBinary ? buf : buf.toString('utf8'));\n });\n }\n if (handlers.onClose) {\n rawSocket.on('close', (code: number, reason: Buffer) => {\n handlers.onClose!(ws, code, reason.toString('utf8'));\n });\n }\n if (handlers.onError) {\n rawSocket.on('error', (err: Error) => {\n handlers.onError!(ws, err);\n });\n }\n}\n\n/** ws 库 RawData(Buffer | ArrayBuffer | Buffer[])归一化为 Buffer */\nfunction toWsBuffer(data: RawData): Buffer {\n if (Buffer.isBuffer(data)) return data;\n if (Array.isArray(data)) return Buffer.concat(data);\n return Buffer.from(data as ArrayBuffer);\n}\n\n/**\n * 把 Web Response 写回原始 socket(握手被中间件拦截或出错时使用)\n *\n * socket 未升级为 WebSocket 时,按 HTTP 响应格式写回并销毁。\n */\nasync function sendResponseToSocket(socket: Socket, response: Response): Promise<void> {\n const body = await response.text().catch(() => '');\n const statusLine = `HTTP/1.1 ${response.status} ${response.statusText || ''}\\r\\n`;\n const headerLines: string[] = [];\n let hasContentLength = false;\n // 延迟落头(headers-only meta)并入写回的 header 行\n const deferredHeaders = consumePendingMetaHeaders(response);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n if (key.toLowerCase() === 'content-length') hasContentLength = true;\n headerLines.push(`${key}: ${value}`);\n }\n for (const [key, value] of response.headers) {\n if (key.toLowerCase() === 'content-length') {\n hasContentLength = true;\n }\n headerLines.push(`${key}: ${value}`);\n }\n if (!hasContentLength) {\n headerLines.push(`Content-Length: ${Buffer.byteLength(body)}`);\n }\n socket.write(statusLine + headerLines.join('\\r\\n') + '\\r\\n\\r\\n' + body);\n socket.destroy();\n}\n\nexport interface AttachWsOptions {\n /** HTTP server 实例(已由 createServer 创建) */\n server: Server;\n /** 路由可变引用容器(watch 模式热替换时 routesRef.wsCurrent 被更新) */\n routesRef: RoutesRef;\n /** 项目根目录,用于解析路由文件绝对路径 */\n rootDir: string;\n /** 业务配置,注入到 WsContext.config */\n config?: Record<string, unknown>;\n /** 全局中间件(来自 faapi.config.ts,WS 握手最外层) */\n globalMiddlewares?: FaapiMiddleware[];\n /** 全局注入器(来自 faapi.config.ts,WS handler 可通过 ctx 间接访问全局依赖) */\n globalInjectors?: InjectorMap;\n /**\n * 是否信任反向代理头(X-Forwarded-For),默认 false\n *\n * true 时 `ctx.ip` 取 `x-forwarded-for` 第一个 IP;false(默认)时直取 socket 地址。\n */\n trustedProxy?: boolean;\n /** app 级注册表——经 WsContext 进入 WS 握手链路 */\n registries?: AppRegistries;\n}\n\n/**\n * 在 HTTP server 上挂载 WebSocket 升级处理\n *\n * 创建独立的 WebSocketServer(noServer 模式),监听 server 的 upgrade 事件。\n * 路由匹配成功后,走洋葱中间件链:\n * - 中间件放行(await next):finalHandler 内调用 ws.handleUpgrade 完成协议升级、绑定事件回调\n * - 中间件拦截(返回 Response):把 Response 写回 socket 后销毁,不进行协议升级\n * - 中间件抛错:由 formatErrorResponse/内置兜底生成错误 Response,写回 socket 后销毁\n *\n * 中间件链顺序:全局中间件(外)→ 目录中间件(内)→ finalHandler。\n * watch 模式下 wsRoutes 通过 routesRef 引用更新。\n */\nexport function attachWebSocket(options: AttachWsOptions): WebSocketServer {\n const {\n server,\n routesRef,\n rootDir,\n config,\n globalMiddlewares,\n trustedProxy = false,\n registries,\n } = options;\n\n const wss = new WebSocketServer({ noServer: true });\n\n server.on('upgrade', async (req: IncomingMessage, socket: Socket, head: Buffer) => {\n try {\n await handleUpgradeRequest(req, socket, head);\n } catch (err) {\n // 兜底 catch:upgrade 监听器是 async 函数,路由匹配 / header 构造 / 上下文创建等\n // 抛出的未捕获错误会变成 unhandled rejection(Node 15+ 默认崩掉进程)。\n // HTTP 路径有 catch 兜底,WS 路径保持对称——写回 500 后销毁 socket。\n console.error('[faapi] WS upgrade 处理失败:', err);\n if (!socket.destroyed) {\n try {\n if (!socket.writableEnded) {\n socket.write('HTTP/1.1 500 Internal Server Error\\r\\n\\r\\n');\n }\n } catch {\n // socket 已不可写,忽略\n }\n socket.destroy();\n }\n }\n });\n\n async function handleUpgradeRequest(\n req: IncomingMessage,\n socket: Socket,\n head: Buffer,\n ): Promise<void> {\n const currentWsRoutes = routesRef.wsCurrent;\n const pathname = getPathname(req);\n const match: WsRouteMatch | null = matchWsRoute(currentWsRoutes, pathname);\n\n if (!match) {\n // 路由不匹配:返回 404 并销毁 socket\n socket.write('HTTP/1.1 404 Not Found\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n\n const { route, params } = match;\n const headers = nodeHttpToWebHeaders(req);\n const host = req.headers.host ?? 'localhost';\n const url = `http://${host}${req.url ?? '/'}`;\n\n // 构造 Web Request 与 FaapiContext(与 HTTP 请求一致,供中间件使用)\n const request = new Request(url, { method: 'GET', headers });\n const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n\n // 标记握手是否已完成协议升级(用于判断 socket 是否可写)\n let upgraded = false;\n\n // finalHandler:加载 WS handler + 协议升级 + 绑定事件\n const finalHandler = async (): Promise<Response> => {\n let handlers: WsEventHandlers | void;\n try {\n const absoluteFilePath = path.resolve(rootDir, route.filePath);\n // WsContext 是 FaapiContext 的结构子集,直接复用 ctx 实例\n // 中间件塞入的字段(如 ctx.user)对 WS handler 可见\n handlers = await loadWsHandler(absoluteFilePath, ctx as unknown as WsContext, rootDir);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.error(`[faapi] WS handler 加载失败 ${route.filePath}: ${reason}`);\n return new Response('Internal Server Error', { status: 500 });\n }\n\n // 协议升级:等 wss.handleUpgrade 回调完成 bindEvents\n await new Promise<void>((resolve, reject) => {\n wss.handleUpgrade(req, socket, head, (rawSocket: WebSocket) => {\n try {\n bindEvents(rawSocket, handlers);\n wss.emit('connection', rawSocket, req);\n upgraded = true;\n resolve();\n } catch (err) {\n reject(err);\n }\n });\n });\n\n // 占位 Response,表示握手成功(socket 已升级,不会被写出)\n return new Response(null, { status: 200 });\n };\n\n // 执行中间件链:全局中间件(外)+ 目录中间件(内)\n let response: Response;\n try {\n // 按需加载中间件:route.middlewares 为 undefined 时从 middlewarePaths 加载(Vite 风格)\n if (route.middlewares === undefined && route.middlewarePaths) {\n // dev 按需模式:先编译中间件源码(含依赖闭包)再 import\n await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);\n const bundle = await loadMergedMiddlewares(route.middlewarePaths);\n if (bundle) {\n route.middlewares = bundle.middlewares;\n } else {\n route.middlewares = [];\n }\n }\n const dirMiddlewares = route.middlewares ?? [];\n const allMiddlewares =\n globalMiddlewares && globalMiddlewares.length > 0\n ? [...globalMiddlewares, ...dirMiddlewares]\n : dirMiddlewares;\n if (allMiddlewares.length > 0) {\n response = await compose(allMiddlewares, ctx, finalHandler);\n } else {\n response = await finalHandler();\n }\n } catch (err) {\n if (upgraded) {\n // 握手已完成(socket 已被 ws 库接管),中间件 after 阶段抛错无法改写响应\n console.error('[faapi] WS 握手后中间件抛错:', err);\n return;\n }\n // 错误兜底链:内置 formatErrorResponse 兜底\n // 业务方如需自定义错误响应,在全局中间件中 try/catch next() 即可\n response = buildErrorResponse(err);\n }\n\n // 握手成功:socket 已升级,中间件 after 阶段已执行完,无需再写\n if (upgraded) {\n return;\n }\n\n // 中间件拦截或错误:把 Response 写回 socket 后销毁\n await sendResponseToSocket(socket, mergeMeta(response, meta));\n }\n\n return wss;\n}\n","import type { IncomingMessage } from 'node:http';\nimport { formatErrorResponse } from '../errors/formatErrorResponse';\n\n/**\n * 将 Node.js IncomingMessage 的 headers 转为 Web Headers\n *\n * 处理数组型 header 值(如 set-cookie)和 undefined 值。\n */\nexport function nodeHttpToWebHeaders(req: IncomingMessage): Headers {\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const v of value) headers.append(key, v);\n } else {\n headers.set(key, value);\n }\n }\n return headers;\n}\n\n/**\n * 构建错误响应(兜底链)\n *\n * 1. 框架内置 formatErrorResponse 处理(handler 抛错时)——读 config.response.fail\n * 自定义包装函数,确保错误格式与 ctx.fail() 主动错误响应一致\n * 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n *\n * 业务方如需进一步自定义错误响应,在全局中间件中 try/catch next() 即可。\n *\n * @param err handler 抛出的错误\n * @param config 业务方配置(用于读取 response.fail,可选——未传时走框架默认 fail 函数)\n */\nexport function buildErrorResponse(\n err: unknown,\n config?: Record<string, unknown> | undefined,\n): Response {\n try {\n return formatErrorResponse(err, config);\n } catch {\n // 极端情况:内置兜底也失败,返回最简 500\n return new Response(\n JSON.stringify({ error: { code: 'INTERNAL_ERROR', message: 'Internal Server Error' } }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n}\n","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","/**\n * WebSocket Handler 类型定义与 socket 封装\n *\n * 用户在 handler.ts 中导出 WS 函数,返回事件回调对象。\n * 框架在协议升级成功后调用对应回调,传递封装后的 WsSocket。\n *\n * @see wsHandler.md 功能说明\n */\n\n/**\n * faapi 封装的 WebSocket socket 抽象\n *\n * 不直接暴露 ws 库的原生 socket,提供更安全、易用的 API。\n * send 对象时自动 JSON.stringify。\n */\nexport interface WsSocket {\n /** 发送数据(string/Buffer 直发,对象自动 JSON.stringify) */\n send(data: string | Buffer | object): void;\n /** 关闭连接 */\n close(code?: number, reason?: string | Buffer): void;\n /** 连接状态:0=connecting, 1=open, 2=closing, 3=closed */\n readonly readyState: number;\n}\n\n/**\n * WebSocket 事件回调集合\n *\n * 用户在 WS handler 中返回此对象,框架在对应事件触发时调用。\n * 所有回调可选,未提供则忽略事件。\n */\nexport interface WsEventHandlers {\n /** 连接建立时触发 */\n onOpen?: (ws: WsSocket) => void;\n /** 收到客户端消息时触发 */\n onMessage?: (ws: WsSocket, message: string | Buffer) => void;\n /** 连接关闭时触发 */\n onClose?: (ws: WsSocket, code: number, reason: string) => void;\n /** 发生错误时触发 */\n onError?: (ws: WsSocket, error: Error) => void;\n}\n\n/**\n * WebSocket 上下文(握手阶段构造)\n *\n * 与 HTTP FaapiContext 类似但精简,提供路由参数、查询参数、请求头、配置。\n * 可通过 declare module '@faapi/faapi' 增强自定义字段。\n */\nexport interface WsContext {\n /** 动态路由参数(如 [id] → params.id) */\n params: Record<string, string>;\n /** URL 查询参数 */\n query: URLSearchParams;\n /** 请求头 */\n headers: Headers;\n /** 业务配置(来自 faapi.config.ts) */\n config: Record<string, unknown>;\n /** 中间件塞入的用户信息(鉴权等) */\n user?: unknown;\n /** 允许通过 declare module 扩展 */\n [key: string]: unknown;\n}\n\n/**\n * WS handler 签名:接收 WsContext,返回事件回调对象(或无返回)\n */\nexport type WsHandler = (ctx: WsContext) => WsEventHandlers | void;\n\n/**\n * 将 ws 库的原生 WebSocket 封装为 faapi 的 WsSocket\n *\n * - send 对象时自动 JSON.stringify\n * - close/re.readyState 透传原生 socket\n */\nexport function wrapWsSocket(rawSocket: {\n send: (data: string | Buffer, cb?: (err?: Error) => void) => void;\n close: (code?: number, reason?: string | Buffer) => void;\n readonly readyState: number;\n}): WsSocket {\n return {\n send(data: string | Buffer | object): void {\n const payload =\n typeof data === 'string' || Buffer.isBuffer(data) ? data : JSON.stringify(data);\n rawSocket.send(payload);\n },\n close(code?: number, reason?: string | Buffer): void {\n rawSocket.close(code, reason);\n },\n get readyState(): number {\n return rawSocket.readyState;\n },\n };\n}\n","import type { Server } from 'node:http';\nimport type { RouteManifest, WsRouteManifest } from '../router/routeTypes';\nimport type { CorsOptions } from '../middleware/cors';\nimport type { FaapiContext } from '../runtime/contextTypes';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport type { RequestHandler, UpgradeHandler } from '../config/pluginTypes';\nimport { createServer } from './createServer';\n\nexport interface StartOptions {\n port: number;\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 路由清单 */\n wsRoutes?: WsRouteManifest;\n /** 全局中间件(来自 faapi.config.ts,对所有路由生效,最外层) */\n middlewares?: FaapiMiddleware[];\n /** 全局注入器(来自 faapi.config.ts,对所有路由 handler 参数注入生效) */\n injectors?: InjectorMap;\n /**\n * server.listen 之前的钩子(用于加载插件、应用 handler 包装等)\n *\n * 钩子在 server 创建后、listen 之前调用,可修改 server 的 request/upgrade listener。\n */\n beforeListen?: (server: Server) => Promise<void> | void;\n}\n\n/**\n * 启动 HTTP server 并打印路由表\n *\n * 流程:createServer → beforeListen(可选,加载插件/应用包装)→ listen → 打印路由表\n */\nexport function startServer(options: StartOptions): Promise<Server> {\n const {\n port,\n routes,\n rootDir,\n dist,\n cors,\n onError,\n config,\n wsRoutes,\n middlewares,\n injectors,\n beforeListen,\n } = options;\n const { server } = createServer({\n routes,\n rootDir,\n dist,\n cors,\n onError,\n config,\n wsRoutes,\n middlewares,\n injectors,\n });\n\n return (async () => {\n // beforeListen 钩子:加载插件、应用 handler 包装等\n if (beforeListen) {\n await beforeListen(server);\n }\n\n return new Promise<Server>((resolve) => {\n server.listen(port, () => {\n const address = server.address();\n const actualPort = typeof address === 'object' && address !== null ? address.port : port;\n\n console.log('faapi dev server started');\n console.log(`- Local: http://localhost:${actualPort}`);\n console.log('- Loaded routes:');\n\n for (const route of routes) {\n const method = route.method.padEnd(6);\n console.log(` ${method}${route.urlPath} ${route.filePath}`);\n }\n\n if (wsRoutes && wsRoutes.length > 0) {\n console.log('- WebSocket routes:');\n for (const route of wsRoutes) {\n console.log(` WS ${route.urlPath} ${route.filePath}`);\n }\n }\n\n resolve(server);\n });\n });\n })();\n}\n\n/**\n * 应用 handler / upgrade 包装器到 server\n *\n * 在 server.listen 之前调用,替换 server 的 request/upgrade listener。\n * 包装器按数组顺序嵌套:finalHandler = wrap1(wrap2(originalHandler))。\n *\n * @param server 目标 server(未 listen)\n * @param handlerWrappers HTTP handler 包装器数组\n * @param upgradeWrappers WS upgrade handler 包装器数组\n */\nexport function applyPluginWrappers(\n server: Server,\n handlerWrappers: Array<(original: RequestHandler) => RequestHandler>,\n upgradeWrappers: Array<(original: UpgradeHandler | undefined) => UpgradeHandler>,\n): void {\n // 应用 HTTP handler 包装\n if (handlerWrappers.length > 0) {\n const listeners = server.listeners('request');\n const original = listeners[0] as RequestHandler | undefined;\n if (original) {\n server.removeAllListeners('request');\n let handler: RequestHandler = original;\n for (const wrap of handlerWrappers) {\n handler = wrap(handler);\n }\n server.on('request', handler);\n }\n }\n\n // 应用 WS upgrade handler 包装\n if (upgradeWrappers.length > 0) {\n const listeners = server.listeners('upgrade');\n const original = listeners[0] as UpgradeHandler | undefined;\n server.removeAllListeners('upgrade');\n let upgrade: UpgradeHandler | undefined = original;\n for (const wrap of upgradeWrappers) {\n upgrade = wrap(upgrade);\n }\n if (upgrade) {\n server.on('upgrade', upgrade);\n }\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type {\n RouteManifest,\n WsRouteManifest,\n RouteRecord,\n WsRouteRecord,\n} from '../router/routeTypes';\nimport type { HttpMethod } from '../router/constants';\nimport { toProdFilePath } from '../utils/prodPaths';\nimport { atomicWriteFile } from '../utils/atomicWrite';\n\n/**\n * 序列化路由记录(可写入 JS 模块,无函数引用)\n *\n * build 时生成,start 时读取。filePath/middlewarePaths 已转为 prd 形式\n * (打平 src/ 前缀 + dist 前缀 + .js)。\n */\nexport interface SerializedRouteRecord {\n method: HttpMethod;\n urlPath: string;\n filePath: string;\n paramNames: string[];\n isDynamic: boolean;\n isCatchAll?: boolean;\n /** 从根到路由目录的中间件文件绝对路径列表(已排序,根在前) */\n middlewarePaths: string[];\n}\n\nexport interface SerializedWsRouteRecord {\n urlPath: string;\n filePath: string;\n paramNames: string[];\n isDynamic: boolean;\n isCatchAll?: boolean;\n middlewarePaths: string[];\n}\n\nexport interface SerializedRouteManifest {\n routes: SerializedRouteRecord[];\n wsRoutes: SerializedWsRouteRecord[];\n}\n\n/**\n * 序列化路由清单为可写入 JS 模块的结构\n *\n * - filePath 转为产物路径(打平 src/ 前缀 + dist 前缀 + .js)\n * - middlewares/injectors 不序列化(函数无法序列化),改为 middlewarePaths(中间件文件绝对路径列表)\n * - middlewarePaths 已排序(根在前,路由目录在后),start 时按序加载即可还原洋葱模型\n *\n * @param rootDir 项目根目录\n * @param dist 产物目录(dist 或 .faapi),用于转换 filePath 和查找中间件\n */\nexport function serializeRoutes(\n routes: RouteManifest,\n wsRoutes: WsRouteManifest,\n rootDir: string,\n dist: string = 'dist',\n): SerializedRouteManifest {\n const serialize = <T extends RouteRecord | WsRouteRecord>(\n route: T,\n ): T extends RouteRecord ? SerializedRouteRecord : SerializedWsRouteRecord => {\n const middlewarePaths = extractMiddlewarePaths(route.filePath, rootDir, dist);\n const serialized = {\n urlPath: route.urlPath,\n filePath: toProdFilePath(route.filePath, dist),\n paramNames: route.paramNames,\n isDynamic: route.isDynamic,\n isCatchAll: route.isCatchAll,\n middlewarePaths,\n };\n if ('method' in route) {\n (serialized as SerializedRouteRecord).method = (route as RouteRecord).method;\n }\n return serialized as T extends RouteRecord ? SerializedRouteRecord : SerializedWsRouteRecord;\n };\n\n return {\n routes: routes.map(serialize) as SerializedRouteRecord[],\n wsRoutes: wsRoutes.map(serialize) as SerializedWsRouteRecord[],\n };\n}\n\n/**\n * 从路由文件路径,向上查找所有中间件文件绝对路径(根在前,路由目录在后)\n *\n * 与 scanRoutes.findMergedMiddlewares 的收集逻辑一致,但只返回路径不加载。\n * 检查源码形式(.ts)中间件文件是否存在,返回产物形式(.js)绝对路径。\n *\n * @param routeFilePath 源码相对路径(如 src/api/hello/handler.ts)\n * @param rootDir 项目根目录\n * @param dist 产物目录(dist 或 .faapi)\n */\nfunction extractMiddlewarePaths(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 // 检查源码 middlewares.ts 是否存在,回退 middlewares.js(prd 残留场景)\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/ 前缀)\n const relMwPath = path.relative(rootDir, absMwPath);\n const prodAbsPath = path.resolve(rootDir, toProdFilePath(relMwPath, dist));\n paths.push(prodAbsPath);\n }\n if (currentDir === resolvedRoot) break;\n // 不超出 src 目录(避免向上查到非源码目录)\n // 到达 src 的父目录(rootDir)时停止\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 * 把序列化的路由清单写入 JS 模块\n *\n * 生成 ESM 模块,start 时通过 import() 加载。\n */\nexport async function writeRoutesModule(\n manifest: SerializedRouteManifest,\n outputPath: string,\n): Promise<void> {\n // 用 JSON.stringify 嵌入,保证字符串转义安全\n const content = `// 自动生成,请勿手动编辑(faapi build 产物)\nexport const routes = ${JSON.stringify(manifest.routes, null, 2)};\nexport const wsRoutes = ${JSON.stringify(manifest.wsRoutes, null, 2)};\n`;\n\n await atomicWriteFile(outputPath, content);\n}\n\n/**\n * 从序列化清单水合路由(还原 RouteRecord,不预加载中间件)\n *\n * 中间件改为按需加载:只把 `middlewarePaths` 存到 `route.middlewarePaths`,\n * 请求阶段由 `createServer` / `handleWsUpgrade` 调 `loadMergedMiddlewares` 按需加载。\n * 这样 dev/prod 启动时都不全量加载中间件模块(Vite 风格),首次请求时才加载。\n */\nexport async function hydrateRoutes(\n manifest: SerializedRouteManifest,\n): Promise<{ routes: RouteManifest; wsRoutes: WsRouteManifest }> {\n const hydrateRoute = (serialized: SerializedRouteRecord): RouteRecord => ({\n method: serialized.method,\n urlPath: serialized.urlPath,\n filePath: serialized.filePath,\n paramNames: serialized.paramNames,\n isDynamic: serialized.isDynamic,\n isCatchAll: serialized.isCatchAll,\n middlewarePaths: serialized.middlewarePaths,\n });\n\n const hydrateWsRoute = (serialized: SerializedWsRouteRecord): WsRouteRecord => ({\n urlPath: serialized.urlPath,\n filePath: serialized.filePath,\n paramNames: serialized.paramNames,\n isDynamic: serialized.isDynamic,\n isCatchAll: serialized.isCatchAll,\n middlewarePaths: serialized.middlewarePaths,\n });\n\n const routes = manifest.routes.map(hydrateRoute);\n const wsRoutes = manifest.wsRoutes.map(hydrateWsRoute);\n return { routes, wsRoutes };\n}\n","import path from 'node:path';\nimport type { AgentManifestList } from '../agents/agentTypes';\nimport type { AgentMetadata } from '../ast/extractAgentMetadata';\nimport { extractAgentMetadata } from '../ast/extractAgentMetadata';\nimport { createPrograms } from '../ast/createProgram';\nimport { toProdFilePath } from '../utils/prodPaths';\nimport { atomicWriteFile } from '../utils/atomicWrite';\n\n/**\n * 序列化的 agent manifest 记录(可写入 JS 模块,无函数引用)\n *\n * 与 [AgentMetadata](../ast/extractAgentMetadata.md) 字段一一对应,仅 `filePath`\n * 由源码形式(`src/...`)转为产物形式(`<dist>/...`,打平 `src/` 前缀 + dist 前缀 + `.js`)。\n *\n * `undefined` 字段(description / systemPrompt / tools / agents / model / maxTurns)\n * 在 JSON.stringify 时自动省略,水合时通过 `??` 兜底为 undefined。\n *\n * > `hasConfig` 字段已移除——它原本用于控制 `loadAgentModule` 是否提取 `config` 对象,\n * > 但 `AgentModule.config` 已废弃(`executeSubAgent` 拿到后从不读取),属于死链路。\n */\nexport interface SerializedAgentRecord {\n /** agent 名(`@agent` 覆盖值 或 目录推导值) */\n name: string;\n /** JSDoc 描述(对 LLM 可见),无则省略 */\n description?: string;\n /** 是否导出 run 函数(从 manifest 透传) */\n hasRun: boolean;\n /** 系统提示词(config 块字面量提取),无/非字面量时省略 */\n systemPrompt?: string;\n /** agent 显式声明可用的 tool 引用列表(config 块字面量提取),无/含非字面量元素时省略 */\n tools?: string[];\n /** 可调用的其他 agent 名列表(config 块字面量提取),无/含非字面量元素时省略 */\n agents?: string[];\n /** LLM 模型名(config 块字面量提取),无/非字面量时省略 */\n model?: string;\n /** 最大对话轮数(config 块字面量提取),无/非字面量时省略 */\n maxTurns?: number;\n /** 产物形式路径(如 `dist/agents/researcher/handler.js`),供运行时 import agent.js */\n filePath: string;\n}\n\n/**\n * faapi-agents.js 文件名(与 faapi-routes.js / faapi-tools.js 同构)\n */\nconst AGENTS_FILE = 'faapi-agents.js';\n\n/**\n * 序列化 agent 清单为可写入 JS 模块的结构\n *\n * - `filePath` 转为产物形式(打平 `src/` 前缀 + dist 前缀 + `.js`)\n * - 其他字段(name/description/hasRun/systemPrompt/tools/agents/model/maxTurns)直接透传\n * - `undefined` 字段在 JSON.stringify 时自动省略\n *\n * @param agents AST 增强后的 AgentMetadata[](由 generateAgentArtifacts 内部从 AgentManifest 增强)\n * @param dist 产物目录(默认 `dist`),用于转换 filePath\n */\nexport function serializeAgents(\n agents: AgentMetadata[],\n dist: string = 'dist',\n): SerializedAgentRecord[] {\n return agents.map((a) => ({\n name: a.name,\n description: a.description,\n hasRun: a.hasRun,\n systemPrompt: a.systemPrompt,\n tools: a.tools,\n agents: a.agents,\n model: a.model,\n maxTurns: a.maxTurns,\n filePath: toProdFilePath(a.filePath, dist),\n }));\n}\n\n/**\n * 把序列化的 agent 清单写入 faapi-agents.js\n *\n * 生成 ESM 模块,运行时 `createAppCore` 通过 `importWithCacheBust` 加载。\n * 用 JSON.stringify 嵌入,保证字符串转义安全(与 `writeRoutesModule` / `writeToolsModule` 一致)。\n */\nexport async function writeAgentsModule(\n manifest: SerializedAgentRecord[],\n outputPath: string,\n): Promise<void> {\n const content = `// 自动生成,请勿手动编辑(faapi build/dev 产物)\nexport const agents = ${JSON.stringify(manifest, null, 2)};\n`;\n await atomicWriteFile(outputPath, content);\n}\n\n/**\n * 从序列化清单水合还原 AgentMetadata[]\n *\n * 字段一一对应(无函数引用需还原)。`undefined` 字段在 JSON.parse 时缺失,\n * 通过 `??` 兜底为 undefined(保证 `AgentMetadata` 类型完整)。\n */\nexport function hydrateAgents(manifest: SerializedAgentRecord[]): AgentMetadata[] {\n return manifest.map((a) => ({\n name: a.name,\n description: a.description ?? undefined,\n filePath: a.filePath,\n hasRun: a.hasRun,\n systemPrompt: a.systemPrompt ?? undefined,\n tools: a.tools ?? undefined,\n agents: a.agents ?? undefined,\n model: a.model ?? undefined,\n maxTurns: a.maxTurns ?? undefined,\n }));\n}\n\n/**\n * 清单级校验:跨 agent 组合才暴露的问题,静默水合会让注册表处于与声明意图不符的状态\n *\n * - **agent 名重复**——水合语义是后者覆盖前者,静默覆盖丢失 agent\n * - **`agents` 引用不存在的 agent 名**——sub-agent 递归到运行时首次调用才失败,\n * 错误被推迟且不带构建上下文\n *\n * `tools` 引用不做构建期校验——业务方 plugin 可在运行时注册额外 tool\n * (`PluginContext.registries`),构建期校验会误报。\n */\nfunction validateAgentList(metadata: AgentMetadata[]): void {\n const names = new Map<string, string>();\n for (const a of metadata) {\n const prev = names.get(a.name);\n if (prev) {\n throw new Error(\n `agent 名重复: \"${a.name}\"(${prev} 与 ${a.filePath} 冲突——目录推导名或 @agent 覆盖名撞名)`,\n );\n }\n names.set(a.name, a.filePath);\n }\n\n for (const a of metadata) {\n for (const ref of a.agents ?? []) {\n if (!names.has(ref)) {\n const available = [...names.keys()].join(', ') || '无';\n throw new Error(\n `agent \"${a.name}\" 的 agents 引用了不存在的 agent: \"${ref}\"(清单中可用: ${available})`,\n );\n }\n }\n }\n}\n\n/**\n * 主入口:从 AgentManifest[] 生成 faapi-agents.js\n *\n * agent **不生成 zod.js**——与 tool 不同,agent 没有用户输入参数(config 块字段\n * 已在 [extractAgentMetadata](../ast/extractAgentMetadata.md) AST 阶段提取为字面量),\n * 运行时无需 schema 校验。`run` 函数参数由 [agentRegistry](../injection/agentRegistry.md)\n * 的 `asTool()` 在 Phase 2.2 处理(若需输入校验,由 agent 子包在 Phase 3.x 自行生成)。\n *\n * 内部流程:\n * 1. 对每个 AgentManifest 调 `createProgram` + `extractAgentMetadata` → AgentMetadata[]\n * (AST 增强:补全 description / `@agent` 覆盖名 / config 块字段)\n * 2. `validateAgentList` 清单级校验(名重复 / agents 互引存在性)\n * 3. `serializeAgents(metadata, dist)` → SerializedAgentRecord[](filePath 转产物形式)\n * 4. `writeAgentsModule(serialized, faapiAgentsPath)` → 写入 `<dist>/faapi-agents.js`\n *\n * 与 [generateToolArtifacts](./generateToolArtifacts.md) 的差异:\n * - 不生成 zod.js(agent 无输入参数)\n * - 无 `skipSchema` 选项(没有 schema 可跳过)\n * - 文件名常量为 `faapi-agents.js`,导出 `agents` 而非 `tools`\n *\n * @param agents scanAgents 产出的 AgentManifest[](仅路径推导字段)\n * @param rootDir 项目根目录\n * @param dist 产物目录(`.faapi` 或 `dist`)\n * @returns AST 增强后的 AgentMetadata[](供调用方日志/调试)\n */\nexport async function generateAgentArtifacts(\n agents: AgentManifestList,\n rootDir: string,\n dist: string,\n): Promise<AgentMetadata[]> {\n // 1. AST 增强:对每个 manifest 调 extractAgentMetadata(批量共享 Program)\n const metadata: AgentMetadata[] = [];\n const programByFile = createPrograms(agents.map((m) => path.resolve(rootDir, m.filePath)));\n for (const manifest of agents) {\n const absPath = path.resolve(rootDir, manifest.filePath);\n const program = programByFile.get(absPath)!;\n const result = extractAgentMetadata(program, absPath, {\n name: manifest.name,\n filePath: manifest.filePath,\n hasRun: manifest.hasRun,\n });\n // 正常构建链路不该发生(createPrograms 按同一批 filePath 建 Program)——\n // 静默跳过会让 agent 从清单里无声消失\n if (!result) {\n throw new Error(\n `agent \"${manifest.name}\" 源文件不在 Program 中: ${manifest.filePath}——无法生成清单`,\n );\n }\n metadata.push(result);\n }\n\n // 2. 清单级校验:名重复 / agents 互引存在性\n validateAgentList(metadata);\n\n // 3. 序列化 + 写入 faapi-agents.js\n const serialized = serializeAgents(metadata, dist);\n const agentsPath = path.resolve(rootDir, dist, AGENTS_FILE);\n await writeAgentsModule(serialized, agentsPath);\n\n return metadata;\n}\n","import ts from 'typescript';\nimport {\n extractDescription,\n extractJSDocTagValue,\n getJSDocFromNode,\n hasExportModifier,\n} from './jsDocMetadata';\nimport { SchemaExtractionError } from './resolveTypeNode';\n\n/**\n * Agent 的 LLM 可见核心字段\n *\n * 描述\"agent 是什么\"——LLM 真正需要消费的字段,**不含**代码本体加载细节\n * (filePath / hasRun)。文件型 agent 与 DB-driven skill 都实现此接口。\n *\n * - 文件型 agent:由 [AgentMetadata](./extractAgentMetadata.md) 继承扩展,\n * 额外含 `filePath` / `hasRun`(代码本体加载用)\n * - DB-driven skill:业务方 plugin 从 DB 字段映射到本接口即可,无需填占位值\n * (skill 无源文件,不走 `loadAgentModule`,自然不读 filePath / hasRun)\n *\n * `@faapi/agent` 子包的 `Agent` 类、`agentRegistry` 查询入口、`asTool` 包装\n * 都消费 `AgentCore`,实现\"agent 与 skill 走同一运行时链路\"。\n */\nexport interface AgentCore {\n /** agent 名(`@agent` JSDoc 覆盖值 或 目录推导值) */\n name: string;\n /** JSDoc 描述(agent 描述,对 LLM 可见),无 JSDoc 或 JSDoc 无自由文本时为 `undefined` */\n description?: string;\n /** 系统提示词(config 块字面量提取);文件型 agent 必填(构建期校验),DB skill 由业务方自治 */\n systemPrompt?: string;\n /** agent 显式声明可用的 tool 引用列表(config 块字面量提取),未声明时为 `undefined`;声明了但含非字面量元素在构建期抛错 */\n tools?: string[];\n /** 可调用的其他 agent 名列表(config 块字面量提取),未声明时为 `undefined`;声明了但含非字面量元素在构建期抛错 */\n agents?: string[];\n /** LLM 模型名(config 块字面量提取),未声明时为 `undefined`;声明了但非字面量在构建期抛错 */\n model?: string;\n /** 最大对话轮数(config 块字面量提取),未声明时为 `undefined`;声明了但非数字字面量在构建期抛错 */\n maxTurns?: number;\n}\n\n/**\n * Agent 完整元数据(文件型 agent)\n *\n * 继承 [AgentCore](./extractAgentMetadata.md) 的 LLM 字段,额外扩展**代码本体加载细节**:\n * - `filePath` — `loadAgentModule` 加载 `handler.js` 产物提取 `run` 函数用\n * - `hasRun` — 是否导出 `run` 函数(`Agent.executeSubAgent` 据此决定走自定义 run\n * 还是默认 reactLoop)\n *\n * DB-driven skill 不实现此接口(无源文件,无需加载),只实现 `AgentCore`。\n *\n * 由 [extractAgentMetadata](./extractAgentMetadata.md) 产出,合并路径推导字段\n * (来自 [scanAgents](../agents/scanAgents.md) 的 `AgentManifest`)与 AST 提取字段\n * (JSDoc 描述、`@agent` 覆盖名、config 块字段)。\n *\n * 字段来源:\n * - `name` — `@agent` JSDoc 覆盖值,或 `pathMeta.name`(目录推导)\n * - `filePath` / `hasRun` — 由 `pathMeta` 透传\n * - `description` — JSDoc 注释块自由文本(对 LLM 可见)\n * - `systemPrompt` / `tools` / `agents` / `model` / `maxTurns` — config 块字面量提取\n */\nexport interface AgentMetadata extends AgentCore {\n /** 源码相对路径(从 `pathMeta` 透传),`loadAgentModule` 据此加载 `handler.js` 提取 `run` */\n filePath: string;\n /** 是否导出 `run` 函数(从 `pathMeta` 透传),`Agent.executeSubAgent` 据此选择自定义 run / 默认 reactLoop */\n hasRun: boolean;\n}\n\n/**\n * 路径推导的 agent 元数据(由 [scanAgents](../agents/scanAgents.ts) 计算)\n *\n * 透传到 [AgentMetadata](./extractAgentMetadata.ts) 输出,与 AST 提取字段合并。\n * 与 [ToolPathMeta](./extractToolMetadata.md) 对称。\n */\nexport interface AgentPathMeta {\n /** 目录推导的 agent 名(如 `researcher`) */\n name: string;\n /** 源码相对路径(如 `src/agents/researcher/handler.ts`) */\n filePath: string;\n /** 是否导出 `run` 函数(scanAgents 正则检测) */\n hasRun: boolean;\n}\n\n/** config 导出查找结果:JSDoc 持有节点 + 对象字面量(可能为 null) */\ninterface FoundConfig {\n /** JSDoc 持有节点(VariableStatement 或 FunctionDeclaration) */\n jsDocOwner: ts.Node;\n /** config 对象字面量(export const config = {...} 的 initializer 或 export function config() 的 return 值) */\n objectLiteral: ts.ObjectLiteralExpression | null;\n}\n\n/**\n * 从 agent handler.ts 提取 agent 的完整元数据\n *\n * 提取内容:\n * 1. **JSDoc 描述** — config 导出的 JSDoc 自由文本\n * 2. **`@agent` 覆盖名** — JSDoc 中 `@agent` 标签后的文本,覆盖目录推导的 `name`\n * 3. **config 块字段** — systemPrompt / tools / agents / model / maxTurns\n *\n * 不提取(由 `pathMeta` 透传):`filePath` / `hasRun`\n *\n * config 查找支持两种导出形式,与 [scanAgents](../agents/scanAgents.md) 的 `CONFIG_EXPORT_RE` 正则同构:\n * - `export const config = { ... }` — 对象字面量(最常见)\n * - `export function config() { return { ... } }` — 函数返回对象\n *\n * JSDoc 查找对箭头函数/函数表达式自动回溯到外层 `VariableStatement`\n * (JSDoc 通常写在 `export const` 上方,而非箭头函数本身)。\n * 与 [extractToolMetadata](./extractToolMetadata.md) 的 JSDoc 查找同构。\n *\n * config 块字段提取仅接受字面量值——字符串字面量与无插值模板字符串同等提取;\n * 声明了字段但值提取失败(变量引用/含插值模板字符串/混合数组元素等)抛 `SchemaExtractionError`。\n * `systemPrompt` 必填——无 config 导出、config 无 return 对象、config 缺该 key 均抛错,\n * 提示词是 agent 的必要组成。\n *\n * @param program TypeScript Program\n * @param filePath 源文件**绝对路径**(AST 用,需与 `program.getSourceFile` 一致)\n * @param pathMeta 路径推导的元数据(scanAgents 已计算)\n * @returns `AgentMetadata` 或 `null`(源文件不在 Program 中)\n */\nexport function extractAgentMetadata(\n program: ts.Program,\n filePath: string,\n pathMeta: AgentPathMeta,\n): AgentMetadata | null {\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) return null;\n\n // systemPrompt 必填——config 是提示词的唯一载体,无 config 导出时直接抛错。\n // 不存在合法的无提示词文件型 agent(JSDoc description 只是用途说明,不构成提示词)。\n const configFound = findConfigExport(sourceFile);\n if (!configFound) {\n throw SchemaExtractionError.at(\n sourceFile,\n 'config.systemPrompt',\n 'agent 必填——请在 config 块声明系统提示词(字符串字面量或无插值模板字符串)',\n sourceFile,\n );\n }\n // 声明了 config 但提取不出对象字面量——形式不支持,与\"未声明\"分开提示,\n // 避免\"请在 config 块声明\"误导已声明 config 的用户\n if (!configFound.objectLiteral) {\n throw SchemaExtractionError.at(\n configFound.jsDocOwner,\n 'config',\n '仅支持对象字面量、箭头函数返回对象字面量或函数返回对象字面量',\n sourceFile,\n );\n }\n const objectLiteral = configFound.objectLiteral;\n\n const jsDoc = getJSDocFromNode(configFound.jsDocOwner);\n const description = extractDescription(jsDoc);\n const agentNameOverride = extractJSDocTagValue(jsDoc, 'agent');\n\n // config 块字段提取(extractConfigFields 保证 systemPrompt 非空——requireStringValue 失败即抛)\n const { systemPrompt, tools, agents, model, maxTurns } = extractConfigFields(\n objectLiteral,\n sourceFile,\n );\n\n return {\n name: agentNameOverride ?? pathMeta.name,\n description,\n filePath: pathMeta.filePath,\n hasRun: pathMeta.hasRun,\n systemPrompt,\n tools,\n agents,\n model,\n maxTurns,\n };\n}\n\n/**\n * 在源文件中查找 `config` 导出\n *\n * 支持两种形式:\n * - `export const config = { ... }` → VariableStatement,对象字面量是 initializer\n * - `export function config() { return { ... } }` → FunctionDeclaration,对象字面量是 return 表达式\n *\n * 返回 JSDoc 持有节点和对象字面量(可能为 null——函数无 return / return 非对象字面量 /\n * const initializer 非对象字面量与箭头函数)。`objectLiteral` 为 null 而 `jsDocOwner`\n * 已定位时,表达\"声明了 config 但形式不支持\",与\"未声明 config\"(返回 null)区分。\n *\n * 不依赖 `node.parent` 链——`ts.createProgram` 配置 `noEmit: true` 时不会设置父指针。\n */\nfunction findConfigExport(sourceFile: ts.SourceFile): FoundConfig | null {\n let result: FoundConfig | null = null;\n\n ts.forEachChild(sourceFile, (node) => {\n if (result) return;\n\n // export const config = { ... }\n if (ts.isVariableStatement(node) && hasExportModifier(node)) {\n for (const decl of node.declarationList.declarations) {\n if (result) break;\n const nameText = ts.isIdentifier(decl.name) ? decl.name.text : '';\n if (nameText !== 'config' || !decl.initializer) continue;\n\n // 记录 config 导出——形式是否支持由 objectLiteral 是否为 null 表达,\n // 调用方据此区分\"未声明 config\"与\"声明了但形式不支持\"\n if (ts.isObjectLiteralExpression(decl.initializer)) {\n result = { jsDocOwner: node, objectLiteral: decl.initializer };\n } else if (ts.isArrowFunction(decl.initializer)) {\n result = { jsDocOwner: node, objectLiteral: getReturnObjectLiteral(decl.initializer) };\n } else {\n result = { jsDocOwner: node, objectLiteral: null };\n }\n }\n }\n\n // export function config() { return { ... } }\n if (ts.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === 'config') {\n const returnObj = getReturnObjectLiteral(node);\n result = { jsDocOwner: node, objectLiteral: returnObj };\n }\n });\n\n return result;\n}\n\n/**\n * 在源文件中查找 `config` 导出\n *\n * - `() => ({ ... })` — 箭头函数直接返回对象字面量(body 是 ObjectLiteralExpression)\n * - `() => { return { ... }; }` — block body 中的 return 语句\n * - `function f() { return { ... }; }` — block body 中的 return 语句\n *\n * 无 return / return 非对象字面量 → null\n */\nfunction getReturnObjectLiteral(\n fn: ts.FunctionDeclaration | ts.ArrowFunction,\n): ts.ObjectLiteralExpression | null {\n const body = fn.body;\n if (!body) return null;\n\n // 箭头函数直接返回对象字面量:() => ({ ... })\n if (ts.isObjectLiteralExpression(body)) {\n return body;\n }\n\n // block body:查找 return 语句\n if (ts.isBlock(body)) {\n for (const stmt of body.statements) {\n if (\n ts.isReturnStatement(stmt) &&\n stmt.expression &&\n ts.isObjectLiteralExpression(stmt.expression)\n ) {\n return stmt.expression;\n }\n }\n }\n\n return null;\n}\n\n/**\n * 从 config 对象字面量提取 config 块字段\n *\n * 遍历对象属性,按属性名匹配提取对应字段。仅接受字面量值——\n * 字符串字面量与无插值模板字符串(`NoSubstitutionTemplateLiteral`)同等提取;\n * **声明了字段但值提取失败**(变量引用/含插值模板字符串/混合数组元素等)\n * 抛 `SchemaExtractionError`——静默降级为 `undefined` 后,运行时与\n * \"合法地未声明该字段\"不可区分,只能在构建期拦截。\n *\n * 属性名匹配支持 Identifier 和 StringLiteral 两种形式:\n * - `{ systemPrompt: 'x' }` — Identifier 属性名\n * - `{ 'systemPrompt': 'x' }` — StringLiteral 属性名\n *\n * SpreadAssignment(`...other`)跳过——不声明任何具名字段,无法静态归属。\n * 未知字段(拼写错误/框架不读的 key)与不支持的属性形式(shorthand/方法/\n * getter/computed 名)抛 `SchemaExtractionError`,不静默忽略。\n *\n * 返回类型的 `systemPrompt` 必填——属性存在但提取失败由 `requireStringValue`\n * 抛错,属性未声明(空对象/缺 key/仅 spread)由返回前校验抛错,正常返回时必有值。\n *\n * @param objLit config 对象字面量\n * @param sourceFile 所在源文件(报错定位用——纯语法遍历路径无 parent 指针,\n * `node.getSourceFile()` 不可用,需显式传入)\n */\nfunction extractConfigFields(\n objLit: ts.ObjectLiteralExpression,\n sourceFile: ts.SourceFile,\n): {\n systemPrompt: string;\n tools?: string[];\n agents?: string[];\n model?: string;\n maxTurns?: number;\n} {\n let systemPrompt: string | undefined;\n let tools: string[] | undefined;\n let agents: string[] | undefined;\n let model: string | undefined;\n let maxTurns: number | undefined;\n\n for (const prop of objLit.properties) {\n // 跳过 SpreadAssignment(...other)\n if (ts.isSpreadAssignment(prop)) continue;\n\n // shorthand / 方法 / getter 等形式——config 只支持 key: value 字面量属性\n if (!ts.isPropertyAssignment(prop)) {\n throw SchemaExtractionError.at(\n prop,\n 'config',\n '仅支持 key: value 属性形式(不支持 shorthand/方法/getter)',\n sourceFile,\n );\n }\n\n const propName = getPropertyName(prop.name);\n if (!propName) {\n throw SchemaExtractionError.at(prop.name, 'config', '不支持 computed 属性名', sourceFile);\n }\n\n switch (propName) {\n case 'systemPrompt':\n systemPrompt = requireStringValue(prop, 'systemPrompt', sourceFile);\n break;\n case 'tools':\n tools = requireStringArrayValue(prop, 'tools', sourceFile);\n break;\n case 'agents':\n agents = requireStringArrayValue(prop, 'agents', sourceFile);\n break;\n case 'model':\n model = requireStringValue(prop, 'model', sourceFile);\n break;\n case 'maxTurns':\n maxTurns = requireNumberValue(prop, 'maxTurns', sourceFile);\n break;\n default:\n // 框架不读的字段几乎必然是拼写错误或误解——静默忽略后运行时按默认值\n // 跑,与声明意图不符(如 maxTurn 拼错后轮数走默认值)\n throw SchemaExtractionError.at(\n prop,\n `config.${propName}`,\n '未知 config 字段——仅支持 systemPrompt / tools / agents / model / maxTurns',\n sourceFile,\n );\n }\n }\n\n if (systemPrompt === undefined) {\n throw SchemaExtractionError.at(\n objLit,\n 'config.systemPrompt',\n 'agent 必填——请在 config 块声明系统提示词(字符串字面量或无插值模板字符串)',\n sourceFile,\n );\n }\n\n return { systemPrompt, tools, agents, model, maxTurns };\n}\n\n/**\n * 从属性名节点提取字符串名\n *\n * 支持:\n * - Identifier(`systemPrompt` → `'systemPrompt'`)\n * - StringLiteral(`'systemPrompt'` → `'systemPrompt'`)\n *\n * ComputedPropertyName / NumericLiteral / PrivateIdentifier → null\n */\nfunction getPropertyName(name: ts.PropertyName): string | null {\n if (ts.isIdentifier(name)) return name.text;\n if (ts.isStringLiteral(name)) return name.text;\n return null;\n}\n\n/**\n * 从表达式提取字符串值(StringLiteral / NoSubstitutionTemplateLiteral)\n *\n * - `'hello'` / `\"hello\"` → `'hello'`\n * - `` `hello` `` → `'hello'`(无插值模板字符串语义等价字符串字面量,多行人设的常见写法)\n * - 含插值模板字符串 / 变量引用 / 数字 → `undefined`\n */\nfunction extractStringValue(expr: ts.Expression): string | undefined {\n if (isStringLikeLiteral(expr)) return expr.text;\n return undefined;\n}\n\n/**\n * 从属性赋值提取字符串字段值,声明了但提取失败抛 `SchemaExtractionError`\n *\n * 与直接用 `extractStringValue` 的区别:失败即抛错(带字段名与 file:line:column),\n * 不静默返回 `undefined`——\"声明了却提取不出\"是确定的构建错误。\n */\nfunction requireStringValue(\n prop: ts.PropertyAssignment,\n fieldName: string,\n sourceFile: ts.SourceFile,\n): string {\n const value = extractStringValue(prop.initializer);\n if (value === undefined) {\n throw SchemaExtractionError.at(\n prop.initializer,\n `config.${fieldName}`,\n '仅支持字符串字面量或无插值模板字符串(含插值的模板字符串/变量引用无法静态求值)',\n sourceFile,\n );\n }\n return value;\n}\n\n/**\n * 从属性赋值提取字符串数组字段值,声明了但提取失败抛 `SchemaExtractionError`\n *\n * 覆盖两种失败形态:值不是数组表达式、数组含非字符串字面量元素。\n */\nfunction requireStringArrayValue(\n prop: ts.PropertyAssignment,\n fieldName: string,\n sourceFile: ts.SourceFile,\n): string[] {\n const value = extractStringArrayValue(prop.initializer);\n if (value === undefined) {\n throw SchemaExtractionError.at(\n prop.initializer,\n `config.${fieldName}`,\n '仅支持全字符串字面量数组(元素为字符串字面量或无插值模板字符串)',\n sourceFile,\n );\n }\n return value;\n}\n\n/**\n * 从属性赋值提取数字字段值,声明了但提取失败抛 `SchemaExtractionError`\n */\nfunction requireNumberValue(\n prop: ts.PropertyAssignment,\n fieldName: string,\n sourceFile: ts.SourceFile,\n): number {\n const value = extractNumberValue(prop.initializer);\n if (value === undefined) {\n throw SchemaExtractionError.at(\n prop.initializer,\n `config.${fieldName}`,\n '仅支持数字字面量',\n sourceFile,\n );\n }\n return value;\n}\n\n/**\n * 从表达式提取数字值(仅 NumericLiteral)\n *\n * - `10` → `10`\n * - `'10'` / 变量引用 → `undefined`\n */\nfunction extractNumberValue(expr: ts.Expression): number | undefined {\n if (ts.isNumericLiteral(expr)) {\n const num = Number(expr.text);\n return Number.isNaN(num) ? undefined : num;\n }\n return undefined;\n}\n\n/**\n * 字符串字面量形态判断(StringLiteral 或无插值模板字符串)\n *\n * 两者 `.text` 均为去引号后的源码文本,语义等价。\n */\nfunction isStringLikeLiteral(\n node: ts.Expression,\n): node is ts.StringLiteral | ts.NoSubstitutionTemplateLiteral {\n return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node);\n}\n\n/**\n * 从表达式提取字符串数组(ArrayLiteralExpression,全字符串字面量元素)\n *\n * - `['a', 'b']` → `['a', 'b']`\n * - `` [`a`, `b`] `` → `['a', 'b']`(无插值模板字符串元素)\n * - `['a', someVar]` → `undefined`(含非字符串字面量元素)\n * - `[]` → `[]`(空数组)\n * - 非数组 → `undefined`\n */\nfunction extractStringArrayValue(expr: ts.Expression): string[] | undefined {\n if (!ts.isArrayLiteralExpression(expr)) return undefined;\n const values: string[] = [];\n for (const element of expr.elements) {\n if (!isStringLikeLiteral(element)) return undefined;\n values.push(element.text);\n }\n return values;\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport { pathToFileURL } from 'node:url';\nimport type { FaapiPlugin, PluginDeclaration } from '../config/pluginTypes';\nimport type { RequestHandler, UpgradeHandler } from '../config/pluginTypes';\nimport { ensureCompiled, isDevOnDemandEnabled } from './compileOnDemand';\nimport { compileProjectModules } from './compileConfig';\n\n/** loadPlugins 的输入上下文(不含 wrap 能力,由 loadPlugins 内部注入) */\ntype LoadPluginsContext = Omit<\n import('../config/pluginTypes').PluginContext,\n 'wrapHandler' | 'wrapUpgradeHandler'\n>;\n\n/** loadPlugins 返回的包装器收集结果 */\nexport interface PluginLoadResult {\n /** 插件注册的 HTTP handler 包装器(按注册顺序) */\n handlerWrappers: Array<(original: RequestHandler) => RequestHandler>;\n /** 插件注册的 WS upgrade handler 包装器(按注册顺序) */\n upgradeWrappers: Array<(original: UpgradeHandler | undefined) => UpgradeHandler>;\n /** 加载失败的插件(specifier + 原因)——调用方据此汇总报告或 fail-fast */\n failures: Array<{ specifier: string; reason: string }>;\n}\n\n/**\n * 加载并执行插件列表\n *\n * 遍历 config.plugins,动态 import 插件包,调用 setup(ctx)。\n * 插件在 server 创建后、listen 之前按声明顺序执行。\n *\n * 插件可通过 ctx.wrapHandler / ctx.wrapUpgradeHandler 注册包装函数,\n * 本函数收集后返回,由调用方在 listen 之前应用。\n *\n * 错误口径(单一语义,不再静默降级):\n * - 单个插件加载/setup 失败不中断其他插件,但失败明细收集进 `failures` 并\n * 在加载完成后统一 `console.error` 汇总——鉴权/CORS 类插件静默丢失等同裸奔,\n * 必须对业务方可见\n * - 非法声明(resolveDeclaration 失败)与其他插件错误同口径收集,不崩启动\n * - `path` 声明相对项目根目录解析(`pathToFileURL(path.resolve(rootDir, path))`),\n * 此前直接 import 会相对 faapi 包自身产物解析,几乎必然失败且报错指向\n * node_modules 深处\n *\n * 本地 TS 插件(`{ path: './plugins/xxx' }`,`.ts` 源码)的加载路径:\n * - 探测源文件(原样 → `.ts`/`.js` → `/index.ts`/`/index.js`),Node ESM 不做\n * 扩展名补全,无扩展名 file URL 直接 import 必然 `Cannot find module`\n * - 产物存在且不比源码旧 → import 产物(build 固化 / dev 已编译产物复用)\n * - `.ts` 源码 + dev 按需模式 → esbuild 逐文件编译到产物目录后 import 产物\n * (src 内插件走 ensureCompiled 打平产物与 routes 同一运行时对象;src 外插件\n * 走 compileProjectModules,含依赖闭包与 specifier 重写——插件内部\n * `import '../src/xxx'` 无扩展名由此生效)\n * - `.ts` 源码 + 非按需模式(prod / 编程式)且产物 stale → 明确报错指引 `faapi build`,\n * 不静默使用旧产物(与 handler 的 prod 语义一致:产物固化,import 失败即报错)\n *\n * @param declarations 插件声明列表\n * @param ctx 插件上下文(不含 wrap 能力,由本函数注入)\n * @param rootDir 项目根目录(`path` 声明解析基准)\n * @param dist 产物目录(dev 为 `.faapi`,prod 为 `dist`——本地 TS 插件按需编译/产物复用基准)\n * @returns 包装器收集结果 + 失败清单\n */\nexport async function loadPlugins(\n declarations: PluginDeclaration[] | undefined,\n ctx: LoadPluginsContext,\n rootDir?: string,\n dist?: string,\n): Promise<PluginLoadResult> {\n const handlerWrappers: Array<(original: RequestHandler) => RequestHandler> = [];\n const upgradeWrappers: Array<(original: UpgradeHandler | undefined) => UpgradeHandler> = [];\n const failures: Array<{ specifier: string; reason: string }> = [];\n\n if (!declarations || declarations.length === 0) {\n return { handlerWrappers, upgradeWrappers, failures };\n }\n\n // 注入 wrap 能力到 ctx\n const fullCtx = {\n ...ctx,\n wrapHandler: (fn: (original: RequestHandler) => RequestHandler) => {\n handlerWrappers.push(fn);\n },\n wrapUpgradeHandler: (fn: (original: UpgradeHandler | undefined) => UpgradeHandler) => {\n upgradeWrappers.push(fn);\n },\n };\n\n const loaded = new Set<string>();\n\n for (const decl of declarations) {\n // 非法声明与其他插件错误同口径:收集进 failures,不崩启动\n let specifier: string;\n let options: unknown;\n let enable: boolean | undefined;\n try {\n ({ specifier, options, enable } = resolveDeclaration(decl));\n } catch (err) {\n failures.push({\n specifier: JSON.stringify(decl),\n reason: err instanceof Error ? err.message : String(err),\n });\n continue;\n }\n\n // enable 检查\n if (enable === false) continue;\n\n // 去重\n if (loaded.has(specifier)) {\n console.warn(`! Plugin already loaded: ${specifier}, skipping`);\n continue;\n }\n loaded.add(specifier);\n\n try {\n const mod = await importPluginModule(specifier, rootDir ?? process.cwd(), dist);\n const plugin: FaapiPlugin = mod.default ?? (mod as unknown as FaapiPlugin);\n\n if (typeof plugin.setup !== 'function') {\n failures.push({ specifier, reason: 'plugin has no setup function' });\n continue;\n }\n\n await plugin.setup({ ...fullCtx, options });\n console.log(`- Plugin loaded: ${plugin.name ?? specifier}`);\n } catch (err) {\n failures.push({\n specifier,\n reason: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n // 失败汇总:单一出口,业务方启动日志一眼可见(鉴权类插件静默丢失不可接受)\n if (failures.length > 0) {\n console.error(\n `[faapi] ${failures.length} plugin(s) failed to load:\\n` +\n failures.map((f) => ` - ${f.specifier}: ${f.reason}`).join('\\n'),\n );\n }\n\n return { handlerWrappers, upgradeWrappers, failures };\n}\n\n/** TS 源文件后缀(需 esbuild 编译才能保证加载) */\nconst TS_EXTS = /\\.(ts|tsx|mts|cts)$/;\n/** JS 源文件后缀(Node ESM 可直接加载) */\nconst JS_EXTS = /\\.(js|mjs|cjs|jsx)$/;\n\n/**\n * 探测本地插件源文件\n *\n * 探测顺序:specifier 原样(已带扩展名)→ `.ts` → `.js` → `/index.ts` → `/index.js`。\n * Node ESM 对 file URL 不做扩展名补全,无扩展名声明必须由框架探测到实际文件。\n *\n * @param specifier 插件声明路径(相对项目根目录或绝对路径)\n * @param baseDir 项目根目录\n * @returns 源文件绝对路径,或 null(未找到)\n */\nexport function resolveLocalPluginSource(specifier: string, baseDir: string): string | null {\n const base = path.isAbsolute(specifier) ? specifier : path.resolve(baseDir, specifier);\n if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs.existsSync(base)) return base;\n for (const ext of ['.ts', '.js']) {\n if (fs.existsSync(base + ext)) return base + ext;\n }\n for (const indexExt of ['/index.ts', '/index.js']) {\n if (fs.existsSync(base + indexExt)) return base + indexExt;\n }\n return null;\n}\n\n/**\n * 从源文件绝对路径推算产物绝对路径\n *\n * - src 内源文件:打平 src/ 前缀(与 compileDevRoutes/compileBuildRoutes 产物同一,\n * 运行时对象同一,instanceof 跨边界生效)\n * - src 外源文件:保留相对 rootDir 的路径结构(compileProjectModules 以 rootDir 为\n * outbase,产物在 `<dist>/` 下保留结构)\n */\nfunction mirrorProductPath(sourcePath: string, baseDir: string, distDir: string): string {\n let rel = path.relative(baseDir, sourcePath).replace(/\\\\/g, '/');\n if (rel.startsWith('src/')) rel = rel.slice(4);\n return path.resolve(distDir, rel.replace(TS_EXTS, '.js'));\n}\n\n/** 插件模块形态:default 导出优先 */\ntype PluginModule = { default?: FaapiPlugin } & Record<string, unknown>;\n\n/** 产物是否比源码旧(stale) */\nfunction isSourceNewer(sourcePath: string, productPath: string): boolean {\n try {\n return fs.statSync(sourcePath).mtimeMs > fs.statSync(productPath).mtimeMs;\n } catch {\n return true;\n }\n}\n\n/**\n * 加载插件模块\n *\n * - 包名(非相对/绝对路径):原样 import,Node 按包解析\n * - 相对/绝对路径:探测源文件与产物,按「产物 fresh → 源码编译/直载」决策,\n * 详见 loadPlugins 注释的「本地 TS 插件的加载路径」\n *\n * @throws 找不到可加载目标或 prod 模式产物 stale 时抛错(带修复指引),进 failures 汇总\n */\nasync function importPluginModule(\n specifier: string,\n baseDir: string,\n dist: string | undefined,\n): Promise<PluginModule> {\n const isRelative = specifier.startsWith('./') || specifier.startsWith('../');\n if (!isRelative && !path.isAbsolute(specifier)) {\n return import(specifier);\n }\n\n const distDir = dist ? path.resolve(baseDir, dist) : null;\n const sourcePath = resolveLocalPluginSource(specifier, baseDir);\n const productPath =\n sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;\n\n // 产物可用:存在 且(源码不存在——部署机只发产物,或 产物不比源码旧)\n if (\n productPath !== null &&\n fs.existsSync(productPath) &&\n (sourcePath === null || !isSourceNewer(sourcePath, productPath))\n ) {\n return import(pathToFileURL(productPath).href);\n }\n\n // TS 源码:dev 按需编译后 import 产物\n if (sourcePath && TS_EXTS.test(sourcePath)) {\n if (!isDevOnDemandEnabled() || !distDir) {\n throw new Error(\n `Local plugin \"${specifier}\" has no up-to-date build artifact (dist is stale or missing). ` +\n `Run \"faapi build\" first, or use \"faapi dev\" for on-demand compilation.`,\n );\n }\n const relFromRoot = path.relative(baseDir, sourcePath).replace(/\\\\/g, '/');\n if (relFromRoot.startsWith('src/')) {\n // src 内插件:打平产物与 routes 同一(同一运行时对象)\n await ensureCompiled(sourcePath, baseDir, dist!);\n } else {\n // src 外插件:编译入口 + 依赖闭包(含插件 import 的 src 内模块)\n await compileProjectModules([sourcePath], baseDir, dist!);\n }\n if (productPath && fs.existsSync(productPath)) {\n return import(pathToFileURL(productPath).href);\n }\n // 编译已完成但产物仍缺失:落到统一报错(理论不可达,防御性兜底)\n }\n\n // JS 源码:Node 可直接加载,无需产物\n if (sourcePath && JS_EXTS.test(sourcePath)) {\n return import(pathToFileURL(sourcePath).href);\n }\n\n // 找不到可加载目标:给明确修复指引(此前仅报 Cannot find module,无法定位原因)\n const candidates = [\n `${specifier}.ts`,\n `${specifier}/index.ts`,\n `${specifier}.js`,\n `${specifier}/index.js`,\n ].map((c) => (path.isAbsolute(c) ? c : path.join(baseDir, c)));\n throw new Error(\n `Cannot find local plugin \"${specifier}\" (resolved from ${baseDir}). ` +\n `Expected one of:\\n ${candidates.join('\\n ')}\\n` +\n `Create the plugin file (export default { name, setup(ctx) {...} }) or fix the path.`,\n );\n}\n\n/**\n * 解析插件声明为统一格式\n */\nfunction resolveDeclaration(decl: PluginDeclaration): {\n specifier: string;\n options?: unknown;\n enable?: boolean;\n} {\n if (typeof decl === 'string') {\n return { specifier: decl };\n }\n\n if (Array.isArray(decl)) {\n const [specifier, options] = decl;\n return { specifier, options };\n }\n\n if ('package' in decl) {\n return { specifier: decl.package, options: decl.options, enable: decl.enable };\n }\n\n if ('path' in decl) {\n return { specifier: decl.path, options: decl.options, enable: decl.enable };\n }\n\n throw new Error(`Invalid plugin declaration: ${JSON.stringify(decl)}`);\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport type { Plugin } from 'esbuild';\nimport { buildAliasPlugins } from './aliasPlugin';\nimport { collectRelativeImports } from './collectImports';\nimport { toProdExtension, toRealPath } from '../utils/prodPaths';\n\n/**\n * 基础配置文件查找顺序(与 loadConfig 保持一致)\n */\nconst BASE_CONFIG_FILES = ['faapi.config.ts', 'faapi.config.js'];\n\n/**\n * 查找基础配置文件(返回源文件名,含后缀)\n *\n * 返回相对 rootDir 的路径(如 'faapi.config.ts'),供 esbuild 作为 entryPoint。\n */\nfunction findBaseConfig(rootDir: string): string | null {\n for (const f of BASE_CONFIG_FILES) {\n if (fs.existsSync(path.join(rootDir, f))) {\n return f;\n }\n }\n return null;\n}\n\n/**\n * 配置文件名 → 产物 import 名(如 'faapi.config.ts' → 'faapi.config.js')\n */\nfunction toProdImport(filename: string): string {\n return toProdExtension(filename);\n}\n\nexport interface CompileConfigOptions {\n /** 项目根目录 */\n rootDir: string;\n /** 输出目录(如 dist) */\n dist: string;\n}\n\nexport interface CompileConfigResult {\n /** 是否生成了配置产物(无基础配置文件时不生成) */\n generated: boolean;\n /** 输出文件绝对路径(generated=false 时为空字符串) */\n outputFile: string;\n /** 是否命中 mtime 短路缓存跳过了编译(watcher 重建场景,输入无变化时省 3 次 esbuild build) */\n skipped: boolean;\n}\n\n/**\n * compileConfig 的 mtime 短路缓存\n *\n * watcher 每次重建都会调 compileConfig;若无短路,即使配置源毫无变化,\n * 也要付出 3 次 esbuild build + collectRelativeImports 递归读整个依赖图的开销。\n *\n * key 为 `rootDir::dist`,value 记录上次编译的全部输入文件 mtime。\n * 再次调用时输入文件集合 mtime 全部一致且产物仍存在 → 跳过编译;\n * 任一变化/缺失 → 全量重编译并更新缓存。编译抛错时不写缓存(下次可重试)。\n */\ninterface CompileConfigCacheEntry {\n /** 上次编译的输入文件(绝对路径 → mtimeMs) */\n inputs: Map<string, number>;\n /** 主产物绝对路径(faapi-config.js) */\n outputFile: string;\n}\n\nconst compileConfigCache = new Map<string, CompileConfigCacheEntry>();\n\n/** 检查缓存是否仍然新鲜:产物存在 + 全部输入文件存在且 mtime 一致 */\nasync function isCacheFresh(entry: CompileConfigCacheEntry): Promise<boolean> {\n try {\n await fs.promises.stat(entry.outputFile);\n } catch {\n return false;\n }\n for (const [filePath, mtimeMs] of entry.inputs) {\n try {\n const stat = await fs.promises.stat(filePath);\n if (stat.mtimeMs !== mtimeMs) return false;\n } catch {\n return false;\n }\n }\n return true;\n}\n\n/**\n * 匹配 from '...' / from \"...\" / import('...') / import(\"...\") 中的 specifier\n * 与递归收集逻辑见 collectImports.ts(compileConfig 与 compileOnDemand 共用)\n */\n\n/**\n * 创建 external 相对路径插件(步骤 2 用)\n *\n * 在 `bundle: true` 模式下,把所有相对路径 import(./xxx 或 ../xxx)标记为 external,\n * 阻止 esbuild 把已编译的 config 产物 inline 进 faapi-config.js。\n *\n * 这使 faapi-config.js 保留 `import base from './faapi.config.js'` 语句,运行时\n * Node.js ESM loader 加载 faapi.config.js,后者再 import 项目模块产物——\n * 与 routes 编译的同一文件共享,instanceof 生效。\n */\nfunction createExternalRelativePlugin(): Plugin {\n return {\n name: 'faapi-external-relative',\n setup(build) {\n // 相对路径(./ 或 ../开头)标记为 external\n build.onResolve({ filter: /^\\.{1,2}\\// }, (args) => ({\n path: args.path,\n external: true,\n }));\n },\n };\n}\n\n/**\n * 编译 src 外入口文件及其项目内依赖闭包(compileConfig / 本地 TS 插件编译共用)\n *\n * - 入口 + src 外依赖:outbase=rootDir,产物在 `<dist>/` 下保留相对结构\n * (如 `plugins/db-skills.ts` → `dist/plugins/db-skills.js`)\n * - src 内依赖:outbase=rootDir/src(打平前缀,与 compileDevRoutes 一致),\n * 使入口产物 import 的 src 内模块与 routes/config 共享同一份产物(instanceof 生效)\n * - aliasPlugin 重写 specifier:相对路径加 .js 后缀;src 外 importer 引用 src 内\n * 模块时剥离前缀(import 路径相对 importer 产物位置计算,见 aliasPlugin 的\n * toProdImportFromImporter)\n *\n * @param entryPoints src 外入口文件(绝对路径);src 内入口请走 compileDevRoutes /\n * compileBuildRoutes / ensureCompiled(打平产物 + 同一运行时对象)\n * @returns 收集到的 src 内/外依赖文件(供调用方记录 mtime 缓存)\n */\nexport async function compileProjectModules(\n entryPoints: string[],\n rootDir: string,\n dist: string,\n): Promise<{ insideFiles: string[]; outsideFiles: string[] }> {\n const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);\n\n const esbuild = await import('esbuild');\n const aliasPlugins = buildAliasPlugins(rootDir);\n const absDist = path.resolve(rootDir, dist);\n\n // src 外文件(outbase=rootDir):入口 + src 外依赖,产物保留相对结构\n await esbuild.build({\n entryPoints: [...entryPoints, ...outsideFiles],\n outdir: absDist,\n outbase: rootDir,\n bundle: false,\n platform: 'node',\n format: 'esm',\n sourcemap: true,\n packages: 'external',\n plugins: aliasPlugins,\n logLevel: 'silent',\n });\n\n // src 内依赖(outbase=rootDir/src,打平前缀,与 compileDevRoutes 一致)\n if (insideFiles.length > 0) {\n const appOutbase = path.resolve(rootDir, 'src');\n await esbuild.build({\n entryPoints: insideFiles,\n outdir: absDist,\n outbase: appOutbase,\n bundle: false,\n platform: 'node',\n format: 'esm',\n sourcemap: true,\n packages: 'external',\n plugins: aliasPlugins,\n logLevel: 'silent',\n });\n }\n\n return { insideFiles, outsideFiles };\n}\n\n/**\n * build 时编译配置文件,生成 `dist/faapi-config.js`\n *\n * 采用两步编译,使 config 引用的项目模块与 routes 共享同一份运行时对象(instanceof 跨边界生效):\n *\n * **步骤 1:逐文件编译 config 源文件(`bundle: false`)**\n * - 编译 `faapi.config.ts` → `dist/faapi.config.js`\n * - 递归收集 config 引用的项目模块,按 src 内/外分别编译:\n * - src 内:outbase=rootDir/src(打平前缀,与 compileDevRoutes 一致)→ `dist/lib/errors.js`\n * - src 外:outbase=rootDir → `dist/base.js`\n * - aliasPlugin 重写 specifier:相对路径加 .js 后缀;config 引用 src 内模块时剥离前缀\n *\n * **步骤 2:编译合并入口(`bundle: true` + external 相对路径)**\n * - 生成虚拟入口源码(import 已编译的 config 产物 + export base)\n * - 相对路径 import 标记为 external(不 inline config 产物)\n * - 第三方依赖(`packages: 'external'`)也保持 external\n * - 产物 `dist/faapi-config.js` 保留 `import base from './faapi.config.js'`\n *\n * 产物由 `loadConfig` 在运行时统一 import。环境变量通过 `.env` 文件加载(见 `loadEnv`),\n * 配置文件中通过 `process.env.XXX` 读取,运行时取值。不传 `define`,保留 `process.env` 表达式。\n */\nexport async function compileConfig(options: CompileConfigOptions): Promise<CompileConfigResult> {\n const { rootDir, dist } = options;\n\n const baseConfigName = findBaseConfig(rootDir);\n if (!baseConfigName) {\n // 无基础配置文件:不生成产物,loadConfig 会返回 null\n return { generated: false, outputFile: '', skipped: false };\n }\n\n const absDist = path.resolve(rootDir, dist);\n await fs.promises.mkdir(absDist, { recursive: true });\n\n const outputFile = path.resolve(absDist, 'faapi-config.js');\n const cacheKey = `${toRealPath(rootDir)}::${dist}`;\n\n // mtime 短路:上次编译的全部输入文件无变化且产物仍存在 → 跳过编译\n const cached = compileConfigCache.get(cacheKey);\n if (cached && (await isCacheFresh(cached))) {\n return { generated: true, outputFile: cached.outputFile, skipped: true };\n }\n compileConfigCache.delete(cacheKey);\n\n // 收集 config 入口文件(绝对路径)\n const configEntryPoints: string[] = [path.resolve(rootDir, baseConfigName)];\n\n // 步骤 1:逐文件编译 config 源 + 项目模块(共享实现见 compileProjectModules)\n const { insideFiles: appDirFiles, outsideFiles: nonAppDirFiles } = await compileProjectModules(\n configEntryPoints,\n rootDir,\n dist,\n );\n\n // 步骤 2:编译入口(bundle:true + external 相对路径)\n // 入口源码 import 已编译的 config 产物(带 .js 后缀)+ export base\n const baseImport = `import base from './${toProdImport(baseConfigName)}';`;\n const exportDefault = 'export default base;';\n\n const entryCode = [baseImport, exportDefault].join('\\n');\n\n const esbuild = await import('esbuild');\n await esbuild.build({\n stdin: { contents: entryCode, resolveDir: absDist, loader: 'ts' },\n outfile: outputFile,\n bundle: true,\n format: 'esm',\n platform: 'node',\n target: 'node20',\n sourcemap: true,\n packages: 'external',\n plugins: [createExternalRelativePlugin()],\n logLevel: 'silent',\n });\n\n // 编译成功:记录全部输入文件的 mtime(config 源 + src 内/外依赖模块 + tsconfig——\n // alias 重写依赖 tsconfig paths,tsconfig 变化必须触发重编译),\n // 供下次调用的 mtime 短路判断。编译抛错时不走此处(下次可重试)。\n const inputs = new Map<string, number>();\n const tsconfigPath = path.resolve(rootDir, 'tsconfig.json');\n const inputCandidates = [\n ...configEntryPoints,\n ...appDirFiles,\n ...nonAppDirFiles,\n ...(fs.existsSync(tsconfigPath) ? [tsconfigPath] : []),\n ];\n for (const filePath of inputCandidates) {\n try {\n const stat = await fs.promises.stat(filePath);\n inputs.set(filePath, stat.mtimeMs);\n } catch {\n // 收集阶段存在的文件理论上不会消失;消失则不缓存该文件,下次触发重编译\n }\n }\n compileConfigCache.set(cacheKey, { inputs, outputFile });\n\n return { generated: true, outputFile, skipped: false };\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 fg from 'fast-glob';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport type { ToolManifestList } from './toolTypes';\n\n/**\n * tool 扫描 patterns(框架约定,非用户可配置项)\n *\n * 与路由的 src/api 扫描对称——tool 文件约定放在 `src/tools` 下任意层级子目录的 handler.ts。\n * 所有 tool 都是共享的,agent 通过 config 块的 `tools` 字段显式声明引用哪些。\n */\nexport const TOOL_PATTERNS = ['src/tools/**/*.ts'];\n\n/**\n * 匹配源码中导出的函数名(任意合法标识符,区别于路由的固定 HTTP 方法名)\n *\n * 支持:\n * - `export function getWeather(input) {}`\n * - `export async function getWeather(input) {}`\n * - `export const getWeather = (input) => {}`\n * - `export const getWeather = async (input) => {}`\n *\n * 不匹配:\n * - `export interface WeatherInput {}`(interface 关键字)\n * - `export type X = ...`(type 关键字)\n * - `export { getWeather }`(命名导出引用,非声明)\n * - `export default function ...`(default 后非 function/const)\n *\n * 不通过运行时 import 提取函数名,避免启动时全量加载 tool 模块(Vite 风格:\n * tool 发现与 tool.js 加载解耦,tool.js 按需编译/导入)。\n */\nconst TOOL_EXPORT_RE = new RegExp(\n String.raw`export\\s+(?:async\\s+)?(?:function\\s+|const\\s+)([A-Za-z_$][\\w$]*)\\s*(?:\\(|=)`,\n 'g',\n);\n\n/**\n * 保留导出名(即使匹配正则也不识别为 tool)\n *\n * - `default` — 默认导出保留\n * - `config` — agent 配置块(scanAgents 使用)\n * - `run` — agent 自定义运行函数(scanAgents 使用)\n */\nconst RESERVED_EXPORTS = new Set(['default', 'config', 'run']);\n\n/**\n * 从源码内容中提取所有 tool 函数导出名(去重 + 排除保留名)\n */\nfunction extractToolExportsFromSource(source: string): Set<string> {\n const names = new Set<string>();\n let match: RegExpExecArray | null;\n TOOL_EXPORT_RE.lastIndex = 0;\n while ((match = TOOL_EXPORT_RE.exec(source)) !== null) {\n const name = match[1]!;\n if (!RESERVED_EXPORTS.has(name)) {\n names.add(name);\n }\n }\n return names;\n}\n\n/**\n * 从相对路径(已剥离前缀)提取命名空间\n *\n * weather/handler.ts → 'weather'\n * handler.ts → ''\n * a/b/handler.ts → 'a.b'\n */\nfunction extractNamespaceFromRelPath(relPath: string): string {\n const lastSlash = relPath.lastIndexOf('/');\n const dirPath = lastSlash === -1 ? '' : relPath.slice(0, lastSlash);\n if (!dirPath) return '';\n return dirPath.split('/').join('.');\n}\n\n/**\n * 从文件路径生成 tool 命名空间(子目录,用 . 连接,不含函数名)\n *\n * 命名空间生成规则:\n * 1. 剥离 `tools/` 前缀(找第一个 `tools/`)\n * 2. 剥离文件名(handler.ts)\n * 3. 剩余路径段用 `.` 连接\n *\n * 例子:\n * - src/tools/weather/handler.ts → 'weather'\n * - src/tools/handler.ts → ''\n * - src/tools/a/b/handler.ts → 'a.b'\n */\nfunction filePathToToolNamespace(filePath: string): string {\n const normalized = filePath.replace(/\\\\/g, '/');\n\n // 剥离 tools/ 前缀(找第一个 tools/)\n const toolsMatch = normalized.match(/(?:^|\\/)tools\\/(.+)$/);\n if (toolsMatch) {\n return extractNamespaceFromRelPath(toolsMatch[1]!);\n }\n\n // 不在 tools/ 下,返回空(不应发生)\n return '';\n}\n\n/**\n * 生成 tool 名:命名空间.函数名 或 纯函数名\n */\nfunction buildToolName(namespace: string, functionName: string): string {\n return namespace ? `${namespace}.${functionName}` : functionName;\n}\n\n/**\n * 扫描 tools 目录,生成 tool 清单\n *\n * Vite 风格:启动时只读源码 + 正则提取函数导出名,不 import tool.js。\n * tool.js 加载延后到 loadToolModule 请求阶段(详见 compileOnDemand)。\n *\n * tool 文件格式:handler.ts,导出任意函数即声明一个 tool(一个文件可多个)。\n * 排除保留导出名:default / config / run(agent 系统保留)。\n *\n * 所有 tool 都是共享的——放在 `src/tools` 下任意层级子目录的 handler.ts。\n * agent 通过 config 块的 `tools` 字段显式声明引用哪些 tool。\n *\n * tool 命名规则:子目录.函数名(如 weather.getWeather),无子目录时纯函数名。\n *\n * 重名检测:同名 tool 抛错。\n *\n * @param rootDir 项目根目录\n * @param patterns glob patterns(源码 .ts 路径,匹配 src/tools 下 tool 文件)\n */\nexport async function scanTools(rootDir: string, patterns: string[]): Promise<ToolManifestList> {\n const files = await fg(patterns, {\n cwd: rootDir,\n onlyFiles: true,\n absolute: false,\n });\n\n const tools: ToolManifestList = [];\n // 重名检测:toolName → filePath\n const seen = new Map<string, string>();\n\n for (const file of files) {\n const normalizedFile = file.replace(/\\\\/g, '/');\n const fileName = normalizedFile.split('/').pop()!;\n\n // 只处理 handler.ts(与路由 handler.ts 对称),其他 .ts 文件跳过\n if (fileName !== 'handler.ts' && fileName !== 'handler.js') {\n continue;\n }\n\n const absPath = path.resolve(rootDir, normalizedFile);\n const source = await fs.promises.readFile(absPath, 'utf8').catch(() => '');\n const exportNames = extractToolExportsFromSource(source);\n\n const namespace = filePathToToolNamespace(normalizedFile);\n\n for (const fnName of exportNames) {\n const toolName = buildToolName(namespace, fnName);\n\n // 重名检测:同名 tool 报错\n const prevFile = seen.get(toolName);\n if (prevFile) {\n throw new Error(\n `Tool conflict: \"${toolName}\" declared in both ${prevFile} and ${normalizedFile}`,\n );\n }\n\n seen.set(toolName, normalizedFile);\n\n tools.push({\n name: toolName,\n functionName: fnName,\n filePath: normalizedFile,\n });\n }\n }\n\n return tools;\n}\n","import fg from 'fast-glob';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport type { AgentManifestList } from './agentTypes';\n\n/**\n * 默认 agent 扫描 patterns\n *\n * 与 tool 扫描([TOOL_PATTERNS](../tools/scanTools.md))对称——\n * agent 定义文件约定放在 `src/agents/<agentName>/handler.ts`(一级目录)。\n *\n * 由 devCommand / buildCommand / createDevApp.reloadAgents(Phase 1.9)共享,\n * 避免多处重复定义。\n */\nexport const DEFAULT_AGENT_PATTERNS = ['src/agents/*/handler.ts'];\n\n/**\n * 检测 `run` 函数导出\n *\n * 匹配:\n * - `export function run() { ... }`\n * - `export async function run() { ... }`\n * - `export const run = () => { ... }`\n * - `export const run = async () => { ... }`\n *\n * 不匹配(`\\b` 词边界保证精确匹配 `run`):\n * - `export const runtime = ...`(run 后跟 `time`)\n *\n * > `config` 导出检测已移除(`hasConfig` 字段已废弃,见 [agentTypes](./agentTypes.md))。\n * > AST 提取阶段 [extractAgentMetadata](../ast/extractAgentMetadata.md) 仍会查找 config\n * > 导出(用于提取 JSDoc 描述 + config 块字段),但运行时不再需要 `hasConfig` 标志。\n */\nconst RUN_EXPORT_RE = /export\\s+(?:async\\s+)?(?:function\\s+|const\\s+)run\\b/;\n\n/**\n * 从文件路径提取 agent 名\n *\n * 匹配 `src/agents/<agentName>/handler.ts` 模式,提取 `<agentName>`。\n *\n * - `src/agents/researcher/handler.ts` → `researcher`\n * - `backup/agents/researcher/handler.ts` → `researcher`(任意前缀,只要匹配 agents/<name>/handler.ts)\n *\n * @throws 路径不匹配 agent 模式时抛错(不应发生——glob pattern 已限制)\n */\nfunction extractAgentNameFromPath(filePath: string): string {\n const normalized = filePath.replace(/\\\\/g, '/');\n const match = normalized.match(/(?:^|\\/)agents\\/([^/]+)\\/handler\\.ts$/);\n if (!match) {\n throw new Error(\n `Not an agent path: \"${filePath}\". Expected pattern: src/agents/<name>/handler.ts`,\n );\n }\n return match[1]!;\n}\n\n/**\n * 从源码检测 `run` 导出是否存在\n *\n * 不 import 模块——正则匹配源码文本(Vite 风格,与 [scanTools](../tools/scanTools.md) 的\n * `extractToolExportsFromSource` 同构)。\n */\nfunction detectAgentExports(source: string): { hasRun: boolean } {\n return {\n hasRun: RUN_EXPORT_RE.test(source),\n };\n}\n\n/**\n * 扫描 agents 目录,生成 agent 清单\n *\n * Vite 风格:启动时只读源码 + 正则检测 `run` 导出,不 import agent.js。\n * agent.js 加载延后到 [loadAgentModule](../loader/loadAgentModule.md) 请求阶段(Phase 1.9)。\n *\n * agent 文件格式:`src/agents/<agentName>/handler.ts`,导出 `config` 块(可选)和\n * `run` 函数(可选)。一个目录一份 handler.ts = 一个 agent。\n *\n * 重名检测:同 agent 名出现在多个文件 → 抛错(agent 名全局唯一,无作用域维度)。\n *\n * @param rootDir 项目根目录\n * @param patterns glob patterns(源码 .ts 路径,匹配 agent handler 文件)\n * @returns `AgentManifestList`\n */\nexport async function scanAgents(rootDir: string, patterns: string[]): Promise<AgentManifestList> {\n const files = await fg(patterns, {\n cwd: rootDir,\n onlyFiles: true,\n absolute: false,\n });\n\n const agents: AgentManifestList = [];\n // 重名检测:agentName → filePath\n const seen = new Map<string, string>();\n\n for (const file of files) {\n const normalizedFile = file.replace(/\\\\/g, '/');\n const fileName = normalizedFile.split('/').pop()!;\n\n // 只处理 handler.ts(与路由/tool handler.ts 对称),其他 .ts 文件跳过\n if (fileName !== 'handler.ts' && fileName !== 'handler.js') {\n continue;\n }\n\n const absPath = path.resolve(rootDir, normalizedFile);\n const source = await fs.promises.readFile(absPath, 'utf8').catch(() => '');\n const { hasRun } = detectAgentExports(source);\n const name = extractAgentNameFromPath(normalizedFile);\n\n // 重名检测:同 agent 名报错\n const prevFile = seen.get(name);\n if (prevFile) {\n throw new Error(\n `Agent conflict: \"${name}\" declared in both ${prevFile} and ${normalizedFile}`,\n );\n }\n seen.set(name, normalizedFile);\n\n agents.push({\n name,\n filePath: normalizedFile,\n hasRun,\n });\n }\n\n return agents;\n}\n","import type { AppBase, CreateAppOptions } from './createAppCore';\nimport { createAppBase, loadAndHydrateTools, loadAndHydrateAgents } from './createAppCore';\nimport { scanRoutes } from '../router/scanRoutes';\nimport { sortRoutes } from '../router/sortRoutes';\nimport { scanTools } from '../tools/scanTools';\nimport { TOOL_PATTERNS } from '../tools/scanTools';\nimport { generateToolArtifacts } from './generateToolArtifacts';\nimport { scanAgents } from '../agents/scanAgents';\nimport { DEFAULT_AGENT_PATTERNS } from '../agents/scanAgents';\nimport { generateAgentArtifacts } from './generateAgentArtifacts';\nimport { invalidateMiddlewareCache } from '../middleware/loadMiddlewares';\nimport { invalidateProgramCache } from '../ast/createProgram';\nimport { invalidateSchemaCache } from '../validator/validateInput';\nimport { setLoadTimestamp } from '../utils/importWithCacheBust';\nimport {\n clearCompiledFiles,\n clearGeneratedSchemas,\n deleteSchemaFiles,\n isDevOnDemandEnabled,\n} from './compileOnDemand';\n\n/** dev 应用接口(AppBase + reloadRoutes/reloadTools/reloadAgents 热替换) */\nexport interface DevApp extends AppBase {\n /** 重新水合路由清单 + 清 schema 缓存 + 更新 server 路由引用(dev 热替换用) */\n reloadRoutes(): Promise<void>;\n /** 重新扫描 tools + 重生成 faapi-tools.js + 清缓存(dev 热替换用) */\n reloadTools(): Promise<void>;\n /** 重新扫描 agents + 重生成 faapi-agents.js + 清缓存(dev 热替换用) */\n reloadAgents(): Promise<void>;\n}\n\n/**\n * dev 模式应用启动 API\n *\n * 在 createAppBase(共享逻辑)基础上增加 `reloadRoutes` 热替换能力,供 `faapi dev` watcher 调用。\n *\n * 与 createProdApp 的区别:\n * - dev:含 reloadRoutes(重新扫描路由 + 重新生成 schema + 清缓存 + 更新 server 路由引用)\n * - prod:精简,无 reloadRoutes(产物已固化,运行时不重建)\n *\n * 由 `devCommand` 直接调用,devCommand 持有 app 引用并传给 watcher。\n *\n * @example\n * ```ts\n * // devCommand 内部\n * const app = await createDevApp();\n * await app.listen();\n * startWatcher({ rootDir, app, devDist });\n * ```\n */\nexport async function createDevApp(options?: CreateAppOptions): Promise<DevApp> {\n const { app, ctx } = await createAppBase(options);\n\n const devApp = app as DevApp;\n\n devApp.reloadRoutes = async (): Promise<void> => {\n // 更新模块加载时间戳(ESM import 绕过缓存)\n setLoadTimestamp(Date.now());\n // 清理缓存(中间件/schema 已被 watcher 重新生成)\n invalidateMiddlewareCache();\n invalidateProgramCache();\n invalidateSchemaCache();\n // 清按需编译缓存(让被修改的 handler.js 重新编译)\n clearCompiledFiles();\n // 重新扫描路由\n // 不走 faapi-routes.js 重新 import——ESM 模块缓存难以可靠绕过,直接 scanRoutes 更稳定\n const reScanned = await scanRoutes(ctx.rootDir, ctx.patterns, ctx.dist);\n const sorted = sortRoutes(reScanned.routes);\n\n if (isDevOnDemandEnabled()) {\n // 按需模式:删除 stale zod.js(类型引用变化等),后台批量预生成 + 请求兜底\n // 不在 reload 路径上 await 生成——保持热替换轻快\n await deleteSchemaFiles(sorted, ctx.rootDir, ctx.dist);\n clearGeneratedSchemas();\n // 后台预生成全量 zod.js:此前只删不生成,每次保存后所有路由的首个请求都要\n // 在请求路径上同步付全项目 Program 创建 + schema 生成的代价(p99 尖刺)。\n // 预生成不阻塞 reload 与请求;请求路径的 ensureSchemaGenerated(mtime 缓存 +\n // in-flight mutex + 原子写)与之自然协同——已生成命中 mtime 跳过,并发生成\n // 各自原子落盘内容一致\n void (async (): Promise<void> => {\n try {\n const { generateSchemaFiles } = await import('./generateSchemaFiles');\n await generateSchemaFiles(sorted, ctx.rootDir, ctx.dist);\n } catch (err) {\n // 后台失败不崩进程——请求路径的按需生成仍会兜底\n console.error('[faapi] Background schema regeneration failed:', err);\n }\n })();\n } else {\n // 非按需模式(兼容旧路径):全量重新生成 zod.js\n const { generateSchemaFiles } = await import('./generateSchemaFiles');\n await generateSchemaFiles(sorted, ctx.rootDir, ctx.dist);\n }\n\n // 更新 app 和 server 路由引用\n ctx.updateRoutes(sorted, reScanned.wsRoutes);\n };\n\n devApp.reloadTools = async (): Promise<void> => {\n // 更新模块加载时间戳(ESM import 绕过缓存,让 faapi-tools.js 重新读取)\n setLoadTimestamp(Date.now());\n // 清 Program 缓存(tool 源码可能变化,AST 需重新分析)\n invalidateProgramCache();\n // 重新扫描 tools(零 import,仅读源码 + 正则提取函数名)\n const tools = await scanTools(ctx.rootDir, TOOL_PATTERNS);\n // 重生成 faapi-tools.js(含 AST 增强:description / inputTypeName)\n // 按需模式跳过 zod.js 生成——首次请求时按需生成(与 reloadRoutes 的策略一致)\n await generateToolArtifacts(tools, ctx.rootDir, ctx.dist, {\n skipSchema: isDevOnDemandEnabled(),\n });\n // 重新水合 faapi-tools.js 到 toolRegistry(reload 后需更新注册表)\n await loadAndHydrateTools(ctx.rootDir, ctx.dist, ctx.registries);\n };\n\n devApp.reloadAgents = async (): Promise<void> => {\n // 更新模块加载时间戳(ESM import 绕过缓存,让 faapi-agents.js 重新读取)\n setLoadTimestamp(Date.now());\n // 清 Program 缓存(agent 源码可能变化,AST 需重新分析)\n invalidateProgramCache();\n // 重新扫描 agents(零 import,仅读源码 + 正则检测 config/run 导出)\n const agents = await scanAgents(ctx.rootDir, DEFAULT_AGENT_PATTERNS);\n // 重生成 faapi-agents.js(含 AST 增强:description / @agent 覆盖 / config 块字段)\n // agent 不生成 zod.js(无输入参数),无 skipSchema 选项\n await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);\n // 重新水合 faapi-agents.js 到 agentRegistry(reload 后需更新注册表)\n await loadAndHydrateAgents(ctx.rootDir, ctx.dist, ctx.registries);\n };\n\n return devApp;\n}\n","import { createAppBase } from './createAppCore';\nimport type { AppBase, CreateAppOptions } from './createAppCore';\n\n/** prod 应用接口(AppBase,无 reloadRoutes) */\nexport type ProdApp = AppBase;\n\n// 重新导出 CreateAppOptions 供 createApp.ts 别名使用\nexport type { CreateAppOptions };\n\n/**\n * prod 模式应用启动 API\n *\n * 直接返回 createAppBase 结果(共享逻辑),不含 dev 专用能力(reloadRoutes、缓存失效)。\n * 产物在 `faapi build` 阶段已固化,运行时不重建。\n *\n * 框架采用零入口设计——用户无需编写 main.ts:\n * - `faapi build` 自动生成 `dist/main.js` 启动入口,内部调用 `createProdApp()` + `listen()` 启动生产服务器\n * - 用户自定义启动逻辑通过 `faapi.config.ts` 的 `lifecycle.onReady` / `onClose` 钩子实现\n *\n * 编程式调用场景(如自定义 CLI 启动器)也可直接调用:\n *\n * @example\n * ```ts\n * import { createProdApp } from '@faapi/faapi';\n * const app = await createProdApp();\n * await app.listen();\n * ```\n */\nexport async function createProdApp(options?: CreateAppOptions): Promise<ProdApp> {\n const { app } = await createAppBase(options);\n return app;\n}\n"],"mappings":"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","names":["ts","ts","ts","ts","path","path","fs","unionInner","path","getAgent","fs","ts","url","path","fs","path","fs","path","fs","path","fs","ts","path","fs","path","fs","SPEC_RE","path","fs","path","fs","path","fs","fs","inFlight","fs","path","fs","fs","fs","existsSync","path","ts","ts","ts","path","existsSync","path","fs","path","fs","fs","path","path","fs","path","Readable","Readable","path","path","params","body","url","text","headers","body","path","headers","DEFAULTS","etag","fs","path","fs","path","Readable","path","fs","path","path","ts","ts","path","path","fs","pathToFileURL","path","fs","path","path","fs","pathToFileURL","TOOLS_FILE","AGENTS_FILE","path","fs","body","Readable","fg","path","fs","path","path","fs","fg","fg","path","fs","fg","path","fs","generateSchemaFiles"]}
|
|
1
|
+
{"version":3,"sources":["../src/ast/createProgram.ts","../src/ast/resolveTypeNode.ts","../src/ast/extractHandlerTypes.ts","../src/runtime/inputType.ts","../src/validator/schemaName.ts","../src/injection/resolveInjection.ts","../src/injection/analyzeInjection.ts","../src/cli/collectRouteSchemaSources.ts","../src/utils/atomicWrite.ts","../src/ast/generateZodSchema.ts","../src/cli/generateSchemaFiles.ts","../src/index.ts","../src/injection/registries.ts","../src/injection/agentRegistry.ts","../src/injection/toolRegistry.ts","../src/injection/skillRegistry.ts","../src/loader/loadAgentModule.ts","../src/loader/resolveExports.ts","../src/utils/importWithCacheBust.ts","../src/cli/compileOnDemand.ts","../src/cli/compileSourceFiles.ts","../src/cli/aliasPlugin.ts","../src/utils/resolveAlias.ts","../src/utils/prodPaths.ts","../src/utils/readTsconfig.ts","../src/cli/compileDevRoutes.ts","../src/cli/collectImports.ts","../src/loader/loadToolModule.ts","../src/loader/loadToolSchema.ts","../src/cli/generateToolArtifacts.ts","../src/ast/extractToolMetadata.ts","../src/ast/jsDocMetadata.ts","../src/injection/agentHandle.ts","../src/middleware/cors.ts","../src/middleware/logger.ts","../src/middleware/helmet.ts","../src/config/loadConfig.ts","../src/cli/loadEnv.ts","../src/errors/FaapiError.ts","../src/errors/httpErrors.ts","../src/cli/createAppCore.ts","../src/router/sortRoutes.ts","../src/router/detectRouteConflicts.ts","../src/server/createServer.ts","../src/router/matchRoute.ts","../src/loader/validateRouteModule.ts","../src/loader/loadRouteModule.ts","../src/runtime/sse.ts","../src/response/responseFormatter.ts","../src/runtime/createContext.ts","../src/utils/queryToObject.ts","../src/utils/parseJsonBody.ts","../src/utils/parseMultipart.ts","../src/runtime/resolveInput.ts","../src/utils/isPlainObject.ts","../src/response/pendingMeta.ts","../src/response/toResponse.ts","../src/injection/injectParams.ts","../src/runtime/invokeHandler.ts","../src/response/sendNodeResponse.ts","../src/validator/validateInput.ts","../src/utils/getClientIp.ts","../src/middleware/compression.ts","../src/middleware/etag.ts","../src/server/handleWsUpgrade.ts","../src/server/serverUtils.ts","../src/middleware/loadMiddlewares.ts","../src/runtime/wsHandler.ts","../src/server/startServer.ts","../src/cli/generateRoutes.ts","../src/cli/generateAgentArtifacts.ts","../src/ast/extractAgentMetadata.ts","../src/cli/loadPlugins.ts","../src/cli/compileConfig.ts","../src/router/scanRoutes.ts","../src/router/constants.ts","../src/utils/normalizePath.ts","../src/router/parseRouteFile.ts","../src/tools/scanTools.ts","../src/agents/scanAgents.ts","../src/cli/createDevApp.ts","../src/cli/createProdApp.ts"],"sourcesContent":["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';\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","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","/**\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';\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 | 'unknown';\n\n/**\n * 注入项信息\n */\nexport interface InjectionItem {\n name: string; // 参数名\n type: InjectionType; // 注入类型\n hasType: boolean; // 是否有类型标注(运行时类型已擦除,始终为 false)\n}\n\n/**\n * 参数名到注入类型的映射(单一来源,构建期和运行时共用)\n *\n * `form` 与 `body` 互斥:handler 声明其一即可。`form` 适用于\n * `application/x-www-form-urlencoded` 请求体(值均为 string,schema coerce=true);\n * `body` 适用于 JSON 请求体(coerce=false)。两者共享 `resolveInput` 的解析结果。\n *\n * Phase 2.3 添加 `agent` / `agents`:\n * - `agent` → 注入默认 agent 元数据(Phase 2.4 实现,暂返回 `undefined`)\n * - `agents` → 注入所有已注册 agent 元数据列表(`agentRegistry.listAgents()`)\n *\n * Phase 3.x 的 `@faapi/agent` 插件通过注入器机制增强为 `AgentHandle`\n * (含可调用 `run` 函数),覆盖内置的元数据注入。\n */\nexport const PARAM_TYPE_MAP: Record<string, InjectionType> = {\n query: 'query',\n body: 'body',\n form: 'form',\n headers: 'headers',\n params: 'params',\n context: 'context',\n ctx: 'context', // 别名\n cookies: 'cookies',\n ip: 'ip',\n ua: 'ua',\n files: 'files',\n fields: 'fields',\n agent: 'agent', // Phase 2.3\n agents: 'agents', // Phase 2.3\n};\n\n/**\n * 分析结果按函数引用缓存。\n *\n * handler 函数在路由模块加载后引用稳定,参数列表不会变化;用 WeakMap 弱键,\n * dev 热替换后旧模块的函数不再被引用即可随 GC 回收,不需要手动失效。\n */\ntype AnyFn = (...args: unknown[]) => unknown;\nconst injectionCache = new WeakMap<AnyFn, InjectionItem[]>();\n\n/**\n * 分析函数参数,决定需要注入什么内容\n *\n * 使用 TypeScript AST 解析 fn.toString() 的结果,\n * 正确处理解构参数、默认值、rest 参数等情况。\n *\n * 注意:运行时类型信息已被擦除,hasType 始终为 false。\n *\n * 返回的数组是缓存的共享实例,调用方不得就地修改。\n */\nexport function resolveInjection(fn: (...args: unknown[]) => unknown): InjectionItem[] {\n const cached = injectionCache.get(fn);\n if (cached) {\n return cached;\n }\n\n const fnStr = fn.toString();\n const params = extractParamsWithAst(fnStr);\n\n const items = params.map((param) => {\n const type = PARAM_TYPE_MAP[param.name] || 'unknown';\n return {\n name: param.name,\n type,\n hasType: false, // 运行时类型已擦除\n };\n });\n\n injectionCache.set(fn, items);\n return items;\n}\n\n/**\n * 使用 TypeScript AST 从函数字符串中提取参数名\n *\n * 相比正则解析,AST 能正确处理:\n * - 解构参数 `function GET({ page, size })` → page, size\n * - 默认值 `function GET(query = {})` → query\n * - rest 参数 `function GET(...args)` → args\n * - 含逗号的泛型(类型擦除后不存在该问题)\n */\nfunction extractParamsWithAst(fnStr: string): Array<{ name: string }> {\n // 包装成模块,让 TS 能解析\n const sourceFile = ts.createSourceFile(\n '__faapi_injection__.ts',\n fnStr,\n ts.ScriptTarget.Latest,\n true,\n );\n\n const paramNames: string[] = [];\n\n function visit(node: ts.Node): void {\n // 函数声明:function GET(query) {}\n if (ts.isFunctionDeclaration(node) && node.parameters.length > 0) {\n for (const param of node.parameters) {\n extractParamName(param, paramNames);\n }\n return;\n }\n\n // 箭头函数 / 函数表达式:(query) => {} 或 function(query) {}\n if ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && node.parameters.length > 0) {\n for (const param of node.parameters) {\n extractParamName(param, paramNames);\n }\n return;\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n\n return paramNames.map((name) => ({ name }));\n}\n\n/**\n * 从单个参数节点提取参数名\n *\n * 支持的参数形式:\n * - 标识符:query → query\n * - 解构对象:{ page, size } → page, size(每个属性都作为一个注入项)\n * - 解构数组:[a, b] → a, b\n * - rest 参数:...args → args\n * - 默认值:query = {} → query\n */\nfunction extractParamName(param: ts.ParameterDeclaration, names: string[]): void {\n const name = param.name;\n\n // 标识符:query\n if (ts.isIdentifier(name)) {\n names.push(name.text);\n return;\n }\n\n // 解构对象:{ page, size }\n if (ts.isObjectBindingPattern(name)) {\n for (const element of name.elements) {\n if (ts.isBindingElement(element)) {\n const elemName = element.name;\n if (ts.isIdentifier(elemName)) {\n names.push(elemName.text);\n }\n }\n }\n return;\n }\n\n // 解构数组:[a, b]\n if (ts.isArrayBindingPattern(name)) {\n for (const element of name.elements) {\n if (element && ts.isBindingElement(element)) {\n const elemName = element.name;\n if (ts.isIdentifier(elemName)) {\n names.push(elemName.text);\n }\n }\n }\n return;\n }\n}\n","import ts from 'typescript';\nimport type { InjectionType } from './resolveInjection';\nimport { PARAM_TYPE_MAP } from './resolveInjection';\n\n/**\n * 参数元数据\n */\nexport interface ParamMeta {\n name: string; // 参数名\n type: InjectionType; // 注入类型\n typeName?: string; // 引用类型名(如 'Query')\n schema?: PropertyInfo[]; // 类型结构(内联类型时)\n}\n\n/**\n * 属性信息\n */\nexport interface PropertyInfo {\n name: string;\n type: string;\n optional: boolean;\n}\n\n/**\n * 注入元数据\n */\nexport interface InjectionMeta {\n params: ParamMeta[];\n}\n\n/**\n * AST 分析 handler 函数,提取参数注入元数据\n *\n * @param code handler 源码字符串\n * @param functionName 函数导出名(如 'GET')\n */\nexport function analyzeInjection(code: string, functionName: string): InjectionMeta {\n const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);\n return analyzeInjectionInSourceFile(sourceFile, functionName);\n}\n\n/**\n * 从已有 SourceFile 分析 handler 函数的参数注入元数据\n *\n * 与 analyzeInjection 语义一致,但接受 AST 节点而非源码字符串——\n * 调用方(如 collectRouteSchemaSources)的 program 中已有解析好的 SourceFile,\n * 逐方法重新 parse 同一文件字符串是纯浪费(同文件 N 个方法 = N 次全量重复解析)。\n *\n * @param sourceFile 已解析的 AST\n * @param functionName 函数导出名(如 'GET')\n */\nexport function analyzeInjectionInSourceFile(\n sourceFile: ts.SourceFile,\n functionName: string,\n): InjectionMeta {\n const params: ParamMeta[] = [];\n\n // 遍历 AST 查找目标函数\n ts.forEachChild(sourceFile, (node) => {\n if (ts.isFunctionDeclaration(node) && node.name?.text === functionName) {\n // 分析参数\n for (const param of node.parameters) {\n const paramMeta = analyzeParam(param, sourceFile);\n params.push(paramMeta);\n }\n }\n });\n\n return { params };\n}\n\n/**\n * 分析单个参数\n */\nfunction analyzeParam(param: ts.ParameterDeclaration, sourceFile: ts.SourceFile): ParamMeta {\n const name = param.name.getText(sourceFile);\n const type = PARAM_TYPE_MAP[name] || 'unknown';\n\n const result: ParamMeta = { name, type };\n\n // 分析类型\n if (param.type) {\n if (ts.isTypeReferenceNode(param.type)) {\n // 引用类型:Query, Body 等\n result.typeName = param.type.typeName.getText(sourceFile);\n } else if (ts.isTypeLiteralNode(param.type)) {\n // 内联类型:{ page: number }\n result.schema = extractSchema(param.type, sourceFile);\n }\n }\n\n return result;\n}\n\n/**\n * 从类型字面量提取 schema\n */\nfunction extractSchema(typeNode: ts.TypeLiteralNode, sourceFile: ts.SourceFile): PropertyInfo[] {\n const schema: PropertyInfo[] = [];\n\n for (const member of typeNode.members) {\n if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {\n const propName = member.name.text;\n const optional = !!member.questionToken;\n const propType = member.type?.getText(sourceFile) || 'unknown';\n\n schema.push({\n name: propName,\n type: propType,\n optional,\n });\n }\n }\n\n return schema;\n}\n","import { createPrograms } from '../ast/createProgram';\nimport {\n createLazyTypeResolver,\n type HandlerTypeInfo,\n type LazyTypeResolver,\n} from '../ast/extractHandlerTypes';\nimport { getInputTypeForMethod } from '../runtime/inputType';\nimport { getSchemaName } from '../validator/schemaName';\nimport { analyzeInjectionInSourceFile } from '../injection/analyzeInjection';\nimport type { RouteManifest } from '../router/routeTypes';\nimport path from 'node:path';\n\n/**\n * 单个路由的 schema 提取结果\n *\n * key 使用 urlPath(如 '/api/hello')而非 filePath,因为 urlPath 在 dev/prod 完全一致,\n * 无需 remapManifestKeys 桥接 .ts/.js 路径差异。\n */\nexport interface RouteSchemaSource {\n /** 路由 URL 路径(如 '/api/hello'),作为 schema key */\n urlPath: string;\n /** 源文件绝对路径(用于 generateSchemaFiles 按文件分组生成 zod.js) */\n filePath: string;\n schemaName: string;\n typeInfo: HandlerTypeInfo | null;\n /**\n * 是否对 number/boolean 字段生成 z.preprocess 字符串转换(coerce)。\n *\n * - query/params:始终 coerce=true(URL 来源均为 string)\n * - body:始终 coerce=false(JSON 解析已是天然 JS 类型)\n * - form:coerce=true(form-urlencoded 来源均为 string),由本函数在提取时\n * 检测到 handler 声明 `form` 参数时显式设置。schema 名仍为 `POSTBody`\n * (与 body 共享运行时 schema key),运行时 validateInput 无需感知 form/body 差异。\n *\n * 未设置时由 generateSchemaFileSource 回退到 schemaName 后缀正则推断(Query/Params → true)。\n */\n coerce?: boolean;\n}\n\n/**\n * 从路由清单收集 schema 提取所需的原始数据\n *\n * dev 和 prd 共享的核心提取流程:\n * 1. 按文件分组遍历路由\n * 2. 批量共享 Program(同一次提取只按文件组创建少量 Program)\n * 3. 对每个路由用 analyzeInjectionInSourceFile 定位入口参数类型,\n * extractTypeInfo 解析入口类型(入口类型必须严格解析,失败抛 SchemaExtractionError)\n * 4. 返回按文件分组的惰性类型解析器——generateSchemaFiles 生成 zod.js 时\n * 遇到 ref(同文件循环引用)按需解析并缓存\n *\n * 惰性语义:与路由无关的类型(未被任何入口类型引用)不会被解析——文件里\n * 存在一个含不支持语法的无关类型不再拖垮整个 build/reload。\n */\nexport function collectRouteSchemaSources(\n routes: RouteManifest,\n rootDir?: string,\n): {\n sources: RouteSchemaSource[];\n /** 按文件分组的惰性类型解析器(generateSchemaFiles 解析 ref 用) */\n resolversByFile: Map<string, LazyTypeResolver>;\n} {\n // 按文件分组收集方法(去重)\n // key 是文件绝对路径(createProgram 需要),但 schema key 用 urlPath\n const methodsByFile = new Map<string, { urlPath: string; methods: Set<string> }>();\n for (const route of routes) {\n const filePath = rootDir ? path.resolve(rootDir, route.filePath) : route.filePath;\n let entry = methodsByFile.get(filePath);\n if (!entry) {\n entry = { urlPath: route.urlPath, methods: new Set() };\n methodsByFile.set(filePath, entry);\n }\n entry.methods.add(route.method);\n }\n\n // 批量共享 Program:同一次提取只创建一个 Program,避免逐文件全量解析\n const programByFile = createPrograms([...methodsByFile.keys()]);\n\n // 每个文件一个惰性解析器:入口类型立即解析,ref 按需解析(缓存幂等)\n const resolversByFile = new Map<string, LazyTypeResolver>();\n for (const filePath of methodsByFile.keys()) {\n resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath)!, filePath));\n }\n\n // 每个文件提取 schema(key 用 urlPath)\n const sources: RouteSchemaSource[] = [];\n for (const [filePath, entry] of methodsByFile) {\n const program = programByFile.get(filePath)!;\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) continue;\n const resolver = resolversByFile.get(filePath)!;\n for (const method of entry.methods) {\n const inputType = getInputTypeForMethod(method);\n const schemaName = getSchemaName(method, inputType);\n // 复用 program 已解析的 SourceFile,逐方法零重复 parse\n const meta = analyzeInjectionInSourceFile(sourceFile, method);\n // POST/PUT/PATCH(inputType='body'):优先找 body 参数,找不到再找 form 参数。\n // form 与 body 共享 schema 名(POSTBody),运行时 validateInput 仍按 POSTBodySchema 查找;\n // 差异仅在校验:form 声明时通过 source.coerce=true 显式覆盖(form 值均为 string,\n // 需 z.preprocess 转换 number/boolean 字段)。\n const param =\n meta.params.find((p) => p.type === inputType) ??\n (inputType === 'body' ? meta.params.find((p) => p.type === 'form') : undefined);\n const isForm = param?.type === 'form';\n // 入口类型:必须严格解析(失败抛 SchemaExtractionError,带文件/类型上下文)\n const typeInfo = param?.typeName ? resolver.resolve(param.typeName) : null;\n sources.push({\n urlPath: entry.urlPath,\n filePath,\n schemaName,\n typeInfo,\n coerce: isForm || undefined,\n });\n }\n }\n\n return { sources, resolversByFile };\n}\n","import 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 {\n SchemaExtractionError,\n type RuntimeType,\n type TupleElement,\n type TypeConstraint,\n} from './resolveTypeNode';\nimport type { HandlerTypeInfo } from './extractHandlerTypes';\n\n/**\n * 类型解析器:根据类型名获取 RuntimeType\n *\n * 用于解析循环引用中的 ref。调用方负责提供(通常从 AST 提取)。\n */\nexport type TypeResolver = (name: string) => RuntimeType | undefined;\n\n/**\n * 代码生成上下文\n *\n * 跟踪已收集的命名类型,用于生成 z.lazy 声明。\n */\nclass CodeGenContext {\n /** 命名类型集合:name → RuntimeType */\n readonly namedTypes = new Map<string, RuntimeType>();\n /** 类型解析器(用于解析 ref 的实际类型) */\n readonly resolveType: TypeResolver;\n /** 入口类型原始名(typeInfo.name,用于识别入口类型的自引用) */\n entryTypeName = '';\n /** 入口类型导出名(exportName,自引用时用此名生成变量名) */\n entryExportName = '';\n /**\n * 是否生成 coerce 逻辑(query/params 场景,URL 来源均为 string)\n *\n * true 时为 number/boolean 字段包 z.preprocess,把合法的字符串转成对应类型。\n * 嵌套类型(array/object/tuple/union 等)的元素递归处理。\n */\n coerce = false;\n\n constructor(resolveType: TypeResolver) {\n this.resolveType = resolveType;\n }\n}\n\n/**\n * 从 RuntimeType 收集所有命名类型(ref)\n *\n * 递归遍历 RuntimeType,遇到 ref 时解析并加入 namedTypes。\n * 已收集的类型不重复处理(防止循环引用无限递归)。\n */\nexport function collectNamedTypes(type: RuntimeType, ctx: CodeGenContext): void {\n switch (type.kind) {\n case 'string':\n case 'number':\n case 'boolean':\n case 'bigint':\n case 'null':\n case 'undefined':\n case 'any':\n case 'unknown':\n case 'literal':\n case 'date':\n return;\n\n case 'array':\n collectNamedTypes(type.element, ctx);\n return;\n\n case 'tuple':\n for (const el of type.elements) {\n collectNamedTypes(el.type, ctx);\n }\n return;\n\n case 'object':\n for (const prop of type.properties) {\n collectNamedTypes(prop.type, ctx);\n }\n return;\n\n case 'union':\n for (const member of type.members) {\n collectNamedTypes(member, ctx);\n }\n return;\n\n case 'record':\n collectNamedTypes(type.key, ctx);\n collectNamedTypes(type.value, ctx);\n return;\n\n case 'map':\n collectNamedTypes(type.key, ctx);\n collectNamedTypes(type.value, ctx);\n return;\n\n case 'set':\n collectNamedTypes(type.element, ctx);\n return;\n\n case 'ref': {\n if (ctx.namedTypes.has(type.name)) return;\n // 先占位,防止循环引用无限递归\n ctx.namedTypes.set(type.name, { kind: 'any' });\n const resolved = ctx.resolveType(type.name);\n if (resolved) {\n ctx.namedTypes.set(type.name, resolved);\n collectNamedTypes(resolved, ctx);\n return;\n }\n // ref 只在 AST 提取阶段遇到真实 TS 类型引用时产生(首现内联、二次标 ref),\n // 声明必然存在于某个源文件中;解析不到说明查找能力不足(如命名空间内声明),\n // 按框架约定显式抛错而非静默降级为 z.unknown()(AGENTS.md §6.3)\n throw new SchemaExtractionError(\n type.name,\n `无法解析命名类型引用 \"${type.name}\"(handler 文件与 program 源文件中均未找到同名顶层声明)`,\n );\n }\n }\n}\n\n/**\n * RuntimeType → zod 表达式字符串(不含声明)\n *\n * @param type 类型描述\n * @param ctx 代码生成上下文\n * @param constraints 字段级约束(仅 object 字段传入,影响最外层 zod 链)\n */\nexport function runtimeTypeToZodExpression(\n type: RuntimeType,\n ctx: CodeGenContext,\n constraints?: TypeConstraint[],\n): string {\n const expr = baseExpression(type, ctx);\n // 字段级约束链(@max/@min/@regex 等)追加到基础表达式后\n // coerce 模式下约束需作用在 preprocess 内部的 z.X() 上,故先应用约束再包裹 preprocess\n const withConstraints =\n constraints && constraints.length > 0 ? applyConstraints(expr, constraints, type.kind) : expr;\n // coerce 模式下,number/boolean 在外层包 z.preprocess\n // 嵌套类型(array/object/tuple/union 等)内部已递归处理,外层不再包裹\n if (ctx.coerce && (type.kind === 'number' || type.kind === 'boolean')) {\n return wrapCoercePreprocess(type.kind, withConstraints);\n }\n // coerce 模式下,数字/布尔字面量同样需要字符串转换:query 声明 `status: 1 | 2`\n // 时 URL 传来 \"1\"(string),裸 z.literal(1) 必然校验失败。union 成员经本函数\n // 递归生成,字面量成员逐个包裹(string 字面量天然命中,无需转换)\n if (\n ctx.coerce &&\n type.kind === 'literal' &&\n (typeof type.value === 'number' || typeof type.value === 'boolean')\n ) {\n return wrapCoercePreprocess(\n typeof type.value === 'number' ? 'number' : 'boolean',\n withConstraints,\n );\n }\n return withConstraints;\n}\n\n/**\n * 在 zod 表达式上追加约束链\n *\n * 约束与字段类型的匹配在 AST 提取阶段已校验,这里直接生成链式调用。\n *\n * coerce 模式下调用方应先 applyConstraints 再 wrapCoercePreprocess,\n * 使约束作用在 preprocess 内部的 z.X() 上,而非 preprocess 外壳。\n *\n * @param baseExpr 基础 zod 表达式(未包裹 preprocess)\n * @param constraints 约束数组\n * @param typeKind 字段类型 kind\n */\nfunction applyConstraints(\n baseExpr: string,\n constraints: TypeConstraint[],\n typeKind: RuntimeType['kind'],\n): string {\n const suffix = constraints.map((c) => constraintToZodChain(c, typeKind)).join('');\n return `${baseExpr}${suffix}`;\n}\n\n/**\n * 单个约束 → zod 链式方法字符串\n *\n * @param constraint 约束\n * @param typeKind 字段类型 kind(用于区分 string/array 的 max/min 含义)\n */\nfunction constraintToZodChain(constraint: TypeConstraint, _typeKind: RuntimeType['kind']): string {\n switch (constraint.kind) {\n case 'max':\n return `.max(${constraint.value})`;\n case 'min':\n return `.min(${constraint.value})`;\n case 'int':\n return '.int()';\n case 'positive':\n return '.positive()';\n case 'negative':\n return '.negative()';\n case 'nonnegative':\n return '.nonnegative()';\n case 'nonpositive':\n return '.nonpositive()';\n case 'maxLength':\n return `.max(${constraint.value})`;\n case 'minLength':\n return `.min(${constraint.value})`;\n case 'length':\n return `.length(${constraint.value})`;\n case 'regex': {\n const flags = constraint.flags ?? '';\n // 用 new RegExp 构造,避免 pattern 中含 / 字符导致的字面量语法错误\n return `.regex(new RegExp(${JSON.stringify(constraint.pattern)}${flags ? `, ${JSON.stringify(flags)}` : ''}))`;\n }\n case 'email':\n return '.email()';\n case 'url':\n return '.url()';\n case 'uuid':\n return '.uuid()';\n }\n}\n\n/**\n * 生成基础 zod 表达式(不含 coerce 包裹)\n */\nfunction baseExpression(type: RuntimeType, ctx: CodeGenContext): string {\n switch (type.kind) {\n case 'string':\n return 'z.string()';\n case 'number':\n return 'z.number()';\n case 'boolean':\n return 'z.boolean()';\n case 'bigint':\n // AST 提取阶段已抛错,这里不会到达\n return 'z.never()';\n case 'null':\n return 'z.null()';\n case 'undefined':\n return 'z.undefined()';\n case 'any':\n case 'unknown':\n return 'z.unknown()';\n case 'literal':\n return `z.literal(${JSON.stringify(type.value)})`;\n case 'array':\n return `z.array(${runtimeTypeToZodExpression(type.element, ctx)})`;\n case 'tuple':\n return generateTupleExpression(type.elements, ctx);\n case 'object':\n return generateObjectExpression(type, ctx);\n case 'union':\n return generateUnionExpression(type.members, ctx);\n case 'date':\n // z.preprocess 限制只接受 string/Date:string → new Date(string),其他原样传入\n // z.coerce.date() 会把 number 也转为 Date(new Date(123)),不符合需求\n // date 类型本身已用 preprocess,coerce 模式下无需再包一层\n return 'z.preprocess((v) => (typeof v === \"string\" ? new Date(v) : v), z.date())';\n case 'record':\n return `z.record(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)})`;\n case 'map':\n // Map<K,V> — JSON 序列化为 entries 数组,coerceMap 还原为 Map 实例后再交给 z.map 校验\n // coerce=false(body)场景也需要 preprocess:JSON.parse 出来是数组,不是 Map 实例\n return `z.preprocess(coerceMap, z.map(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)}))`;\n case 'set':\n // Set<T> — JSON 序列化为数组,coerceSet 还原为 Set 实例后再交给 z.set 校验\n // coerce=false(body)场景也需要 preprocess:JSON.parse 出来是数组,不是 Set 实例\n return `z.preprocess(coerceSet, z.set(${runtimeTypeToZodExpression(type.element, ctx)}))`;\n case 'ref':\n // ref 引用命名类型,直接用 NameSchema 变量\n // 入口类型的自引用需用 exportName(exportName 可能与 typeInfo.name 不同)\n if (type.name === ctx.entryTypeName) {\n return `${ctx.entryExportName}Schema`;\n }\n return `${type.name}Schema`;\n }\n}\n\n/**\n * coerceNumber 公用函数源码(ESM export 格式)\n *\n * 用于 query/params 的 string → number 转换:\n * - 合法数字字符串(\"1\"、\"3.14\")转为 number\n * - 空串/NaN 保留原值,让 z.number() 报错(避免 Number(\"\") = 0 的陷阱)\n *\n * 写入 dist 根部的 faapi-helpers.js,各 zod.js 通过相对路径 import 复用。\n */\nexport const COERCE_NUMBER_HELPER =\n 'export const coerceNumber = (v) => typeof v === \"string\" && v.trim() !== \"\" && !isNaN(Number(v)) ? Number(v) : v;';\n\n/**\n * coerceBoolean 公用函数源码(ESM export 格式)\n *\n * 用于 query/params 的 string → boolean 转换:\n * - 'true'/'1' → true\n * - 'false'/'0' → false\n * - 其他值保留原值,让 z.boolean() 报错\n *\n * 写入 dist 根部的 faapi-helpers.js,各 zod.js 通过相对路径 import 复用。\n */\nexport const COERCE_BOOLEAN_HELPER =\n 'export const coerceBoolean = (v) => {\\n' +\n ' const lower = typeof v === \"string\" ? v.toLowerCase() : v;\\n' +\n ' return lower === \"true\" || lower === \"1\" ? true : lower === \"false\" || lower === \"0\" ? false : v;\\n' +\n '};';\n\n/**\n * coerceMap 公用函数源码(ESM export 格式)\n *\n * 用于 Map<K,V> 的 JSON 还原:JSON 序列化为 entries 数组,JSON.parse 后还原为 Map 实例\n * - entries 数组 [[\"k\",v],...] → new Map(entries)\n * - Map 实例 → 原样返回\n * - 普通对象 { k: v } → new Map(Object.entries(v))\n * - 其他值保留原值,让 z.map() 报错\n *\n * 与 coerceNumber/coerceBoolean 不同,coerceMap 在 coerce=false(body 场景)下也使用:\n * JSON.parse 出来的是数组/对象,不是 Map 实例,必须还原才能交给 z.map() 校验。\n */\nexport const COERCE_MAP_HELPER =\n 'export const coerceMap = (v) => Array.isArray(v) ? new Map(v) : v instanceof Map ? v : (v && typeof v === \"object\" ? new Map(Object.entries(v)) : v);';\n\n/**\n * coerceSet 公用函数源码(ESM export 格式)\n *\n * 用于 Set<T> 的 JSON 还原:JSON 序列化为数组,JSON.parse 后还原为 Set 实例\n * - Set 实例 → 原样返回\n * - 数组 [item,...] → new Set(array)\n * - 其他值保留原值,让 z.set() 报错\n *\n * 与 coerceNumber/coerceBoolean 不同,coerceSet 在 coerce=false(body 场景)下也使用:\n * JSON.parse 出来的是数组,不是 Set 实例,必须还原才能交给 z.set() 校验。\n */\nexport const COERCE_SET_HELPER =\n 'export const coerceSet = (v) => v instanceof Set ? v : (Array.isArray(v) ? new Set(v) : v);';\n\n/**\n * faapi-helpers.js 文件名(生成在 dist 根部)\n */\nexport const HELPERS_FILENAME = 'faapi-helpers.js';\n\n/**\n * 生成 faapi-helpers.js 文件源码\n *\n * 包含 coerceNumber / coerceBoolean / coerceMap / coerceSet 四个公用函数(ESM export)。\n * 由 generateSchemaFiles 按需生成:query/params 场景的 number/boolean 字段触发 coerceNumber/coerceBoolean;\n * Map/Set 字段(query/params 与 body 均包含)触发 coerceMap/coerceSet。\n */\nexport function generateHelpersFileSource(): string {\n return [\n '// faapi-helpers.js — faapi 自动生成的公用函数(请勿手动编辑)',\n COERCE_NUMBER_HELPER,\n COERCE_BOOLEAN_HELPER,\n COERCE_MAP_HELPER,\n COERCE_SET_HELPER,\n '',\n ].join('\\n');\n}\n\n/**\n * 检测代码是否引用了 coerceNumber / coerceBoolean / coerceMap / coerceSet 变量\n *\n * 用于 generateSchemaFiles 判断是否需要生成 faapi-helpers.js。\n * 注意:Map/Set 在 coerce=false(body 场景)下也会引用 coerceMap/coerceSet,\n * 故仅凭此检测即可覆盖所有需要生成 helpers 的场景。\n */\nexport function usesCoerceHelpers(code: string): boolean {\n return (\n code.includes('coerceNumber') ||\n code.includes('coerceBoolean') ||\n code.includes('coerceMap') ||\n code.includes('coerceSet')\n );\n}\n\n/**\n * 为 number/boolean 表达式包裹 z.preprocess(用于 query coerce)\n *\n * 引用从 faapi-helpers.js import 的 coerceNumber / coerceBoolean 变量,\n * 避免每个字段都内联一长串函数。\n */\nfunction wrapCoercePreprocess(kind: 'number' | 'boolean', inner: string): string {\n if (kind === 'number') {\n return `z.preprocess(coerceNumber, ${inner})`;\n }\n return `z.preprocess(coerceBoolean, ${inner})`;\n}\n\n/**\n * 生成元组 zod 表达式\n *\n * zod 的 tuple 不直接支持可选元素,需要用 union 模拟:\n * - 全部必填:`z.tuple([e0, e1, ...])`\n * - 含可选元素:`z.union([z.tuple([e0]), z.tuple([e0, e1]), ...])`(从短到长)\n * - 含 rest:`z.tuple([e0, ...]).rest(eRest)`\n */\nfunction generateTupleExpression(elements: TupleElement[], ctx: CodeGenContext): string {\n const fixedExprs: string[] = [];\n const fixedOptional: boolean[] = [];\n let restExpression = '';\n let restStarted = false;\n\n for (const el of elements) {\n if (el.rest) {\n restExpression = runtimeTypeToZodExpression(el.type, ctx);\n restStarted = true;\n } else if (!restStarted) {\n fixedExprs.push(runtimeTypeToZodExpression(el.type, ctx));\n fixedOptional.push(el.optional);\n }\n }\n\n // 含 rest 元素:z.tuple([fixed...]).rest(restType)。\n // 可选前缀保留(zod v4 原生支持 z.tuple([X.optional()]).rest(...))——\n // 此前丢弃 fixedOptional 导致 [string?, ...number[]] 的空数组被误拒\n if (restExpression) {\n const fixedWithOptional = fixedExprs.map((expr, i) =>\n fixedOptional[i] ? `${expr}.optional()` : expr,\n );\n return `z.tuple([${fixedWithOptional.join(', ')}]).rest(${restExpression})`;\n }\n\n // 无可选元素:z.tuple([e0, e1, ...])\n const hasOptional = fixedOptional.some((o) => o);\n if (!hasOptional) {\n return `z.tuple([${fixedExprs.join(', ')}])`;\n }\n\n // 含可选元素:从后往前截断可选元素,生成 union\n // TS 语法要求可选元素在必填元素之后,所以只需从后往前去掉可选元素\n const variants: string[] = [];\n for (let len = fixedExprs.length; len >= 0; len--) {\n // len === 0 时只有全部可选才有效(空元组)\n // len > 0 时,第 len-1 个元素必须存在(必填或可选都行)\n // 截断点:去掉 fixedExprs[len..end],这些必须都是可选的\n const removed = fixedOptional.slice(len);\n if (removed.length > 0 && removed.some((o) => !o)) {\n // 被截断的元素中有必填元素,无效截断\n break;\n }\n const subset = fixedExprs.slice(0, len);\n variants.push(`z.tuple([${subset.join(', ')}])`);\n }\n\n // 从短到长排序(更自然的校验顺序)\n variants.reverse();\n if (variants.length === 1) {\n return variants[0]!;\n }\n return `z.union([${variants.join(', ')}])`;\n}\n\n/**\n * 生成对象 zod 表达式\n *\n * 可选字段用 .optional(),约束链在 .optional() 之前生成。\n * 带索引签名的对象(catchall)生成 z.object({...}).catchall(valueSchema)——\n * 索引签名与属性共存时属性保留(resolveTypeNode 不再丢弃)。\n */\nfunction generateObjectExpression(\n type: Extract<RuntimeType, { kind: 'object' }>,\n ctx: CodeGenContext,\n): string {\n const fields = type.properties.map((prop) => {\n const expr = runtimeTypeToZodExpression(prop.type, ctx, prop.constraints);\n const finalExpr = prop.optional ? `${expr}.optional()` : expr;\n return `${JSON.stringify(prop.name)}: ${finalExpr}`;\n });\n const objectExpr = `z.object({ ${fields.join(', ')} })`;\n return type.catchall !== undefined\n ? `${objectExpr}.catchall(${runtimeTypeToZodExpression(type.catchall, ctx)})`\n : objectExpr;\n}\n\n/**\n * 生成联合类型 zod 表达式\n *\n * 优化:如果联合中包含 null,用 .nullable() 简化\n * 否则用 z.union([m0, m1, ...])\n */\nfunction generateUnionExpression(members: RuntimeType[], ctx: CodeGenContext): string {\n const hasNull = members.some((m) => m.kind === 'null');\n const nonNull = members.filter((m) => m.kind !== 'null');\n\n // 只有一个非 null 成员 + null → .nullable()\n if (hasNull && nonNull.length === 1) {\n return `${runtimeTypeToZodExpression(nonNull[0]!, ctx)}.nullable()`;\n }\n\n // 多个成员 + null → z.union([...]).nullable()\n if (hasNull) {\n const unionInner = nonNull.map((m) => runtimeTypeToZodExpression(m, ctx)).join(', ');\n return `z.union([${unionInner}]).nullable()`;\n }\n\n // 无 null → z.union([...])\n const unionInner = members.map((m) => runtimeTypeToZodExpression(m, ctx)).join(', ');\n return `z.union([${unionInner}])`;\n}\n\n/**\n * 生成命名类型的 zod schema 声明\n *\n * - 无循环引用:`const NameSchema = z.object({ ... })`\n * - 有循环引用:`const NameSchema = z.lazy(() => z.object({ ... }))`\n */\nfunction generateNamedTypeDeclaration(\n name: string,\n type: RuntimeType,\n ctx: CodeGenContext,\n): string {\n const expr = runtimeTypeToZodExpression(type, ctx);\n const hasRef = containsRef(type, new Set([name]));\n if (hasRef) {\n // 循环引用:用 z.lazy 延迟求值\n return `const ${name}Schema = z.lazy(() => ${expr});`;\n }\n return `const ${name}Schema = ${expr};`;\n}\n\n/**\n * 检查 RuntimeType 是否包含对自身的 ref(循环引用)\n */\nfunction containsRef(type: RuntimeType, visited: Set<string>): boolean {\n switch (type.kind) {\n case 'ref':\n return visited.has(type.name);\n case 'array':\n return containsRef(type.element, visited);\n case 'tuple':\n return type.elements.some((el) => containsRef(el.type, visited));\n case 'object':\n return type.properties.some((prop) => containsRef(prop.type, visited));\n case 'union':\n return type.members.some((m) => containsRef(m, visited));\n case 'record':\n return containsRef(type.key, visited) || containsRef(type.value, visited);\n case 'map':\n return containsRef(type.key, visited) || containsRef(type.value, visited);\n case 'set':\n return containsRef(type.element, visited);\n default:\n return false;\n }\n}\n\n/**\n * 生成单个类型的 zod schema 代码(含命名类型声明)\n *\n * 返回的代码包含:\n * - `import { z } from 'zod'`\n * - 内部命名类型的 const 声明(带 z.lazy 循环引用保护)\n * - `export const NameSchema = ...` 入口导出\n *\n * @param typeInfo 类型信息\n * @param resolveType 类型解析器(用于解析 ref 的实际类型)\n * @param exportName 导出的 schema 变量名(不含 Schema 后缀)。默认用 typeInfo.name。\n * validateInput 按 `${schemaName}Schema` 查找,需与导出名一致。\n * @param coerce 是否生成 coerce 逻辑(query/params 场景)。默认 false。\n * true 时为 number/boolean 字段(含嵌套元素)包 z.preprocess。\n */\nexport function generateZodSchemaSource(\n typeInfo: HandlerTypeInfo,\n resolveType: TypeResolver,\n exportName?: string,\n coerce = false,\n): string {\n const { namedTypeDeclarations, entryDeclaration } = generateZodSchemaSourceParts(\n typeInfo,\n resolveType,\n exportName,\n coerce,\n );\n\n const lines: string[] = [];\n lines.push(\"import { z } from 'zod';\");\n lines.push('');\n\n for (const { declaration } of namedTypeDeclarations) {\n lines.push(declaration);\n }\n if (namedTypeDeclarations.length > 0) lines.push('');\n\n lines.push(entryDeclaration);\n\n return lines.join('\\n');\n}\n\n/**\n * 生成单个类型的 zod schema 代码片段(结构化形式)\n *\n * 与 generateZodSchemaSource 的区别:不拼 import 头,命名类型声明与入口导出分开返回,\n * 供文件级组装按名去重——同一 handler 文件的多个方法引用同一命名类型时,命名类型\n * 声明只允许出现一次,否则拼接产物含重复 const 声明,import 即 SyntaxError。\n *\n * 语义与 generateZodSchemaSource 完全一致(含 coerce / z.lazy 循环引用处理)。\n */\nexport interface ZodSchemaSourceParts {\n /** 命名类型声明(按首次引用顺序排列;name 为不含 Schema 后缀的类型名) */\n namedTypeDeclarations: Array<{ name: string; declaration: string }>;\n /** 入口 schema 导出(`export const NameSchema = ...;`) */\n entryDeclaration: string;\n}\n\nexport function generateZodSchemaSourceParts(\n typeInfo: HandlerTypeInfo,\n resolveType: TypeResolver,\n exportName?: string,\n coerce = false,\n): ZodSchemaSourceParts {\n const ctx = new CodeGenContext(resolveType);\n const name = exportName ?? typeInfo.name;\n ctx.entryTypeName = typeInfo.name;\n ctx.entryExportName = name;\n ctx.coerce = coerce;\n\n // 收集入口类型中的所有命名类型(不含入口类型本身,避免重复声明)\n collectNamedTypes(typeInfo.runtimeType, ctx);\n ctx.namedTypes.delete(typeInfo.name);\n\n const namedTypeDeclarations = [...ctx.namedTypes].map(([n, type]) => ({\n name: n,\n declaration: generateNamedTypeDeclaration(n, type, ctx),\n }));\n\n // 入口 schema 导出(含循环引用时用 z.lazy)\n const entryExpr = runtimeTypeToZodExpression(typeInfo.runtimeType, ctx);\n const hasSelfRef = containsRef(typeInfo.runtimeType, new Set([typeInfo.name]));\n const entryDeclaration = hasSelfRef\n ? `export const ${name}Schema = z.lazy(() => ${entryExpr});`\n : `export const ${name}Schema = ${entryExpr};`;\n\n return { namedTypeDeclarations, entryDeclaration };\n}\n","import path from 'node:path';\nimport { atomicWriteFile } from '../utils/atomicWrite';\nimport type { RouteManifest } from '../router/routeTypes';\nimport type { RouteSchemaSource } from './collectRouteSchemaSources';\nimport type { RuntimeType } from '../ast/resolveTypeNode';\nimport { collectRouteSchemaSources } from './collectRouteSchemaSources';\nimport {\n generateZodSchemaSourceParts,\n generateHelpersFileSource,\n usesCoerceHelpers,\n HELPERS_FILENAME,\n} from '../ast/generateZodSchema';\n\n/**\n * 源文件路径 → 产物 zod.js 路径\n *\n * 每个 handler 目录下生成一个 `zod.js`(与 handler.js 同级,文件名固定为 zod.js):\n * - `src/api/hello/handler.ts` → `<dist>/api/hello/zod.js`\n *\n * 路由源码目录写死为 src,剥离 `src/` 前缀。\n *\n * @param sourceFile 源文件相对路径(相对 rootDir,如 'src/api/hello/handler.ts')\n * @param dist 输出目录(如 'dist' 或 '.faapi')\n * @param rootDir 项目根目录\n */\nexport function getSchemaOutputPath(sourceFile: string, dist: string, rootDir: string): string {\n let rel = sourceFile.replace(/\\\\/g, '/');\n // 去掉 src/ 前缀(打平产物结构)\n if (rel.startsWith('src/')) {\n rel = rel.slice(4);\n }\n // 取目录部分,basename 固定为 zod.js(每个 handler 目录一个 zod.js)\n const idx = rel.lastIndexOf('/');\n const relDir = idx >= 0 ? rel.slice(0, idx) : '';\n return path.resolve(rootDir, dist, relDir, 'zod.js');\n}\n\n/**\n * 运行时从 route.filePath 计算对应 zod.js 绝对路径\n *\n * 每个 handler 目录下的 zod.js(文件名固定为 zod.js,与 handler.js 同级):\n * - dev 模式:route.filePath 是源码路径(如 'src/api/hello/handler.ts'),\n * zod.js 在 `<rootDir>/<dist>/api/hello/zod.js`\n * - prod 模式:route.filePath 是产物路径(如 'dist/api/hello/handler.js'),\n * zod.js 在 `<rootDir>/dist/api/hello/zod.js`(与 handler.js 同级)\n *\n * 通过同时检查 src 和 dist 前缀,统一处理两种模式:\n * - dev:strip 'src/' 前缀,join dist\n * - prod:strip 'dist/' 前缀,join dist(dist='dist',结果与 rootDir 一致)\n *\n * @param filePath route.filePath(dev 为源码路径,prod 为产物路径)\n * @param dist 输出目录(如 'dist' 或 '.faapi')\n * @param rootDir 项目根目录\n */\nexport function getRuntimeSchemaPath(filePath: string, dist: string, rootDir: string): string {\n let rel = filePath.replace(/\\\\/g, '/');\n // strip src/ 前缀(dev 模式:filePath = 'src/...')\n if (rel.startsWith('src/')) {\n rel = rel.slice(4);\n }\n // strip dist 前缀(prod 模式:filePath = 'dist/...')\n else if (rel.startsWith(`${dist}/`)) {\n rel = rel.slice(dist.length + 1);\n }\n // 取目录部分,basename 固定为 zod.js\n const idx = rel.lastIndexOf('/');\n const relDir = idx >= 0 ? rel.slice(0, idx) : '';\n return path.resolve(rootDir, dist, relDir, 'zod.js');\n}\n\n/**\n * 计算 zod.js 到 faapi-helpers.js 的相对 import 路径\n *\n * faapi-helpers.js 固定在 dist 根部,zod.js 可能在子目录(如 'api/hello/zod.js')。\n * 根据子目录深度计算 `../` 数量:\n * - zod.js 在 `api/hello/`(深度 2)→ `../../faapi-helpers.js`\n * - zod.js 在 `api/`(深度 1)→ `../faapi-helpers.js`\n * - zod.js 在 dist 根(深度 0,理论不会发生)→ `./faapi-helpers.js`\n *\n * @param relDir zod.js 所在目录相对 dist 的路径(如 'api/hello' 或 '')\n * @returns ESM import 路径(如 '../../faapi-helpers.js')\n */\nexport function getHelpersImportPath(relDir: string): string {\n if (!relDir) return `./${HELPERS_FILENAME}`;\n const depth = relDir.split('/').filter(Boolean).length;\n return `${'../'.repeat(depth)}${HELPERS_FILENAME}`;\n}\n\n/**\n * 生成单个 handler 文件的 zod.js 源码\n *\n * 自包含:extractAllTypes 在 AST 阶段已通过 TypeScript checker 内联跨文件类型,\n * 每个 zod.js 无需 import 其他 zod.js。ref 仅用于同文件内的循环引用(通过 z.lazy 处理)。\n *\n * 导出格式:\n * - `<SchemaName>Schema`:zod schema 对象(用于 safeParse 校验)\n *\n * 无类型声明的方法不导出对应 Schema(validateInput 检测到 undefined 跳过校验)。\n *\n * coerce 逻辑:query/params 来源均为 string,schemaName 以 \"Query\" 或 \"Params\" 结尾时\n * 生成 coerce=true 的 schema(number/boolean 字段用 z.preprocess 包裹字符串转换)。\n * body 是 JSON 解析的天然 JS 类型,不需要 coerce。form 与 body 共享 schema 名(POSTBody),\n * 但 `RouteSchemaSource.coerce=true` 显式覆盖(form 值均为 string,需 coerce)。\n * Map/Set 字段在两种场景下都生成 z.preprocess 包裹(JSON.parse 出来的是数组/对象,需还原为 Map/Set 实例)。\n *\n * 命名类型声明文件级去重:同一文件的多个方法引用同一命名类型时,`const XSchema` 只在\n * 文件头声明一次(按首次引用顺序),避免重复 const 声明导致 zod.js import 即 SyntaxError。\n *\n * 公用函数复用:schema 引用 coerceNumber / coerceBoolean / coerceMap / coerceSet 变量时,\n * 这些变量从 dist 根部的 faapi-helpers.js import(跨文件复用,仅一份声明)。\n *\n * @param sources 同一文件的 schema 提取结果(含多个方法,如 GETQuery + POSTBody)\n * @param resolveType ref 解析函数(解析循环引用中的 ref):名称 → 运行时类型,\n * 未声明返回 undefined。惰性实现由 collectRouteSchemaSources 的 resolversByFile 提供。\n * @param helpersImportPath 到 faapi-helpers.js 的相对 import 路径(如 '../../faapi-helpers.js')。\n * 传空字符串表示不注入 coerce helpers 的 import(用于无 coerce schema 的文件或不支持外部 import 的测试场景)。\n * @returns zod.js 源码字符串\n */\nexport function generateSchemaFileSource(\n sources: RouteSchemaSource[],\n resolveType: (name: string) => RuntimeType | undefined,\n helpersImportPath: string,\n): string {\n const lines: string[] = [\"import { z } from 'zod';\"];\n\n // 文件级命名类型声明去重:同一文件的多个方法引用同一命名类型(如 GETQuery 与\n // POSTBody 都引用 Item)时,const 声明只提升到文件头生成一次——否则拼接产物含\n // 重复 const 声明,zod.js import 即 SyntaxError,该文件所有路由 500。\n const namedTypeDeclarations: string[] = [];\n const seenNamedTypes = new Set<string>();\n\n // 先生成所有 schema 入口导出,暂存到 schemaBlocks\n const schemaBlocks: string[] = [];\n for (const source of sources) {\n const { schemaName, typeInfo } = source;\n if (!typeInfo) {\n // 无类型声明,不导出对应 Schema\n continue;\n }\n\n // 推断 coerce:\n // - source.coerce 显式设置时优先使用(form 声明时由 collectRouteSchemaSources 设置为 true)\n // - 否则回退到 schemaName 后缀正则:query/params 需要 coerce(URL 来源均为 string),body 不需要\n const coerce = source.coerce ?? /(?:Query|Params)$/.test(schemaName);\n\n // 生成结构化片段:命名类型声明按名去重后提升到文件头,入口导出留在方法 block 内\n const { namedTypeDeclarations: decls, entryDeclaration } = generateZodSchemaSourceParts(\n typeInfo,\n resolveType,\n schemaName,\n coerce,\n );\n for (const { name, declaration } of decls) {\n if (seenNamedTypes.has(name)) continue;\n seenNamedTypes.add(name);\n namedTypeDeclarations.push(declaration);\n }\n\n schemaBlocks.push([`// ${schemaName}`, entryDeclaration, ''].join('\\n'));\n }\n\n if (namedTypeDeclarations.length > 0) {\n lines.push(...namedTypeDeclarations);\n lines.push('');\n }\n\n // 检测是否有 schema 引用了 coerce 公用函数,若有则注入 import 语句\n // 公用函数包含 coerceNumber / coerceBoolean(query/params 的 string 转换)和\n // coerceMap / coerceSet(Map/Set 的 JSON 还原,body 场景也会引用)\n const allSchemaCode = [...namedTypeDeclarations, ...schemaBlocks].join('\\n');\n if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {\n lines.push(\n `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`,\n );\n }\n lines.push('');\n\n lines.push(...schemaBlocks);\n\n return lines.join('\\n').replace(/\\n+$/, '\\n');\n}\n\n/**\n * 为路由清单中每个 handler 生成 zod.js\n *\n * 输出路径与 handler.js 同级(文件名固定为 zod.js):\n * - dev:`.faapi/api/hello/zod.js`\n * - prod:`dist/api/hello/zod.js`\n *\n * 公用函数复用:若项目中存在 coerce schema(query/params)或 Map/Set 字段,\n * 在 dist 根部生成 `faapi-helpers.js`(导出 coerceNumber / coerceBoolean / coerceMap / coerceSet),\n * 各 zod.js 通过相对路径 import。\n *\n * 内部流程:\n * 1. collectRouteSchemaSources 从路由清单提取 schema 源数据(AST 从源码 .ts)\n * 2. 按 filePath 分组 sources\n * 3. 为每个文件生成 zod.js 源码(暂存,用于检测是否需要 helpers)\n * 4. 检测是否需要 helpers,需要则生成 faapi-helpers.js\n * 5. 并行写入所有 zod.js\n *\n * @param routes 排序后的路由清单\n * @param rootDir 项目根目录\n * @param dist 输出目录(如 '.faapi' 或 'dist')\n */\nexport async function generateSchemaFiles(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<void> {\n if (routes.length === 0) return;\n\n const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir);\n\n // 按 filePath 分组 sources(同一 handler 的多个方法合并到一个 zod.js)\n const sourcesByFile = new Map<string, RouteSchemaSource[]>();\n for (const source of sources) {\n let list = sourcesByFile.get(source.filePath);\n if (!list) {\n list = [];\n sourcesByFile.set(source.filePath, list);\n }\n list.push(source);\n }\n\n // 为每个文件生成 zod.js 源码(先暂存,用于检测是否需要 helpers)\n const fileEntries: { outputPath: string; source: string }[] = [];\n for (const [filePath, fileSources] of sourcesByFile) {\n // filePath 是绝对路径,转为相对 rootDir 的路径用于计算输出路径\n const relFile = path.relative(rootDir, filePath).replace(/\\\\/g, '/');\n const outputPath = getSchemaOutputPath(relFile, dist, rootDir);\n // 计算 zod.js 所在目录相对 dist 的路径(用于 import helpers)\n // 与 getSchemaOutputPath 的目录计算逻辑一致:strip src/ 前缀后取目录部分\n let relForDir = relFile;\n if (relForDir.startsWith('src/')) {\n relForDir = relForDir.slice(4);\n }\n const dirIdx = relForDir.lastIndexOf('/');\n const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : '';\n const helpersImportPath = getHelpersImportPath(zodRelDir);\n\n const resolver = resolversByFile.get(filePath);\n const source = generateSchemaFileSource(\n fileSources,\n (name) => resolver?.resolve(name)?.runtimeType,\n helpersImportPath,\n );\n fileEntries.push({ outputPath, source });\n }\n\n // 检测是否需要生成 faapi-helpers.js\n const allSourceCode = fileEntries.map((e) => e.source).join('\\n');\n if (usesCoerceHelpers(allSourceCode)) {\n const helpersPath = path.resolve(rootDir, dist, HELPERS_FILENAME);\n await writeSchemaFile(helpersPath, generateHelpersFileSource());\n }\n\n // 并行写入所有 zod.js\n await Promise.all(\n fileEntries.map(({ outputPath, source }) => writeSchemaFile(outputPath, source)),\n );\n}\n\n/**\n * 写入 zod.js 文件(确保目录存在)\n */\nasync function writeSchemaFile(outputPath: string, source: string): Promise<void> {\n // 原子写:dev watch 重建与在途请求并发时,请求 import zod.js 不能读到半成品\n await atomicWriteFile(outputPath, source);\n}\n","export type { FaapiContext, FaapiContextConfig, FailOptions } from './runtime/contextTypes';\nexport type { FaapiMiddleware } from './middleware/middlewareTypes';\nexport type { Injector, InjectorMap } from './middleware/injectorTypes';\nexport type { CorsOptions } from './middleware/cors';\nexport type { LoggerOptions } from './middleware/logger';\nexport type { HelmetOptions } from './middleware/helmet';\nexport type {\n FaapiConfig,\n LifecycleHooks,\n LifecycleContext,\n ResponseConfig,\n AgentConfig,\n LlmConfig,\n LlmModelConfig,\n} from './config/configTypes';\nexport type {\n FaapiPlugin,\n PluginContext,\n PluginDeclaration,\n RequestHandler,\n UpgradeHandler,\n} from './config/pluginTypes';\nexport type { SseWriter, SseEvent } from './runtime/sse';\nexport type { WsContext, WsSocket, WsHandler, WsEventHandlers } from './runtime/wsHandler';\nexport type {\n RouteManifest,\n RouteInfo,\n RouteInputSchema,\n RouteOutputSchema,\n RouteParamSchema,\n} from './router/routeTypes';\n\n// AST 能力(供 @faapi/schema 等扩展包复用)\nexport { createProgram, createPrograms, invalidateProgramCache } from './ast/createProgram';\nexport { extractTypeInfo, type HandlerTypeInfo } from './ast/extractHandlerTypes';\nexport type { RuntimeType, PropertyType, TypeConstraint } from './ast/resolveTypeNode';\nexport { SchemaExtractionError, resolveTypeNode } from './ast/resolveTypeNode';\nexport { getInputTypeForMethod } from './runtime/inputType';\nexport { collectRouteSchemaSources, type RouteSchemaSource } from './cli/collectRouteSchemaSources';\n\n// agent 运行时集成面(供 @faapi/agent 等扩展包消费)\n// 类型导出——描述返回结构,运行时擦除\n// 运行时访问器——@faapi/agent 插件 setup 时导入,构造 AgentDeps 注入到 Agent 类\nexport type { AgentCore, AgentMetadata, AgentPathMeta } from './ast/extractAgentMetadata';\nexport type { ToolCore, ToolMetadata, ToolPathMeta } from './ast/extractToolMetadata';\nexport type { AgentToolDescriptor } from './injection/registries';\n// app 级注册表(方案 A:实例化——每个 app 持有独立实例,全局函数为默认实例便捷访问器)\nexport type {\n AppRegistries,\n ToolRegistry,\n AgentRegistry,\n SkillRegistry,\n AgentHandleStore,\n AgentHandleFactory,\n} from './injection/registries';\nexport { createAppRegistries } from './injection/registries';\nexport type { AgentModule } from './loader/loadAgentModule';\nexport type { ToolModule } from './loader/loadToolModule';\nexport type { ToolSchemaModule } from './loader/loadToolSchema';\n// 注册表访问器(单例模块,createAppBase 水合后可直接 import 调用)\n// getAgent 返回 AgentCore(LLM 可见字段);getAgentEntry 返回 AgentMetadata(含 filePath/hasRun,供加载 handler.js)\nexport {\n getAgent,\n getAgentEntry,\n resolveAgentTools,\n resolveSubAgents,\n} from './injection/agentRegistry';\nexport { getTool } from './injection/toolRegistry';\n// skill 注册表(运行时 DB-driven skills,业务方 plugin 接入外部源时调)\n// skill 与 agent 物理隔离——agentRegistry 查询函数不 fallback 到 skillRegistry,\n// skill 不参与 agent 查询链路、不覆盖文件型 agent、不参与 sub-agent 递归。\n// 业务方 plugin 自行编排使用(如自定义注入器或中间件注入到 handler)。\nexport {\n hydrateSkillRegistry,\n upsertSkill,\n removeSkill,\n getSkill,\n listSkills,\n} from './injection/skillRegistry';\n// 动态加载器(dev 按需编译模式需 rootDir,prod 模式直接 import 产物)\nexport { loadAgentModule } from './loader/loadAgentModule';\nexport { loadToolModule } from './loader/loadToolModule';\nexport { loadToolSchema, getToolSchemaPath } from './loader/loadToolSchema';\n\n// agent handle 工厂注册(Phase 3.5)——@faapi/agent 插件 setup 时注册,\n// injectParams 在 agent 参数注入时调工厂获取 AgentHandle\nexport { registerAgentHandleFactory, clearAgentHandleFactory } from './injection/agentHandle';\n\nexport { cors } from './middleware/cors';\nexport { logger } from './middleware/logger';\nexport { helmet } from './middleware/helmet';\nexport { loadConfig } from './config/loadConfig';\nexport { loadEnv } from './cli/loadEnv';\n\n// 错误类(供业务侧 instanceof 判定与自定义错误中间件使用)\nexport {\n ValidationError,\n RouteNotFoundError,\n MethodNotAllowedError,\n InternalError,\n ModuleLoadError,\n type ValidationIssue,\n type ValidationErrorCode,\n} from './errors/httpErrors';\nexport { FaapiError } from './errors/FaapiError';\n\n// 高层编程式启动 API(参考 NestJS NestFactory.create())\n// dev/prod 拆分:createDevApp(含 reloadRoutes 热替换)/ createProdApp(精简)\n// createApp 为 createProdApp 的向后兼容别名\nexport { createDevApp, type DevApp } from './cli/createDevApp';\nexport { createProdApp, type ProdApp } from './cli/createProdApp';\nexport { createApp, type App, type CreateAppOptions } from './cli/createApp';\nexport { getApp } from './cli/createAppCore';\nexport type { InjectOptions, InjectResponse } from './cli/createAppCore';\n","import type { ToolMetadata } from '../ast/extractToolMetadata';\nimport type { AgentMetadata, AgentCore } from '../ast/extractAgentMetadata';\nimport type { FaapiContext } from '../runtime/contextTypes';\n\n/**\n * app 级注册表(方案 A:注册表实例化)\n *\n * 每个应用实例(`createAppBase`)持有一套独立的注册表,随 app 创建、随\n * `app.close()` 销毁——多 app 同进程(测试 / 嵌入 / 多租户)互不串台。\n * 此前注册表是模块级全局单例 + hydrate 整体替换语义,后创建的 app 会覆盖\n * 先创建的 app 的清单,任一 app close 会清空全部(详见各模块 .md)。\n *\n * 原四个模块(toolRegistry / agentRegistry / skillRegistry / agentHandle)\n * 保留同名全局函数作为**默认实例的便捷访问器**(供编程式直调 / 单元测试 /\n * 无 app 上下文的场景),内部全部路由到本模块的 `defaultRegistries`。\n * 框架自身链路(hydrate / 请求注入 / `@faapi/agent` 插件 / lifecycle 钩子)\n * 一律走 app 实例,不再读写全局默认实例。\n */\n\n// ─── Tool 注册表 ─────────────────────────────────────────────────────\n\nexport interface ToolRegistry {\n /** 全量替换(tool 清单来自编译期产物,reload 时整体重新生成) */\n hydrate(tools: ToolMetadata[]): void;\n /** 按全名查找(如 `weather.getWeather`) */\n get(name: string): ToolMetadata | undefined;\n /** 所有已注册 tool(副本) */\n list(): ToolMetadata[];\n clear(): void;\n}\n\nexport function createToolRegistry(): ToolRegistry {\n let registry = new Map<string, ToolMetadata>();\n return {\n hydrate(tools) {\n const next = new Map<string, ToolMetadata>();\n for (const tool of tools) {\n next.set(tool.name, tool);\n }\n registry = next;\n },\n get(name) {\n return registry.get(name);\n },\n list() {\n return Array.from(registry.values());\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Agent 注册表 ────────────────────────────────────────────────────\n\n/** agent 包装为 tool 的描述符(reactLoop 据此识别 sub-agent 递归) */\nexport interface AgentToolDescriptor {\n kind: 'agent';\n name: string;\n agentName: string;\n description?: string;\n metadata: AgentCore;\n}\n\nexport interface AgentRegistry {\n /** 全量替换(与 hydrateToolRegistry 同构) */\n hydrate(agents: AgentMetadata[]): void;\n /** LLM 可见元数据(AgentCore) */\n getAgent(name: string): AgentCore | undefined;\n /** 完整元数据(含 filePath / hasRun,供加载 handler.js 执行 run) */\n getAgentEntry(name: string): AgentMetadata | undefined;\n /** 所有已注册 agent 的 LLM 可见元数据(副本) */\n listAgents(): AgentCore[];\n /** 包装为 tool 描述符 */\n asTool(name: string): AgentToolDescriptor | undefined;\n /**\n * 解析 agent 显式声明的 tool 集合。\n * 跨注册表依赖:经由构造时绑定的 tool 注册表查找(同属一套 AppRegistries,\n * 由 createAppBase 在同一启动阶段水合)\n */\n resolveAgentTools(name: string): ToolMetadata[];\n /** 解析 agent 可调用的子 agent 集合 */\n resolveSubAgents(name: string): AgentCore[];\n clear(): void;\n}\n\nexport function createAgentRegistry(tool: ToolRegistry): AgentRegistry {\n let registry = new Map<string, AgentMetadata>();\n\n const getAgent = (name: string): AgentCore | undefined => registry.get(name);\n\n return {\n hydrate(agents) {\n const next = new Map<string, AgentMetadata>();\n for (const agent of agents) {\n next.set(agent.name, agent);\n }\n registry = next;\n },\n getAgent,\n getAgentEntry(name) {\n return registry.get(name);\n },\n listAgents() {\n const merged = new Map<string, AgentCore>();\n for (const agent of registry.values()) merged.set(agent.name, agent);\n return Array.from(merged.values());\n },\n asTool(name) {\n const agent = getAgent(name);\n if (!agent) return undefined;\n return {\n kind: 'agent',\n name: `agent.${agent.name}`,\n agentName: agent.name,\n description: agent.description,\n metadata: agent,\n };\n },\n resolveAgentTools(name) {\n const agent = getAgent(name);\n if (!agent) return [];\n\n const result = new Map<string, ToolMetadata>();\n if (agent.tools) {\n for (const toolName of agent.tools) {\n const resolved = tool.get(toolName);\n if (resolved) result.set(resolved.name, resolved);\n // 未找到的 tool 名静默跳过(tool 可选可用,不强制存在)\n }\n }\n return Array.from(result.values());\n },\n resolveSubAgents(name) {\n const agent = getAgent(name);\n if (!agent || !agent.agents) return [];\n\n const result: AgentCore[] = [];\n for (const subName of agent.agents) {\n const sub = getAgent(subName);\n if (sub) result.push(sub);\n // 未注册的 agent 名跳过(agent 可选可用,不强制存在)\n }\n return result;\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Skill 注册表 ────────────────────────────────────────────────────\n\nexport interface SkillRegistry {\n /** 全量替换(DB change stream 场景也可用 upsert 增量) */\n hydrate(skills: AgentCore[]): void;\n /** 增量注册 / 覆盖(业务方监听 DB 单条变更) */\n upsert(skill: AgentCore): void;\n remove(name: string): void;\n get(name: string): AgentCore | undefined;\n list(): AgentCore[];\n clear(): void;\n}\n\nexport function createSkillRegistry(): SkillRegistry {\n let registry = new Map<string, AgentCore>();\n return {\n hydrate(skills) {\n const next = new Map<string, AgentCore>();\n for (const skill of skills) {\n next.set(skill.name, skill);\n }\n registry = next;\n },\n upsert(skill) {\n registry.set(skill.name, skill);\n },\n remove(name) {\n registry.delete(name);\n },\n get(name) {\n return registry.get(name);\n },\n list() {\n return Array.from(registry.values());\n },\n clear() {\n registry = new Map();\n },\n };\n}\n\n// ─── Agent handle 工厂 ───────────────────────────────────────────────\n\n/** agent handle 工厂函数(由 `@faapi/agent` 插件注册) */\nexport type AgentHandleFactory = (ctx: FaapiContext) => unknown;\n\nexport interface AgentHandleStore {\n /** 注册工厂(null 清理);二次注册覆盖 */\n register(factory: AgentHandleFactory | null): void;\n /** 工厂已注册时返回 AgentHandle 实例,未注册返回 undefined */\n get(ctx: FaapiContext): unknown;\n clear(): void;\n}\n\nexport function createAgentHandleStore(): AgentHandleStore {\n let currentFactory: AgentHandleFactory | null = null;\n return {\n register(factory) {\n currentFactory = factory;\n },\n get(ctx) {\n if (currentFactory === null) return undefined;\n return currentFactory(ctx);\n },\n clear() {\n currentFactory = null;\n },\n };\n}\n\n// ─── App 级集合 ──────────────────────────────────────────────────────\n\n/** 一个 app 实例持有的全套注册表 */\nexport interface AppRegistries {\n tool: ToolRegistry;\n agent: AgentRegistry;\n skill: SkillRegistry;\n agentHandle: AgentHandleStore;\n}\n\n/** 创建一套 app 级注册表(`createAppBase` 每次调用创建独立实例) */\nexport function createAppRegistries(): AppRegistries {\n const tool = createToolRegistry();\n const agent = createAgentRegistry(tool);\n const skill = createSkillRegistry();\n const agentHandle = createAgentHandleStore();\n return { tool, agent, skill, agentHandle };\n}\n\n/**\n * 默认实例:原全局函数的退路(编程式直调 / 单元测试 / 无 app 上下文场景)。\n *\n * 框架自身链路不读写此实例——app 创建自己的 AppRegistries 并在请求链路 /\n * 插件 / lifecycle 钩子中传递。多 app 场景下默认实例无隔离语义(等同旧全局行为)。\n */\nexport const defaultRegistries: AppRegistries = createAppRegistries();\n","import type { AgentCore, AgentMetadata } from '../ast/extractAgentMetadata';\nimport { defaultRegistries } from './registries';\n\n// 类型从 registries 转发(AgentToolDescriptor 的定义源已随实例化迁入 registries)\nexport type { AgentToolDescriptor } from './registries';\n\n/**\n * agent 注册表全局访问器(默认实例便捷入口)\n *\n * 框架自身链路(`createAppBase` 水合 / `@faapi/agent` 插件 deps / `agents`\n * 参数注入)已改为读写 **app 实例级注册表**(见 [registries.md](./registries.md))——\n * agent 注册表实例在构造时绑定同套 tool 注册表实例(`resolveAgentTools` 跨表查找),\n * 多 app 同进程互不串台。\n *\n * 本模块的同名函数保留为**默认实例**的便捷访问器(语义约束不变:不 fallback 到\n * skillRegistry、Core / Entry 双查询入口),供编程式直调 / 单元测试使用。\n *\n * ## 与 skillRegistry 物理隔离\n *\n * agent 负责核心流程,skill 用于拓展——查询函数不 fallback 到 skillRegistry,\n * 实例化后依旧如此(两套注册表实例互不引用)。\n *\n * 详见 [agentRegistry.md](./agentRegistry.md)。\n */\n\n/**\n * 水合默认实例的 agent 注册表(全量替换)\n *\n * 框架路径请使用 app 实例:`ctx.registries.agent.hydrate(agents)`。\n *\n * @param agents 从 `faapi-agents.js` 水合还原的 `AgentMetadata[]`\n */\nexport function hydrateAgentRegistry(agents: AgentMetadata[]): void {\n defaultRegistries.agent.hydrate(agents);\n}\n\n/**\n * 清空默认实例的 agent 注册表\n *\n * app close 清理的是 app 自己的实例,不经过此函数。\n */\nexport function clearAgentRegistry(): void {\n defaultRegistries.agent.clear();\n}\n\n/**\n * 按名查找单个 agent 的 LLM 可见元数据(默认实例)\n *\n * 返回 [AgentCore](../ast/extractAgentMetadata.md)(不含 filePath / hasRun)。\n * 加载 handler.js 执行 `run` 函数请用 [getAgentEntry](#getAgentEntry)。\n *\n * @param name agent 名(如 `researcher`,含 `@agent` 覆盖值)\n */\nexport function getAgent(name: string): AgentCore | undefined {\n return defaultRegistries.agent.getAgent(name);\n}\n\n/**\n * 按名查找单个 agent 的完整元数据(默认实例,含 filePath / hasRun)\n */\nexport function getAgentEntry(name: string): AgentMetadata | undefined {\n return defaultRegistries.agent.getAgentEntry(name);\n}\n\n/**\n * 返回默认实例所有已注册 agent 的 LLM 可见元数据(副本,仅文件型 agent)\n */\nexport function listAgents(): AgentCore[] {\n return defaultRegistries.agent.listAgents();\n}\n\n/**\n * 把 agent 包装为 tool 描述符(默认实例)\n */\nexport function asTool(name: string) {\n return defaultRegistries.agent.asTool(name);\n}\n\n/**\n * 解析 agent 显式声明的 tool 集合(默认实例,跨查默认 tool 注册表)\n */\nexport function resolveAgentTools(name: string) {\n return defaultRegistries.agent.resolveAgentTools(name);\n}\n\n/**\n * 解析 agent 可调用的子 agent 集合(默认实例,按声明顺序)\n */\nexport function resolveSubAgents(name: string): AgentCore[] {\n return defaultRegistries.agent.resolveSubAgents(name);\n}\n","import type { ToolMetadata } from '../ast/extractToolMetadata';\nimport { defaultRegistries } from './registries';\n\n/**\n * tool 注册表全局访问器(默认实例便捷入口)\n *\n * 框架自身链路(`createAppBase` 水合 / 请求注入 / `@faapi/agent` 插件)已改为\n * 读写 **app 实例级注册表**(见 [registries.md](./registries.md))——每个 app\n * 持有独立实例,随 app 创建/销毁,多 app 同进程互不串台。\n *\n * 本模块的同名函数保留为**默认实例**的便捷访问器,供编程式直调 / 单元测试 /\n * 无 app 上下文的场景使用;多 app 场景下默认实例无隔离语义(等同旧全局行为),\n * 框架路径不再读写它。\n *\n * 详见 [toolRegistry.md](./toolRegistry.md)。\n */\n\n/**\n * 水合默认实例的 tool 注册表(全量替换)\n *\n * 框架路径请使用 app 实例:`ctx.registries.tool.hydrate(tools)`。\n *\n * @param tools 从 `faapi-tools.js` 水合还原的 `ToolMetadata[]`\n */\nexport function hydrateToolRegistry(tools: ToolMetadata[]): void {\n defaultRegistries.tool.hydrate(tools);\n}\n\n/**\n * 清空默认实例的 tool 注册表\n *\n * app close 清理的是 app 自己的实例,不经过此函数。\n */\nexport function clearToolRegistry(): void {\n defaultRegistries.tool.clear();\n}\n\n/**\n * 按全名查找单个 tool(默认实例)\n *\n * 框架路径(`@faapi/agent` 插件 / 请求注入)从 app 实例查找。\n *\n * @param tool 全名(如 `weather.getWeather`)\n * @returns `ToolMetadata` 或 `undefined`(未注册)\n */\nexport function getTool(name: string): ToolMetadata | undefined {\n return defaultRegistries.tool.get(name);\n}\n\n/**\n * 返回默认实例所有已注册 tool(副本)\n */\nexport function listTools(): ToolMetadata[] {\n return defaultRegistries.tool.list();\n}\n","import type { AgentCore } from '../ast/extractAgentMetadata';\n\n/**\n * skill 注册表(单例,运行时动态 skill)\n *\n * 与 [agentRegistry](./agentRegistry.md) 物理隔离,承载 DB-driven skills\n * (业务方在 plugin 里从数据库 / 外部源加载的 skill 元数据)。\n *\n * ## 与 agentRegistry 物理隔离\n *\n * - `agentRegistry` —— 编译期 `faapi-agents.js` 产物一次性水合,dev `reloadAgents`\n * 整体重新替换。来源是文件系统(`src/agents/<name>/handler.ts`)。承载文件型\n * agent,负责核心流程(含 `run` 函数的多步串联、sub-agent 递归)。\n * - `skillRegistry` —— 运行时动态,业务方 plugin `onReady` 启动期灌入 + DB change\n * stream 单条增删。来源是数据库 / 外部 API。仅供业务方 plugin 内部使用。\n *\n * 两者职责正交不耦合:**skill 用于拓展**,不参与 agent 查询链路、不覆盖文件型\n * agent、不被 agent 的 `agents` 列表自动引用。agentRegistry 的查询函数\n * (getAgent / listAgents / asTool / resolveAgentTools / resolveSubAgents)\n * **不 fallback 到本模块**。\n *\n * 物理隔离避免 `reloadAgents` 清空 DB skill(dev 模式每次改文件都触发 reload,\n * 业务方手工重新塞 DB skill 不可接受)。\n *\n * ## 存储 AgentCore 而非 AgentMetadata\n *\n * skillRegistry 存储 [AgentCore](../ast/extractAgentMetadata.md) 而非完整\n * `AgentMetadata`——DB skill 无源文件,无需 `filePath` / `hasRun` / `hasConfig`\n * 等代码加载占位字段。业务方从 DB 字段直接映射到 `AgentCore` 的 LLM 可见字段\n * (name / description / systemPrompt / tools / agents / model / maxTurns)即可。\n *\n * 详见 [skillRegistry.md](./skillRegistry.md)。\n */\n\nimport { defaultRegistries } from './registries';\n\n/**\n * skill 注册表全局访问器(默认实例便捷入口)\n *\n * 框架推荐路径已改为 **app 实例级注册表**:业务方 plugin 在\n * `lifecycle.onReady(ctx)` 中通过 `ctx.registries.skill` 灌入 DB skill——\n * 这样 skill 与该 app 的生命周期绑定,多 app 同进程互不串台,且 app close\n * 时随实例销毁。\n *\n * 本模块的同名函数保留为**默认实例**的便捷访问器(向后兼容),但注意:\n * 默认实例与 app 实例相互独立——经全局函数灌入的 skill 不会出现在\n * 该 app 的请求链路中。\n *\n * 详见 [skillRegistry.md](./skillRegistry.md) 与 [registries.md](./registries.md)。\n */\n\n/**\n * 水合默认实例的 skill 注册表(全量替换)\n *\n * 框架推荐路径:`lifecycle.onReady(ctx)` 中 `ctx.registries.skill.hydrate(skills)`。\n */\nexport function hydrateSkillRegistry(skills: AgentCore[]): void {\n defaultRegistries.skill.hydrate(skills);\n}\n\n/**\n * 清空默认实例的 skill 注册表\n */\nexport function clearSkillRegistry(): void {\n defaultRegistries.skill.clear();\n}\n\n/**\n * 单条增改默认实例的 skill(运行时增量)\n *\n * 框架推荐路径:`ctx.registries.skill.upsert(skill)`。\n */\nexport function upsertSkill(core: AgentCore): void {\n defaultRegistries.skill.upsert(core);\n}\n\n/**\n * 单条删除默认实例的 skill(幂等)\n */\nexport function removeSkill(name: string): void {\n defaultRegistries.skill.remove(name);\n}\n\n/**\n * 按名查默认实例的单个 skill\n */\nexport function getSkill(name: string): AgentCore | undefined {\n return defaultRegistries.skill.get(name);\n}\n\n/**\n * 返回默认实例所有已注册 skill(副本)\n */\nexport function listSkills(): AgentCore[] {\n return defaultRegistries.skill.list();\n}\n","import fs from 'node:fs';\nimport { resolveExport } from './resolveExports';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport {\n ensureCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n prodPathToSourcePath,\n} from '../cli/compileOnDemand';\n\n/**\n * 加载后的 agent 模块\n *\n * 与 [ToolModule](./loadToolModule.md) 对称——agent 的代码本体只有可选的 `run` 函数\n * (自定义 agent 运行逻辑,替代默认 reactLoop)。\n *\n * > `config` 字段已移除——`AgentMetadata` 已含 AST 提取的字面量字段\n * > (systemPrompt / tools / agents / model / maxTurns),`AgentModule.config`\n * > 原本用于运行时拿到完整 config 对象(含动态字段),但 `executeSubAgent`\n * > 拿到 `mod.config` 后从不读取(run 函数在自己模块内直接引用 config 变量),\n * > 属于死链路,故移除。\n *\n * `AgentMetadata`(从 `faapi-agents.js` 水合)已含字面量字段,本模块仅用于\n * 在运行时拿到 `run` 函数引用。\n */\nexport interface AgentModule {\n /**\n * 自定义 agent 运行函数(可选)\n *\n * `hasRun` 为 true 时一定为 function;为 false 时为 `undefined`。\n * 调用方式由 `@faapi/agent` 子包的 Agent 类定义(Phase 3.x)。\n */\n run: ((...args: unknown[]) => unknown) | undefined;\n}\n\n/**\n * 动态 import agent handler 文件并提取 `run` 导出\n *\n * Dev 按需编译模式(Vite 风格):先 `ensureCompiled` 确保产物存在再 import,\n * 避免 import 不存在的文件污染 Vite SSR 内部状态(详见 [loadRouteModule](./loadRouteModule.md))。\n * Prod 模式:产物在 build 阶段已固化,直接 import,失败即报错。\n *\n * 与 [loadToolModule](./loadToolModule.md) 的差异:\n * - tool 按 `functionName` 提取单个函数(校验为 function)\n * - agent 只提取 `run`(函数,可选)——config 块字段已在 AST 阶段提取为字面量,\n * 运行时无需再加载 config 对象\n *\n * 错误传递:\n * - 编译失败 → 抛 \"Failed to compile agent module\"\n * - import 失败 → 抛 \"Failed to load agent module\"\n *\n * @param filePath agent handler 文件的绝对路径(产物形式,如 `dist/agents/researcher/handler.js`)\n * @param hasRun 是否应提取 run 导出(来自 `AgentMetadata.hasRun`)\n * @param rootDir 项目根目录(按需编译模式用,可选)\n */\nexport async function loadAgentModule(\n filePath: string,\n hasRun: boolean,\n rootDir?: string,\n): Promise<AgentModule> {\n // Dev 按需模式:先确保编译再 import\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 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 agent 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 agent module \"${filePath}\": ${reason}`, { cause: err });\n }\n\n // 提取 run:hasRun 为 true 时解析并校验为 function,失败抛错\n let run: ((...args: unknown[]) => unknown) | undefined;\n if (hasRun) {\n const runExport = resolveExport(module, 'run');\n if (typeof runExport !== 'function') {\n throw new Error(\n `Agent module \"${filePath}\" does not export a valid \"run\" function (hasRun=true). ` +\n `Expected a function, got ${runExport === undefined ? 'undefined' : typeof runExport}.`,\n );\n }\n run = runExport as (...args: unknown[]) => unknown;\n }\n\n return { run };\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","import { pathToFileURL } from 'node:url';\n\n/**\n * 当前模块加载时间戳(watch 模式下用于绕过 ESM 缓存)\n *\n * 由 createDevApp.reloadRoutes 调用 setLoadTimestamp 设置。\n * ES 模块单例保证所有 import 此模块的地方共享同一个值,无需 globalThis。\n */\nlet loadTs: number | undefined;\n\nexport function setLoadTimestamp(ts: number): void {\n loadTs = ts;\n}\n\n/**\n * vitest 的 importActual 函数类型\n *\n * vi.importActual(path) 走 Vite SSR pipeline:\n * - 识别 vitest.config.ts 的 resolve.alias 与 tsconfig paths 别名\n * - 让 vi.mock 在加载的模块内生效\n */\ntype ImportActualFn = (path: string) => Promise<unknown>;\n\n/**\n * 检测 vitest 的 vi.importActual(globals: true 时注入 globalThis.vi)\n *\n * 业务方 vitest.config.ts 需 test.globals: true,或在测试文件内\n * 显式 `import { vi } from 'vitest'` 后挂到 globalThis.vi。\n *\n * 不在 vitest 环境下返回 undefined,调用方回退到 Node 原生 import()。\n */\nfunction getVitestImportActual(): ImportActualFn | undefined {\n const vi = (globalThis as { vi?: { importActual?: ImportActualFn } }).vi;\n if (typeof vi?.importActual !== 'function') return undefined;\n return vi.importActual.bind(vi);\n}\n\n/**\n * 动态 import 文件\n *\n * 两种加载路径:\n *\n * 1. **vitest 环境**(`globalThis.vi.importActual` 可用):走 Vite SSR pipeline,\n * 识别 tsconfig paths 别名(如 `@/lib/db`)+ 让 `vi.mock` 生效。\n * 业务方在 vitest 下用 `createTestServer` 时自动启用。\n *\n * 2. **非 vitest 环境**:Node 原生 `import()`。watch 模式下拼接 `?t=<timestamp>`\n * query 绕过 ESM 缓存;非 watch 模式等价普通 `import()`。\n *\n * @param filePath 文件绝对路径\n * @param bustViteCache dev 按需模式下走 Node 原生 import(绕过 Vite SSR pipeline)。\n * handler.js / zod.js 是已编译产物(别名已在编译时重写为相对路径),不需要 Vite alias 解析,\n * 直接走 Node 原生 import + 时间戳 query 避免 Vite SSR 缓存干扰。\n * @returns 模块导出对象\n */\nexport async function importWithCacheBust(\n filePath: string,\n bustViteCache = false,\n): Promise<Record<string, unknown>> {\n // vitest 环境优先走 Vite pipeline(识别 tsconfig paths + vi.mock)\n const importActual = getVitestImportActual();\n if (importActual) {\n if (bustViteCache) {\n // dev 按需模式:handler.js / zod.js 是已编译产物(别名已在编译时重写为相对路径),\n // 不需要 Vite alias 解析,直接走 Node 原生 import + 时间戳 query 避免 Vite SSR 缓存干扰\n let url = pathToFileURL(filePath).href;\n url += `?t=${Date.now()}`;\n return (await import(url)) as Record<string, unknown>;\n }\n return (await importActual(filePath)) as Record<string, unknown>;\n }\n\n // 否则走 Node 原生 ESM import\n let url = pathToFileURL(filePath).href;\n if (loadTs !== undefined) {\n url += `?t=${loadTs}`;\n }\n return (await import(url)) as Record<string, unknown>;\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport { compileDevRoutes } from './compileDevRoutes';\nimport { generateSchemaFiles } from './generateSchemaFiles';\nimport { getRuntimeSchemaPath } from './generateSchemaFiles';\nimport { collectRelativeImports } from './collectImports';\nimport type { RouteManifest } from '../router/routeTypes';\n\n/**\n * 按需编译(Vite 风格):仅在请求时编译被访问的 handler.ts 及其依赖闭包 + 生成 zod.js\n *\n * 设计目标:dev 启动时零编译、零 schema 生成,handler.js / zod.js 首次被需要时才触发。\n * 与阶段 1(scanRoutes 去 import)配合,dev 冷启动近乎瞬开。\n *\n * 依赖闭包:`bundle: false` 逐文件编译不分析 import 关系,只编译 handler 单文件时,\n * handler 引用的共享模块(如 `../../lib/db`)没有产物,首次请求 import 即\n * ERR_MODULE_NOT_FOUND。因此 ensureCompiled 先收集依赖闭包\n * (collectRelativeImports,相对 + tsconfig paths 别名),跳过产物已新鲜的文件后\n * 批量编译。src 外依赖不收集(outbase=src 语义限制,与 watcher 增量编译一致)。\n *\n * 四种触发路径:\n * 1. `loadRouteModule` 先调 `ensureCompiled` 编译源码 → 再 import handler.js\n * 2. `loadWsHandler` 同上(WS handler.js)\n * 3. `createServer` 调 `ensureSchemaGenerated` 检查 zod.js 是否存在/最新 → 不存在则生成\n * 4. watcher 文件变化 → 增量编译该文件(保留现有逻辑)+ 删除 stale zod.js\n * 5. `createServer` / `handleWsUpgrade` 首次加载目录中间件前调 `ensureMiddlewaresCompiled`\n *\n * mtime 缓存(阶段 4):\n * - `ensureCompiled`:handler.js 存在且 mtime ≥ 源码 mtime → 跳过编译\n * - `ensureSchemaGenerated`:zod.js 存在且 mtime ≥ 源码 mtime → 跳过生成\n * - 首次调用后写入内存 Set,后续直接跳过(避免 fs.statSync 调用)\n * - watcher 触发时清除内存 Set,mtime 重新评估\n *\n * 并发去重(mutex):\n * - 同一文件的并发编译请求会共享同一个 in-flight Promise,避免重复触发 esbuild\n * - 同一 schemaPath 的并发生成请求同理\n * - watcher 触发 clear 时同步清空 in-flight Map\n */\n\n/**\n * 比较源文件和产物文件的 mtime\n *\n * @returns true 表示产物是最新的(可复用),false 表示需要重新生成\n */\nfunction isProductFresh(sourceAbsPath: string, productAbsPath: string): boolean {\n try {\n const srcStat = fs.statSync(sourceAbsPath);\n const prodStat = fs.statSync(productAbsPath);\n return prodStat.mtimeMs >= srcStat.mtimeMs;\n } catch {\n return false;\n }\n}\n\n// ─── DevOnDemand 状态封装 ────────────────────────────────────────────\n\n/**\n * 按需编译运行时状态\n *\n * 封装原本散落的 4 个模块级可变状态 + 2 个 mutex Map,避免全局污染 +\n * 便于测试隔离(_resetDevOnDemandState 重置回初始状态)。\n *\n * faapi 单进程单 server 设计下,单例 state 已足够。多实例测试场景通过\n * _resetDevOnDemandState 在 beforeEach / afterEach 中清空状态。\n */\ninterface DevOnDemandState {\n /** 是否启用 dev 按需编译模式(prod 始终 false) */\n enabled: boolean;\n /** dev 模式产物目录(仅 enabled 时使用) */\n distDir: string | undefined;\n /** 已编译文件缓存(源码绝对路径) */\n compiledFiles: Set<string>;\n /** 已生成 schema 缓存(schemaPath 绝对路径) */\n generatedSchemas: Set<string>;\n /** handler.js 编译 mutex:同一 sourceAbsPath 的并发请求共享同一 Promise */\n inFlightCompilations: Map<string, Promise<void>>;\n /** zod.js 生成 mutex:同一 schemaPath 的并发请求共享同一 Promise */\n inFlightSchemaGenerations: Map<string, Promise<void>>;\n}\n\nfunction createDevOnDemandState(): DevOnDemandState {\n return {\n enabled: false,\n distDir: undefined,\n compiledFiles: new Set(),\n generatedSchemas: new Set(),\n inFlightCompilations: new Map(),\n inFlightSchemaGenerations: new Map(),\n };\n}\n\nconst state: DevOnDemandState = createDevOnDemandState();\n\n/**\n * 重置 dev on demand 状态(仅测试用)\n *\n * 清空所有缓存 + mutex + 标记位,让下一次测试从干净状态开始。\n * 生产代码不应调用此函数——会丢失已编译产物的缓存。\n */\nexport function _resetDevOnDemandState(): void {\n state.enabled = false;\n state.distDir = undefined;\n state.compiledFiles.clear();\n state.generatedSchemas.clear();\n state.inFlightCompilations.clear();\n state.inFlightSchemaGenerations.clear();\n}\n\n// ─── handler.js 按需编译 ────────────────────────────────────────────\n\n/**\n * 清空所有按需编译缓存(reloadRoutes 时调用)\n *\n * 同时清空 in-flight mutex Map + 产物→源码映射缓存(源文件新建/删除后映射需重建),\n * 避免 watcher 重置后旧 Promise 永久阻塞。\n */\nexport function clearCompiledFiles(): void {\n state.compiledFiles.clear();\n state.inFlightCompilations.clear();\n sourcePathCache.clear();\n}\n\n/**\n * 确保源文件及其依赖闭包已编译为产物,未编译则触发批量编译\n *\n * 调用方(`loadRouteModule` / `loadWsHandler`)在 import 产物之前调用此函数,确保产物已生成。\n *\n * 依赖闭包:handler 引用的项目内共享模块(相对 import + tsconfig paths 别名)一并编译,\n * 跳过产物已新鲜的文件(watcher 已编译或此前请求已编译),只补缺口。\n *\n * mtime 缓存(阶段 4):\n * 1. 内存 Set 命中 → 跳过(最快路径)\n * 2. 产物存在且 mtime ≥ 源码 mtime → 跳过(复用已有产物,如 watcher 已编译)\n * 3. 产物不存在或 stale → 编译(含 stale 的依赖闭包文件) → 加入内存 Set\n *\n * 并发去重(mutex):\n * - 同一 sourceAbsPath 的并发请求共享同一 in-flight Promise\n * - 第二个请求 await 后返回 false(表示「别的请求已触发编译,本次不需要再触发」)\n *\n * @param sourceAbsPath 源码 .ts 绝对路径\n * @param rootDir 项目根目录\n * @param dist 产物目录(dev 模式为 '.faapi')\n * @returns 是否实际触发了编译(false 表示已编译过或产物已最新或源文件不存在)\n * @throws 编译失败时抛错(带原始 cause),调用方可据此给出精确错误信息\n */\nexport async function ensureCompiled(\n sourceAbsPath: string,\n rootDir: string,\n dist: string,\n): Promise<boolean> {\n // mutex: 同一文件正在被别的请求编译 → 等待并返回 false\n const inFlight = state.inFlightCompilations.get(sourceAbsPath);\n if (inFlight) {\n await inFlight.catch(() => {\n // 别的请求编译失败时不在这里抛——让本请求按正常流程自己重试\n });\n return false;\n }\n\n // 内存缓存命中:跳过\n if (state.compiledFiles.has(sourceAbsPath)) {\n return false;\n }\n\n // 源文件不存在:无法编译\n if (!fs.existsSync(sourceAbsPath)) {\n return false;\n }\n\n // mtime 缓存:产物已存在且最新 → 复用(watcher 已编译过的情况)\n const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);\n if (productPath && isProductFresh(sourceAbsPath, productPath)) {\n state.compiledFiles.add(sourceAbsPath);\n return false;\n }\n\n // 触发编译:注册 in-flight Promise 防止并发重复编译\n const compilePromise = (async () => {\n // 依赖闭包:入口本身 + src 内全部传递依赖;产物已新鲜的文件跳过(只补缺口)\n const { insideFiles } = await collectRelativeImports([sourceAbsPath], rootDir);\n const files = [sourceAbsPath];\n for (const dep of insideFiles) {\n if (state.compiledFiles.has(dep)) continue;\n const depProduct = prodSourcePathToProductPath(dep, rootDir, dist);\n if (depProduct && isProductFresh(dep, depProduct)) continue;\n files.push(dep);\n }\n\n await compileDevRoutes({\n rootDir,\n dist,\n files,\n logLevel: 'silent',\n });\n for (const file of files) {\n state.compiledFiles.add(file);\n }\n state.compiledFiles.add(sourceAbsPath);\n })();\n\n state.inFlightCompilations.set(sourceAbsPath, compilePromise);\n try {\n await compilePromise;\n return true;\n } finally {\n state.inFlightCompilations.delete(sourceAbsPath);\n }\n}\n\n/**\n * 确保目录中间件文件已编译(dev 按需模式)\n *\n * middlewarePaths 是产物绝对路径(`.faapi/api/users/middlewares.js`),dev 按需模式下\n * 产物在首次请求前不存在——若不先编译,`loadMergedMiddlewares` import 即\n * ERR_MODULE_NOT_FOUND。通过 `prodPathToSourcePath` 反推源码路径后走 `ensureCompiled`\n * (含依赖闭包)。prod 模式(按需开关关闭)直接返回——产物由 build 固化。\n *\n * @param middlewarePaths 中间件产物绝对路径列表(scanRoutes 收集)\n * @param rootDir 项目根目录\n */\nexport async function ensureMiddlewaresCompiled(\n middlewarePaths: string[],\n rootDir: string,\n): Promise<void> {\n if (!isDevOnDemandEnabled() || middlewarePaths.length === 0) return;\n const dist = getDevDist();\n if (!dist) return;\n\n for (const mwPath of middlewarePaths) {\n const sourcePath = prodPathToSourcePath(mwPath, rootDir, dist);\n try {\n await ensureCompiled(sourcePath, rootDir, dist);\n } catch (err) {\n // 中间件编译失败不阻断请求——loadMiddlewaresFile 对 import 失败已有\n // console.error + 空 bundle 降级语义(鉴权失效由 onError 感知),此处保持一致\n console.error(`[faapi] Failed to compile middleware source ${sourcePath}:`, err);\n }\n }\n}\n\n/**\n * 从源码绝对路径推算产物绝对路径\n *\n * 源码 `<rootDir>/src/api/hello/handler.ts` → 产物 `<rootDir>/<dist>/api/hello/handler.js`\n */\nfunction prodSourcePathToProductPath(\n sourceAbsPath: string,\n rootDir: string,\n dist: string,\n): string | null {\n const rel = path.relative(rootDir, sourceAbsPath).replace(/\\\\/g, '/');\n if (!rel.startsWith('src/')) return null;\n const relWithoutSrc = rel.slice(4); // 去掉 src/\n const jsRel = relWithoutSrc.replace(/\\.ts$/, '.js');\n return path.resolve(rootDir, dist, jsRel);\n}\n\n// ─── zod.js 按需生成 ────────────────────────────────────────────────\n\n/**\n * 清空所有 schema 生成缓存(reloadRoutes 时调用)\n *\n * 同时清空 in-flight mutex Map。\n */\nexport function clearGeneratedSchemas(): void {\n state.generatedSchemas.clear();\n state.inFlightSchemaGenerations.clear();\n}\n\n/**\n * 确保 zod.js 已生成,未生成则触发单文件 schema 生成\n *\n * 调用方(createServer)在 validateInput 之前调用此函数。\n *\n * route.filePath 在 dev/prod 模式下均为产物路径(如 '.faapi/api/hello/handler.js'),\n * 需通过 prodPathToSourcePath 反推源码 .ts 路径用于 AST 分析和 mtime 比较。\n *\n * mtime 缓存(阶段 4):\n * 1. 内存 Set 命中 → 跳过\n * 2. zod.js 存在且 mtime ≥ 源码 mtime → 跳过(复用已有产物)\n * 3. zod.js 不存在或 stale → 生成 → 加入内存 Set\n *\n * 并发去重(mutex):\n * - 同一 schemaPath 的并发请求共享同一 in-flight Promise\n * - 第二个请求 await 后返回 false\n *\n * @param schemaPath zod.js 绝对路径\n * @param routeFilePath route.filePath(产物路径,如 '.faapi/api/hello/handler.js')\n * @param routes 完整路由清单(用于过滤同文件的所有方法)\n * @param rootDir 项目根目录\n * @param dist 产物目录\n * @returns 是否实际触发了生成(false 表示已生成过或产物已最新或源文件不存在)\n * @throws schema 生成失败时抛错(带原始 cause),由 createServer 错误处理链接管\n */\nexport async function ensureSchemaGenerated(\n schemaPath: string,\n routeFilePath: string,\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<boolean> {\n // mutex: 同一 schemaPath 正在被别的请求生成 → 等待并返回 false\n const inFlight = state.inFlightSchemaGenerations.get(schemaPath);\n if (inFlight) {\n await inFlight.catch(() => {\n // 别的请求生成失败时不在这里抛——让本请求按正常流程自己重试\n });\n return false;\n }\n\n // 内存缓存命中:跳过\n if (state.generatedSchemas.has(schemaPath)) {\n return false;\n }\n\n // route.filePath 是产物路径,反推源码绝对路径(用于 mtime 比较和 AST 分析)\n const prodAbsPath = path.resolve(rootDir, routeFilePath);\n const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);\n if (!fs.existsSync(sourceAbsPath)) {\n return false;\n }\n\n // mtime 缓存:zod.js 已存在且最新 → 复用\n if (isProductFresh(sourceAbsPath, schemaPath)) {\n state.generatedSchemas.add(schemaPath);\n return false;\n }\n\n // 过滤同文件的所有路由(一个 handler.ts 可能有 GET/POST/WS 多个方法)\n const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);\n if (fileRoutes.length === 0) {\n return false;\n }\n\n // 把产物 filePath 转为源码 filePath(generateSchemaFiles → collectRouteSchemaSources\n // 需要源码 .ts 路径做 AST 分析)\n const sourceRelPath = path.relative(rootDir, sourceAbsPath).replace(/\\\\/g, '/');\n const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));\n\n // 触发生成:注册 in-flight Promise 防止并发重复生成\n const generatePromise = (async () => {\n // 生成失败时抛错(不吞错误),由 createServer 错误处理链接管\n await generateSchemaFiles(sourceRoutes, rootDir, dist);\n state.generatedSchemas.add(schemaPath);\n })();\n\n state.inFlightSchemaGenerations.set(schemaPath, generatePromise);\n try {\n await generatePromise;\n return true;\n } finally {\n state.inFlightSchemaGenerations.delete(schemaPath);\n }\n}\n\n/**\n * 删除指定路由清单对应的所有 zod.js 文件(reloadRoutes 时调用)\n *\n * watcher 文件变化后,旧 zod.js 可能 stale(类型引用变化等)。\n * 删除后下次请求触发 ensureSchemaGenerated 重新生成。\n *\n * 仅删除文件,不删除目录(faapi-helpers.js 保留,内容确定性不变)。\n */\nexport async function deleteSchemaFiles(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n): Promise<void> {\n const deleted = new Set<string>();\n for (const route of routes) {\n const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);\n if (deleted.has(schemaPath)) continue;\n deleted.add(schemaPath);\n try {\n await fs.promises.unlink(schemaPath);\n } catch {\n // 文件不存在:忽略\n }\n }\n}\n\n// ─── 路径转换 ────────────────────────────────────────────────────────\n\n/**\n * 从产物绝对路径反推源码绝对路径(带映射缓存)\n *\n * 源码 `<rootDir>/src/api/hello/handler.ts` → 产物 `<rootDir>/<dist>/api/hello/handler.js`\n *\n * dev 按需模式下**每请求**都会调此函数(loadRouteModule / ensureSchemaGenerated),\n * 缓存映射后稳态请求零 fs 访问。映射仅取决于源文件的存在性——源文件新建/删除\n * 通过 `clearCompiledFiles()`(reloadRoutes 时调用)清缓存。\n */\nconst sourcePathCache = new Map<string, string>();\n\nexport function prodPathToSourcePath(prodAbsPath: string, rootDir: string, dist: string): string {\n const cached = sourcePathCache.get(prodAbsPath);\n if (cached) return cached;\n\n const rel = path.relative(rootDir, prodAbsPath).replace(/\\\\/g, '/');\n // 去掉 <dist>/ 前缀\n let relWithoutDist = rel;\n if (relWithoutDist.startsWith(`${dist}/`)) {\n relWithoutDist = relWithoutDist.slice(dist.length + 1);\n }\n // 加 src/ 前缀\n const srcRel = `src/${relWithoutDist}`;\n // .js → .ts(.ts 不存在时回退 .js)\n const tsRel = srcRel.replace(/\\.js$/, '.ts');\n const tsAbs = path.resolve(rootDir, tsRel);\n let result: string;\n if (fs.existsSync(tsAbs)) {\n result = tsAbs;\n } else {\n // .ts 不存在,保持 .js(极少见,可能是用户直接放 .js 源码)\n result = path.resolve(rootDir, srcRel);\n }\n sourcePathCache.set(prodAbsPath, result);\n return result;\n}\n\n// ─── Dev 按需模式标记 ────────────────────────────────────────────────\n\n/**\n * Dev 按需编译模式标记\n *\n * 由 devCommand 在按需模式启动时设置为 true,loadRouteModule / loadWsHandler /\n * createServer 通过此标记判断是否启用按需编译/schema 生成回退。\n *\n * prod 模式(node dist/main)始终为 false——产物在 build 阶段已固化,import 失败即报错。\n */\nexport function setDevOnDemandEnabled(enabled: boolean): void {\n state.enabled = enabled;\n}\n\nexport function isDevOnDemandEnabled(): boolean {\n return state.enabled;\n}\n\n/**\n * Dev 模式产物目录(仅 devOnDemandEnabled 时使用)\n *\n * 由 devCommand 设置,loadRouteModule / loadWsHandler / createServer 通过 getDevDist() 读取。\n */\nexport function setDevDist(dist: string): void {\n state.distDir = dist;\n}\n\nexport function getDevDist(): string | undefined {\n return state.distDir;\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport fg from 'fast-glob';\nimport { buildAliasPlugins } from './aliasPlugin';\nimport { APP_DIR } from '../utils/prodPaths';\n\n/**\n * 编译结果\n */\nexport interface CompileResult {\n /** 已编译的文件列表(绝对路径) */\n compiledFiles: string[];\n}\n\nexport interface CompileSourceFilesOptions {\n /** 项目根目录 */\n rootDir: string;\n /** 输出目录(dev 为 `.faapi`,build 为 `dist`) */\n dist: string;\n /**\n * 增量编译:传入要编译的文件列表(绝对路径)。\n * 不传则全量编译 src 下所有 .ts(排除测试文件和声明文件)。\n */\n files?: string[];\n /** 是否输出 esbuild 日志(默认静默) */\n logLevel?: 'silent' | 'info';\n /**\n * prod 语义:`define` 把 `process.env.NODE_ENV` 编译期替换为 `\"production\"` +\n * `minifySyntax` 删除死分支(两者在 `bundle: false` 下均生效,单文件级别优化)。\n */\n production?: boolean;\n /**\n * dev 语义:原子写(write: false + 临时文件 + rename),避免 watch 模式下\n * esbuild 非原子写期间运行时 import 读到半成品产物。\n */\n atomicWrite?: boolean;\n}\n\n/**\n * dev/prod 共用的逐文件 TypeScript 编译实现\n *\n * 每个 `.ts` 独立编译为 `.js`,不分析 import 关系(`bundle: false`——\n * 保证 config 与 routes 共享同一运行时对象,`instanceof` 跨边界生效)。\n * 产物**打平 src/ 前缀**:`src/api/hello/handler.ts` → `<dist>/api/hello/handler.js`。\n *\n * 别名在编译时重写为相对路径,运行时无需 loader。\n *\n * 通过 `compileDevRoutes` / `compileBuildRoutes` 调用,见 ./compileSourceFiles.md\n * 的选项矩阵。\n */\nexport async function compileSourceFiles(\n options: CompileSourceFilesOptions,\n): Promise<CompileResult> {\n const { rootDir, dist, files, logLevel = 'silent', production, atomicWrite } = options;\n\n // 收集要编译的文件:files 优先,否则全量扫描 src 下所有 .ts\n const entryPoints =\n files ??\n (await fg([`${APP_DIR}/**/*.ts`], {\n cwd: rootDir,\n onlyFiles: true,\n absolute: true,\n ignore: ['**/*.test.ts', '**/*.e2e.test.ts', '**/*.d.ts'],\n }));\n\n if (entryPoints.length === 0) {\n return { compiledFiles: [] };\n }\n\n // 确保输出目录存在\n const absDist = path.resolve(rootDir, dist);\n await fs.promises.mkdir(absDist, { recursive: true });\n\n // 构造别名重写插件(无 tsconfig/paths 时为空,相对路径重写仍生效)\n const plugins = buildAliasPlugins(rootDir);\n\n // 逐文件编译,outbase 设为 src 以打平产物结构:\n // `src/api/hello/handler.ts` → `<dist>/api/hello/handler.js`(去掉 src/ 前缀)\n const esbuild = await import('esbuild');\n const outbase = path.resolve(rootDir, APP_DIR);\n const result = await esbuild.build({\n entryPoints,\n outdir: absDist,\n outbase,\n bundle: false,\n platform: 'node',\n format: 'esm',\n sourcemap: true,\n packages: 'external',\n plugins,\n // build 语义:编译期 NODE_ENV 替换 + 死分支删除(见 AGENTS.md §5.3)\n ...(production\n ? { define: { 'process.env.NODE_ENV': '\"production\"' }, minifySyntax: true }\n : {}),\n logLevel,\n // dev 语义:esbuild 返回内存内容,由下方原子写落盘\n ...(atomicWrite ? { write: false as const } : {}),\n });\n\n // 原子写:逐个写临时文件 + rename。rename 在同一文件系统上是原子的(POSIX),\n // HTTP 请求要么看到旧文件要么看到新文件\n if (atomicWrite && result.outputFiles) {\n await Promise.all(\n result.outputFiles.map(async (file) => {\n await fs.promises.mkdir(path.dirname(file.path), { recursive: true });\n const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n await fs.promises.writeFile(tmp, file.contents);\n await fs.promises.rename(tmp, file.path);\n }),\n );\n }\n\n return { compiledFiles: entryPoints };\n}\n\n/**\n * 全量扫描 src 下的 .ts 文件(排除测试文件和声明文件)\n *\n * 与 compileSourceFiles 的全量分支使用同一 ignore 规则,供 watch 等场景独立扫描。\n */\nexport async function collectSourceFiles(rootDir: string): Promise<string[]> {\n return fg([`${APP_DIR}/**/*.ts`], {\n cwd: rootDir,\n onlyFiles: true,\n absolute: true,\n ignore: ['**/*.test.ts', '**/*.e2e.test.ts', '**/*.d.ts'],\n });\n}\n","import type { Plugin } from 'esbuild';\nimport 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 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 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 fs from 'node:fs';\nimport { resolveExport } from './resolveExports';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport {\n ensureCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n prodPathToSourcePath,\n} from '../cli/compileOnDemand';\n\n/**\n * 加载后的 tool 模块\n *\n * 与 `RouteModule` 对称——`functionName` 替代 `method`(tool 没有HTTP方法维度)。\n * `handler` 是从模块解析出的 tool 函数,可直接调用。\n */\nexport interface ToolModule {\n /** tool 函数(已校验为 function 类型) */\n handler: (...args: unknown[]) => unknown;\n /** 源码导出名(如 `getWeather`,用于日志/调试,与 `method` 对应) */\n functionName: string;\n}\n\n/**\n * 动态 import tool handler 文件并提取指定函数名的导出\n *\n * Dev 按需编译模式(Vite 风格):先 `ensureCompiled` 确保产物存在再 import,\n * 避免 import 不存在的文件污染 Vite SSR 内部状态(详见 [loadRouteModule](./loadRouteModule.md))。\n * Prod 模式:产物在 build 阶段已固化,直接 import,失败即报错。\n *\n * 错误传递:\n * - 编译失败 → 抛 \"Failed to compile tool module\"\n * - import 失败 → 抛 \"Failed to load tool module\"\n * - 导出不是函数 → 抛 \"does not export a valid function\"\n *\n * @param filePath tool handler 文件的绝对路径(产物形式,如 `dist/tools/weather/handler.js`)\n * @param functionName 源码导出函数名(如 `getWeather`,由 `ToolManifest.functionName` 提供)\n * @param rootDir 项目根目录(按需编译模式用,可选)\n */\nexport async function loadToolModule(\n filePath: string,\n functionName: string,\n rootDir?: string,\n): Promise<ToolModule> {\n // Dev 按需模式:先确保编译再 import\n // 避免\"import 失败 → 编译 → 重试 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 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 tool 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 tool module \"${filePath}\": ${reason}`, { cause: err });\n }\n\n const handler = resolveExport(module, functionName);\n\n // 内联校验(与 validateRouteModule 同构,但错误消息提到 tool 而非 method)\n if (typeof handler !== 'function') {\n throw new Error(\n `Tool module \"${filePath}\" does not export a valid function for \"${functionName}\". ` +\n `Expected a function, got ${handler === undefined ? 'undefined' : typeof handler}.`,\n );\n }\n\n // typeof 缩窄后为 Function 广义类型,断言为具体的函数签名\n return { handler: handler as (...args: unknown[]) => unknown, functionName };\n}\n","import { existsSync } from 'node:fs';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport { isDevOnDemandEnabled, getDevDist } from '../cli/compileOnDemand';\nimport { getRuntimeToolSchemaPath } from '../cli/generateToolArtifacts';\nimport type { ToolMetadata } from '../ast/extractToolMetadata';\n\n/**\n * 加载后的 tool schema 模块\n *\n * 与 [ToolModule](./loadToolModule.md) 对称——`schema` 替代 `handler`。\n *\n * `schema` 是 `unknown` 类型——faapi 核心不依赖 zod(zod 是 peerDep),\n * `zod.js` 由业务方安装的 zod 创建,`@faapi/agent` 负责断言为 zod schema 后\n * 调 `z.toJSONSchema` / `safeParse`。\n */\nexport interface ToolSchemaModule {\n /** zod schema 对象(由业务方安装的 zod 创建) */\n schema: unknown;\n /** schema 导出名(如 `WeatherInputSchema`,用于日志/调试) */\n schemaName: string;\n}\n\n/**\n * 获取当前 dist 目录\n *\n * dev 按需模式:`getDevDist()`(`.faapi`,dev 产物目录固定不可修改)\n * prod 模式:`process.env.FAAPI_DIST`(默认 `dist`,可通过 `--dist` 修改)\n */\nfunction getDist(): string {\n if (isDevOnDemandEnabled()) {\n return getDevDist() ?? '.faapi';\n }\n return process.env.FAAPI_DIST ?? 'dist';\n}\n\n/**\n * 计算 tool 的 zod.js 绝对路径(纯路径计算,无 fs 访问)\n *\n * 与 [loadToolSchema](./loadToolSchema.ts) 内部使用的路径逻辑同源(共享 `getDist()`),\n * 供 `@faapi/agent` 的跨请求 schema 缓存用作缓存键 + mtime 校验目标。\n *\n * @param tool tool 元数据(含 `filePath`)\n * @param rootDir 项目根目录(`tool.filePath` 是相对路径时拼接)\n */\nexport function getToolSchemaPath(tool: ToolMetadata, rootDir?: string): string {\n const dist = getDist();\n return getRuntimeToolSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());\n}\n\n/**\n * 动态加载 tool 的 zod.js schema 模块\n *\n * 与 [loadToolModule](./loadToolModule.md) 对称——一个加载 handler.js(tool 函数),\n * 一个加载 zod.js(tool schema)。\n *\n * 行为:\n * - `tool.inputTypeName` 为 `undefined` → 返回 `undefined`(无 schema,用自由 schema)\n * - zod.js 文件不存在 → 返回 `undefined`(schema 可选,缺失用自由 schema)\n * - import 失败 / 导出名不匹配 → 返回 `undefined`\n *\n * 与 route schema 不同(route schema 缺失抛 `InternalError`),tool schema 是可选的——\n * `@faapi/agent` 的 `resolveToolSchema` 未提供时用自由 schema `{ type: 'object' }`,\n * LLM 自由传参,handler 内部自行处理参数合法性。\n *\n * @param tool tool 元数据(含 `filePath` + `inputTypeName`)\n * @param rootDir 项目根目录(用于计算 zod.js 绝对路径,`tool.filePath` 是相对路径时拼接)\n */\nexport async function loadToolSchema(\n tool: ToolMetadata,\n rootDir?: string,\n): Promise<ToolSchemaModule | undefined> {\n // 无 inputTypeName → 无 zod.js\n if (!tool.inputTypeName) return undefined;\n\n const schemaName = `${tool.inputTypeName}Schema`;\n const zodPath = getToolSchemaPath(tool, rootDir);\n\n // zod.js 文件不存在 → 返回 undefined(schema 可选)\n if (!existsSync(zodPath)) return undefined;\n\n // import zod.js\n try {\n const mod = await importWithCacheBust(zodPath, isDevOnDemandEnabled());\n const schema = mod[`${tool.inputTypeName}Schema`];\n if (!schema) return undefined;\n return { schema, schemaName };\n } catch {\n // import 失败(语法错误等)→ 返回 undefined\n return undefined;\n }\n}\n","import path from 'node:path';\nimport { existsSync } from 'node:fs';\nimport type { ToolManifestList } from '../tools/toolTypes';\nimport type { ToolMetadata } from '../ast/extractToolMetadata';\nimport { extractToolMetadata } from '../ast/extractToolMetadata';\nimport { createPrograms } from '../ast/createProgram';\nimport { toProdFilePath } from '../utils/prodPaths';\nimport { atomicWriteFile } from '../utils/atomicWrite';\nimport { getSchemaOutputPath } from './generateSchemaFiles';\nimport {\n extractTypeInfo,\n createLazyTypeResolver,\n type HandlerTypeInfo,\n type LazyTypeResolver,\n} from '../ast/extractHandlerTypes';\nimport type { RuntimeType } from '../ast/resolveTypeNode';\nimport {\n generateZodSchemaSource,\n generateHelpersFileSource,\n usesCoerceHelpers,\n HELPERS_FILENAME,\n} from '../ast/generateZodSchema';\nimport { getHelpersImportPath } from './generateSchemaFiles';\n\n/**\n * 序列化的 tool manifest 记录(可写入 JS 模块,无函数引用)\n *\n * 与 [ToolMetadata](../ast/extractToolMetadata.md) 字段一一对应,仅 `filePath`\n * 由源码形式(`src/...`)转为产物形式(`<dist>/...`,打平 `src/` 前缀 + dist 前缀 + `.js`)。\n *\n * `undefined` 字段(description / inputTypeName)在 JSON.stringify 时自动省略,\n * 水合时通过 `??` 兜底为 undefined。\n */\nexport interface SerializedToolRecord {\n /** tool 名(`@tool` 覆盖值 或 路径推导值) */\n name: string;\n /** 源码中的导出函数名(供 loadToolModule 在 handler.js 中按名 import) */\n functionName: string;\n /** JSDoc 描述(对 LLM 可见) */\n description?: string;\n /** 第一个参数 interface 名(用于从 zod.js 加载 schema) */\n inputTypeName?: string;\n /** 产物形式路径(如 `dist/tools/weather/handler.js`),供运行时 import tool.js */\n filePath: string;\n}\n\n/**\n * 单个 tool 的 schema 提取结果\n *\n * 与 [RouteSchemaSource](./collectRouteSchemaSources.md) 对称,但 schema key 用 tool 名而非 urlPath。\n */\nexport interface ToolSchemaSource {\n /** tool 名(含 `@tool` 覆盖),用作注释标识 */\n name: string;\n /** 源文件绝对路径(用于 generateToolArtifacts 按文件分组生成 zod.js) */\n filePath: string;\n /** schema 名 = inputTypeName(导出 `${inputTypeName}Schema`) */\n schemaName: string;\n /** AST 提取的类型信息,null 时不导出 Schema */\n typeInfo: HandlerTypeInfo | null;\n}\n\n/**\n * faapi-tools.js 文件名(与 faapi-routes.js 同构)\n */\nconst TOOLS_FILE = 'faapi-tools.js';\n\n/**\n * tool zod.js 输出路径 / 运行时路径计算\n *\n * 与路由 schema 的路径规则完全一致(strip src/ 前缀 + dist + 固定 zod.js 文件名,\n * 见 generateSchemaFiles.ts 的实现),此处别名 re-export 保持 tool 侧语义命名。\n * 此前两份逐字同构实现已合并。\n */\nexport {\n getSchemaOutputPath as getToolSchemaOutputPath,\n getRuntimeSchemaPath as getRuntimeToolSchemaPath,\n} from './generateSchemaFiles';\n/**\n * 序列化 tool 清单为可写入 JS 模块的结构\n *\n * - `filePath` 转为产物形式(打平 `src/` 前缀 + dist 前缀 + `.js`)\n * - 其他字段(name/functionName/description/inputTypeName)直接透传\n * - `undefined` 字段在 JSON.stringify 时自动省略\n *\n * @param tools AST 增强后的 ToolMetadata[](由 generateToolArtifacts 内部从 ToolManifest 增强)\n * @param dist 产物目录(默认 `dist`),用于转换 filePath\n */\nexport function serializeTools(\n tools: ToolMetadata[],\n dist: string = 'dist',\n): SerializedToolRecord[] {\n return tools.map((t) => ({\n name: t.name,\n functionName: t.functionName,\n description: t.description,\n inputTypeName: t.inputTypeName,\n filePath: toProdFilePath(t.filePath, dist),\n }));\n}\n\n/**\n * 把序列化的 tool 清单写入 faapi-tools.js\n *\n * 生成 ESM 模块,运行时 `createAppCore` 通过 `importWithCacheBust` 加载。\n * 用 JSON.stringify 嵌入,保证字符串转义安全(与 `writeRoutesModule` 一致)。\n */\nexport async function writeToolsModule(\n manifest: SerializedToolRecord[],\n outputPath: string,\n): Promise<void> {\n const content = `// 自动生成,请勿手动编辑(faapi build/dev 产物)\nexport const tools = ${JSON.stringify(manifest, null, 2)};\n`;\n await atomicWriteFile(outputPath, content);\n}\n\n/**\n * 从序列化清单水合还原 ToolMetadata[]\n *\n * 字段一一对应(无函数引用需还原)。`undefined` 字段在 JSON.parse 时缺失,\n * 通过 `??` 兜底为 undefined(保证 `ToolMetadata` 类型完整)。\n */\nexport function hydrateTools(manifest: SerializedToolRecord[]): ToolMetadata[] {\n return manifest.map((t) => ({\n name: t.name,\n functionName: t.functionName,\n description: t.description ?? undefined,\n inputTypeName: t.inputTypeName ?? undefined,\n filePath: t.filePath,\n }));\n}\n\n/**\n * 从 ToolMetadata[] 收集 schema 提取所需的原始数据\n *\n * 与 [collectRouteSchemaSources](./collectRouteSchemaSources.ts) 对称,但:\n * - 按 tool handler.ts 文件分组(同一 handler.ts 多个 tool 合并到一个 zod.js)\n * - schema 名 = inputTypeName(导出 `${inputTypeName}Schema`,与路由的 `<METHOD><InputType>Schema` 不同)\n * - coerce=false(tool input 来自 LLM JSON 调用,与 body 一致,无需字符串转换)\n *\n * 跳过 inputTypeName 为 undefined 的 tool(无参数/无类型标注/内联类型字面量),\n * 不生成对应 schema(与路由的\"无类型声明的方法不导出 Schema\"对齐)。\n *\n * 与路由的 collectRouteSchemaSources 一致采用惰性解析:入口类型(inputTypeName)\n * 立即解析且必须严格校验,ref 按需解析并缓存——与 tool 无关的类型零开销。\n *\n * @returns sources(用于生成 zod.js) + resolversByFile(用于解析 ref)\n */\nfunction collectToolSchemaSources(\n tools: ToolMetadata[],\n rootDir: string,\n): {\n sources: ToolSchemaSource[];\n resolversByFile: Map<string, LazyTypeResolver>;\n} {\n // 按文件分组 tool(同一 handler.ts 多个 tool 合并到一个 zod.js)\n const toolsByFile = new Map<string, ToolMetadata[]>();\n for (const tool of tools) {\n if (!tool.inputTypeName) continue; // 跳过无 inputTypeName 的 tool\n const absPath = path.resolve(rootDir, tool.filePath);\n let list = toolsByFile.get(absPath);\n if (!list) {\n list = [];\n toolsByFile.set(absPath, list);\n }\n list.push(tool);\n }\n\n // 批量共享 Program:同一次提取只创建一个 Program,避免逐文件全量解析\n const programByFile = createPrograms([...toolsByFile.keys()]);\n const resolversByFile = new Map<string, LazyTypeResolver>();\n for (const filePath of toolsByFile.keys()) {\n resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath)!, filePath));\n }\n\n // 为每个 tool 提取 schema\n const sources: ToolSchemaSource[] = [];\n for (const [filePath, fileTools] of toolsByFile) {\n const program = programByFile.get(filePath)!;\n for (const tool of fileTools) {\n // inputTypeName 已在外层过滤,这里一定非空(防御性兜底)\n const inputTypeName = tool.inputTypeName!;\n const typeInfo = extractTypeInfo(program, filePath, inputTypeName); // 入口类型,严格解析\n sources.push({\n name: tool.name,\n filePath,\n schemaName: inputTypeName, // schema 名 = inputTypeName\n typeInfo,\n });\n }\n }\n\n return { sources, resolversByFile };\n}\n\n/**\n * 生成单个 tool handler.ts 的 zod.js 源码\n *\n * 自包含:[extractAllTypes](../ast/extractHandlerTypes.md) 在 AST 阶段已通过 TypeScript checker\n * 内联跨文件类型,每个 zod.js 无需 import 其他 zod.js。`ref` 仅用于同文件内的循环引用\n * (通过 `z.lazy` 处理)。\n *\n * 导出格式:\n * - `${inputTypeName}Schema`:zod schema 对象(用于 safeParse 校验)\n *\n * 与 [generateSchemaFileSource](./generateSchemaFiles.ts) 的差异:\n * - schema 名直接取自 `inputTypeName`(无 HTTP 方法前缀)\n * - coerce=false(tool input 来自 LLM JSON 调用,与 body 一致)\n *\n * 无 inputTypeName 的 tool 不导出对应 Schema(collectToolSchemaSources 已过滤)。\n *\n * @param sources 同一文件的 schema 提取结果(含多个 tool)\n * @param resolveType ref 解析函数(解析循环引用中的 ref):名称 → 运行时类型,未声明返回 undefined\n * @param helpersImportPath 到 faapi-helpers.js 的相对 import 路径(如 `../../faapi-helpers.js`)。\n * 传空字符串表示不注入 coerce helpers 的 import(用于无 helpers 引用的文件或测试场景)。\n */\nexport function generateToolSchemaFileSource(\n sources: ToolSchemaSource[],\n resolveType: (name: string) => RuntimeType | undefined,\n helpersImportPath: string,\n): string {\n const lines: string[] = [\"import { z } from 'zod';\"];\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 // tool schema coerce=false(input 来自 LLM JSON 调用,与 body 一致)\n const coerce = false;\n\n const block = [`// ${source.name} → ${schemaName}`];\n // generateZodSchemaSource 自带 import 语句,剥离后由本函数统一管理 import\n // 传入 schemaName 作为 exportName,确保导出名与运行时查找一致(`${inputTypeName}Schema`)\n const schemaCode = generateZodSchemaSource(typeInfo, resolveType, schemaName, coerce).replace(\n /^import \\{ z \\} from 'zod';\\s*\\n\\s*\\n/,\n '',\n );\n block.push(schemaCode);\n block.push('');\n schemaBlocks.push(block.join('\\n'));\n }\n\n // 检测是否有 schema 引用了 coerce 公用函数(Map/Set 还原,即使 coerce=false 也可能引用)\n const allSchemaCode = 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 * 检测并生成 faapi-helpers.js(若不存在且 schema 引用了公用函数)\n *\n * tool schema 在两种场景下会引用 faapi-helpers.js 的公用变量:\n * 1. Map/Set 字段:即使 coerce=false 也引用 `coerceMap` / `coerceSet`(JSON.parse 出来的是数组/对象,需还原)\n * 2. number/boolean 字段:coerce=true 时引用 `coerceNumber` / `coerceBoolean`\n * (tool schema coerce=false,不会触发此场景)\n *\n * 与 [generateSchemaFiles](./generateSchemaFiles.ts) 共享同一份 `faapi-helpers.js`——\n * 若文件已存在则不重复生成(路由 schema 生成阶段可能已生成)。\n */\nasync function maybeGenerateHelpers(allSourceCode: string, distDir: string): Promise<void> {\n if (!usesCoerceHelpers(allSourceCode)) return;\n const helpersPath = path.resolve(distDir, HELPERS_FILENAME);\n if (existsSync(helpersPath)) return;\n await atomicWriteFile(helpersPath, generateHelpersFileSource());\n}\n\n/**\n * 主入口:从 ToolManifest[] 生成 faapi-tools.js + 每个 tool 的 zod.js\n *\n * 内部流程:\n * 1. 对每个 ToolManifest 调 `createProgram` + `extractToolMetadata` → ToolMetadata[]\n * (AST 增强:补全 description / inputTypeName / `@tool` 覆盖后的 name)\n * 2. `serializeTools(metadata, dist)` → SerializedToolRecord[](filePath 转产物形式)\n * 3. `writeToolsModule(serialized, faapiToolsPath)` → 写入 `<dist>/faapi-tools.js`\n * 4. 若 `skipSchema=true`(dev 按需模式)→ 返回,不生成 zod.js\n * 5. `collectToolSchemaSources` 从 ToolMetadata[] 收集 schema 源数据(按文件分组)\n * 6. 为每个文件计算 `helpersImportPath` + 生成 zod.js 源码(暂存)\n * 7. `maybeGenerateHelpers` 检测并生成/复用 `faapi-helpers.js`\n * 8. 并行写入所有 zod.js\n *\n * @param tools scanTools 产出的 ToolManifest[](仅路径推导字段)\n * @param rootDir 项目根目录\n * @param dist 产物目录(`.faapi` 或 `dist`)\n * @param options.skipSchema 跳过 zod.js 生成(dev 按需模式启动时仅生成清单)\n * @returns AST 增强后的 ToolMetadata[](供调用方日志/调试)\n */\nexport async function generateToolArtifacts(\n tools: ToolManifestList,\n rootDir: string,\n dist: string,\n options?: { skipSchema?: boolean },\n): Promise<ToolMetadata[]> {\n // 1. AST 增强:对每个 manifest 调 extractToolMetadata(批量共享 Program)\n const metadata: ToolMetadata[] = [];\n const programByFile = createPrograms(tools.map((m) => path.resolve(rootDir, m.filePath)));\n for (const manifest of tools) {\n const absPath = path.resolve(rootDir, manifest.filePath);\n const program = programByFile.get(absPath)!;\n const result = extractToolMetadata(program, absPath, manifest.functionName, {\n name: manifest.name,\n filePath: manifest.filePath,\n });\n if (result) {\n metadata.push(result);\n }\n }\n\n // 2. 序列化 + 写入 faapi-tools.js\n const serialized = serializeTools(metadata, dist);\n const toolsPath = path.resolve(rootDir, dist, TOOLS_FILE);\n await writeToolsModule(serialized, toolsPath);\n\n // 4. dev 按需模式:跳过 zod.js 生成(首次请求时由 ensureToolSchemaGenerated 按需生成)\n if (options?.skipSchema) {\n return metadata;\n }\n\n // 5-8. 生成 zod.js\n if (metadata.length === 0) {\n return metadata;\n }\n\n const { sources, resolversByFile } = collectToolSchemaSources(metadata, rootDir);\n if (sources.length === 0) {\n return metadata; // 无 inputTypeName 的 tool,不生成 zod.js\n }\n\n // 按文件分组 sources(同一 handler.ts 多个 tool 合并到一个 zod.js)\n const sourcesByFile = new Map<string, ToolSchemaSource[]>();\n for (const source of sources) {\n let list = sourcesByFile.get(source.filePath);\n if (!list) {\n list = [];\n sourcesByFile.set(source.filePath, list);\n }\n list.push(source);\n }\n\n // 为每个文件生成 zod.js 源码(先暂存,用于检测是否需要 helpers)\n const fileEntries: { outputPath: string; source: string }[] = [];\n for (const [filePath, fileSources] of sourcesByFile) {\n 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 的路径(用于 import helpers)\n let relForDir = relFile;\n if (relForDir.startsWith('src/')) {\n relForDir = relForDir.slice(4);\n }\n const dirIdx = relForDir.lastIndexOf('/');\n const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : '';\n const helpersImportPath = getHelpersImportPath(zodRelDir);\n\n const source = generateToolSchemaFileSource(\n fileSources,\n (name) => resolver?.resolve(name)?.runtimeType,\n helpersImportPath,\n );\n fileEntries.push({ outputPath, source });\n }\n\n // 检测是否需要生成 faapi-helpers.js\n const allSourceCode = fileEntries.map((e) => e.source).join('\\n');\n const distDir = path.resolve(rootDir, dist);\n await maybeGenerateHelpers(allSourceCode, distDir);\n\n // 并行写入所有 zod.js\n await Promise.all(\n fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source)),\n );\n\n return metadata;\n}\n\n/**\n * 写入 tool zod.js 文件(确保目录存在)\n */\nasync function writeToolSchemaFile(outputPath: string, source: string): Promise<void> {\n await atomicWriteFile(outputPath, source);\n}\n","import ts from 'typescript';\nimport {\n extractDescription,\n extractJSDocTagValue,\n getJSDocFromNode,\n hasExportModifier,\n} from './jsDocMetadata';\n\n/**\n * Tool 的 LLM 可见核心字段\n *\n * 描述\"tool 是什么\"——LLM 真正需要消费的字段(发往 LLM 的 tool 定义只含\n * `name` / `description` / input schema),**不含**代码本体加载细节\n * (`filePath` / `functionName` / `inputTypeName`)。\n *\n * 与 [AgentCore](./extractAgentMetadata.md) 对称——LLM-facing 字段与代码加载\n * 细节分离,便于未来扩展(如 DB-driven tool 只实现 `ToolCore` 即可)。\n *\n * `toolRegistry` 查询入口 / `@faapi/agent` 子包的 `buildToolDefinitions`\n * 都消费 `ToolCore` 字段组装 LLM tool 列表。\n */\nexport interface ToolCore {\n /** tool 名(`@tool` JSDoc 覆盖值 或 路径推导值) */\n name: string;\n /** JSDoc 描述(tool 描述,对 LLM 可见),无 JSDoc 或 JSDoc 无自由文本时为 `undefined` */\n description?: string;\n}\n\n/**\n * Tool 完整元数据(文件型 tool)\n *\n * 继承 [ToolCore](./extractToolMetadata.md) 的 LLM 字段,额外扩展**代码本体加载细节**:\n * - `filePath` — `loadToolModule` 加载 `handler.js` 产物定位函数用\n * - `functionName` — 源码导出函数名(不受 `@tool` 覆盖影响,AST 定位 + 运行时 resolveExport 用)\n * - `inputTypeName` — 第一个参数的 TypeReference 名(供 [extractTypeInfo](./extractHandlerTypes.md)\n * 生成 zod schema;运行时 `resolveToolSchema` 据此定位 `zod.js`)\n *\n * 由 [extractToolMetadata](./extractToolMetadata.md) 产出,合并路径推导字段\n * (来自 [scanTools](../tools/scanTools.md) 的 `ToolManifest`)与 AST 提取字段\n * (JSDoc 描述、`@tool` 覆盖名、第一个参数 interface 名)。\n *\n * 字段来源:\n * - `name` — `@tool` JSDoc 覆盖值,或 `pathMeta.name`(路径推导)\n * - `description` — JSDoc 注释块自由文本(对 LLM 可见)\n * - `filePath` / `functionName` — 由 `pathMeta` 透传\n * - `inputTypeName` — 第一个参数的 TypeReference 名(供 [extractTypeInfo](./extractHandlerTypes.md) 生成 zod schema)\n */\nexport interface ToolMetadata extends ToolCore {\n /** 第一个参数的 interface/type 名(用于生成 zod schema),\n * 无参数/参数无类型标注/参数为内联类型字面量时为 `undefined` */\n inputTypeName?: string;\n /** 源码相对路径(从 `pathMeta` 透传) */\n filePath: string;\n /** 源码中的导出函数名(从 `pathMeta` 透传,AST 定位用,不受 `@tool` 覆盖影响) */\n functionName: string;\n}\n\n/**\n * 路径推导的 tool 元数据(由 [scanTools](../tools/scanTools.ts) 计算)\n *\n * 透传到 [ToolMetadata](./extractToolMetadata.ts) 输出,与 AST 提取字段合并。\n */\nexport interface ToolPathMeta {\n /** 路径推导的 tool 名(如 `weather.getWeather`) */\n name: string;\n /** 源码相对路径(如 `src/tools/weather/handler.ts`) */\n filePath: string;\n}\n\n/** 函数节点联合类型(FunctionDeclaration / ArrowFunction / FunctionExpression) */\ntype FunctionLike = ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression;\n\n/**\n * 从 tool handler.ts 提取单个 tool 的元数据\n *\n * 提取内容:\n * 1. **JSDoc 描述** — 注释块自由文本(`@tag` 之前的首段),对 LLM 可见\n * 2. **`@tool` 覆盖名** — JSDoc 中 `@tool` 标签后的文本(如 `customName`),覆盖路径推导的 `name`\n * 3. **第一个参数 interface 名** — 供 [extractTypeInfo](./extractHandlerTypes.ts) 生成 zod schema\n *\n * 不提取(由 `pathMeta` 透传): `filePath` / `functionName`\n *\n * 函数查找支持四种导出形式,与 [scanTools](../tools/scanTools.ts) 的 `TOOL_EXPORT_RE` 正则同构:\n * - `export function name() {}`\n * - `export async function name() {}`\n * - `export const name = () => {}`\n * - `export const name = async () => {}`(以及 `= function () {}`)\n *\n * JSDoc 查找对箭头函数/函数表达式自动回溯到外层 `VariableStatement`\n * (JSDoc 通常写在 `export const` 上方,而非箭头函数本身)。\n *\n * @param program TypeScript Program\n * @param filePath 源文件**绝对路径**(AST 用,需与 `program.getSourceFile` 一致)\n * @param functionName 源码导出函数名(在文件中查找的目标)\n * @param pathMeta 路径推导的元数据(scanTools 已计算)\n * @returns `ToolMetadata` 或 `null`(函数未找到或源文件不在 Program 中)\n */\nexport function extractToolMetadata(\n program: ts.Program,\n filePath: string,\n functionName: string,\n pathMeta: ToolPathMeta,\n): ToolMetadata | null {\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) return null;\n\n const found = findExportedFunction(sourceFile, functionName);\n if (!found) return null;\n\n const { fn, jsDocOwner } = found;\n const jsDoc = getJSDocFromNode(jsDocOwner);\n const description = extractDescription(jsDoc);\n const toolNameOverride = extractJSDocTagValue(jsDoc, 'tool');\n const inputTypeName = getFirstParamTypeName(fn, sourceFile);\n\n return {\n name: toolNameOverride ?? pathMeta.name,\n description,\n inputTypeName,\n filePath: pathMeta.filePath,\n functionName,\n };\n}\n\n/** 函数查找结果:函数节点 + JSDoc 持有节点 */\ninterface FoundFunction {\n /** 函数节点(FunctionDeclaration / ArrowFunction / FunctionExpression) */\n fn: FunctionLike;\n /** JSDoc 持有节点(FunctionDeclaration 时为函数本身;箭头/函数表达式时为外层 VariableStatement) */\n jsDocOwner: ts.Node;\n}\n\n/**\n * 在源文件中查找指定名称的导出函数\n *\n * 遍历顶层节点,匹配四种导出形式。返回函数节点和 JSDoc 持有节点:\n * - `export function name` → 两者都是 FunctionDeclaration 本身\n * - `export const name = () => {}` → fn 是 ArrowFunction,jsDocOwner 是外层 VariableStatement\n * (JSDoc 通常写在 `export const` 上方,而非箭头函数本身)\n *\n * 不依赖 `node.parent` 链——`ts.createProgram` 配置 `noEmit: true` 时不会设置父指针,\n * 故改在遍历时直接绑定 JSDoc 持有节点。\n *\n * 非 export 的同名函数不被识别。\n */\nfunction findExportedFunction(\n sourceFile: ts.SourceFile,\n functionName: string,\n): FoundFunction | null {\n let result: FoundFunction | null = null;\n\n ts.forEachChild(sourceFile, (node) => {\n if (result) return;\n\n // export function name() {} / export async function name() {}\n if (\n ts.isFunctionDeclaration(node) &&\n hasExportModifier(node) &&\n node.name?.text === functionName\n ) {\n result = { fn: node, jsDocOwner: node };\n return;\n }\n\n // export const name = () => {} / export const name = function () {}\n if (ts.isVariableStatement(node) && hasExportModifier(node)) {\n for (const decl of node.declarationList.declarations) {\n if (result) break;\n const nameText = ts.isIdentifier(decl.name)\n ? decl.name.text\n : decl.name.getText(sourceFile);\n if (nameText !== functionName || !decl.initializer) continue;\n if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {\n // JSDoc 通常在 VariableStatement 上(写在 `export const` 上方)\n result = { fn: decl.initializer, jsDocOwner: node };\n }\n }\n }\n });\n\n return result;\n}\n\n/**\n * 提取第一个参数的 interface/type 名(TypeReference)\n *\n * - 参数有 TypeReference 类型标注(如 `input: WeatherInput`)→ `'WeatherInput'`\n * - 参数无类型标注 / 内联类型字面量(如 `input: { city: string }`)→ `undefined`\n * - 函数无参数 → `undefined`\n * - 多参数时取第一个参数的类型名\n *\n * 返回 `undefined` 而非抛错——tool 允许无参数输入或无 schema 校验,\n * 是否生成 `zod.js` 由下游 `generateToolArtifacts` 根据 `inputTypeName` 是否为 `undefined` 决定。\n */\nfunction getFirstParamTypeName(fn: FunctionLike, sourceFile: ts.SourceFile): string | undefined {\n const firstParam = fn.parameters[0];\n if (!firstParam) return undefined;\n if (!firstParam.type) return undefined;\n if (!ts.isTypeReferenceNode(firstParam.type)) return undefined;\n return firstParam.type.typeName.getText(sourceFile);\n}\n","import ts from 'typescript';\n\n/**\n * 判断节点是否有 `export` 修饰符\n */\nexport function hasExportModifier(node: ts.Node): boolean {\n if (!ts.canHaveModifiers(node)) return false;\n const modifiers = ts.getModifiers(node);\n return !!modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);\n}\n\n/**\n * 从节点提取第一个 JSDoc 注释块\n *\n * 1. 标准 API(`ts.getJSDocCommentsAndTags`,jsDocCache 已缓存时命中)\n * 2. 回退:直接访问解析器存入的 `node.jsDoc` 数组(jsDocCache 未同步的情况)\n */\nexport function getJSDocFromNode(node: ts.Node): ts.JSDoc | undefined {\n const apiDocs = ts\n .getJSDocCommentsAndTags(node)\n .filter((entry): entry is ts.JSDoc => ts.isJSDoc(entry));\n if (apiDocs.length > 0) return apiDocs[0];\n\n const directDocs = (node as unknown as { jsDoc?: ts.JSDoc[] }).jsDoc;\n if (directDocs && directDocs.length > 0) return directDocs[0];\n\n return undefined;\n}\n\n/**\n * 提取 JSDoc 描述(注释块自由文本,`@tag` 之前的首段)\n *\n * 多行描述保留换行(TypeScript 已自动剥离每行前缀 ` * `)。\n * 无 JSDoc / JSDoc 无自由文本 / 描述仅空白 / comment 为富文本结构 → `undefined`。\n */\nexport function extractDescription(jsDoc: ts.JSDoc | undefined): string | undefined {\n if (!jsDoc) return undefined;\n if (typeof jsDoc.comment !== 'string') return undefined;\n const trimmed = jsDoc.comment.trim();\n return trimmed || undefined;\n}\n\n/**\n * 提取 `@<tagName>` 标签的覆盖名\n *\n * - `@tool weather.current` / `@agent researcher` → 标签值文本\n * - 花括号包裹(`@tool {name}`)去除花括号\n * - 无指定标签 / 标签无 comment 文本 → `undefined`(调用方回退到路径推导值,不报错)\n */\nexport function extractJSDocTagValue(\n jsDoc: ts.JSDoc | undefined,\n tagName: string,\n): string | undefined {\n if (!jsDoc || !jsDoc.tags) return undefined;\n for (const tag of jsDoc.tags) {\n if (tag.tagName.text !== tagName) continue;\n // tag.comment 类型为 string | false | undefined;false 表示该 tag 无 comment 文本\n if (typeof tag.comment !== 'string') return undefined;\n const text = tag.comment.trim();\n if (!text) return undefined;\n const cleaned = text.replace(/^\\{|\\}$/g, '').trim();\n return cleaned || undefined;\n }\n return undefined;\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 { FaapiMiddleware } from './middlewareTypes';\n\nexport interface CorsOptions {\n origin?: string | string[] | true;\n methods?: string[];\n allowedHeaders?: string[];\n exposeHeaders?: string[];\n credentials?: boolean;\n maxAge?: number;\n}\n\nconst DEFAULT_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];\n\n/**\n * 创建 CORS 中间件(洋葱模型)\n *\n * - origin=true: 允许所有来源(反射请求的 Origin)\n * - origin=string: 允许指定来源\n * - origin=string[]: 允许多个来源\n *\n * OPTIONS 预检请求直接返回 204,不调用 next()。\n */\nexport function cors(options: CorsOptions = {}): FaapiMiddleware {\n const {\n origin = true,\n methods = DEFAULT_METHODS,\n allowedHeaders,\n exposeHeaders,\n credentials = false,\n maxAge,\n } = options;\n\n return async (ctx, next) => {\n const reqOrigin = ctx.headers.get('origin');\n if (!reqOrigin) {\n await next();\n return;\n }\n\n // 确定允许的来源\n let allowOrigin: string | null = null;\n if (origin === true) {\n allowOrigin = reqOrigin; // 反射 Origin\n } else if (typeof origin === 'string') {\n allowOrigin = reqOrigin === origin ? origin : null;\n } else if (Array.isArray(origin)) {\n allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;\n }\n\n // 当 origin 为动态值(true 或数组)时,响应内容(是否带 ACAO)随 Origin 头变化\n // ——不匹配被拒的响应同样必须带 Vary: Origin。缺了它,CDN/浏览器按 URL 缓存\n // \"无 ACAO 的拒绝响应\"后,可能服务给后续合法 Origin 的请求(缓存污染面)\n if (origin === true || Array.isArray(origin)) {\n const existingVary = ctx.headers.get('vary');\n if (existingVary) {\n if (!existingVary.toLowerCase().includes('origin')) {\n ctx.setHeader('Vary', `${existingVary}, Origin`);\n }\n } else {\n ctx.setHeader('Vary', 'Origin');\n }\n }\n\n if (!allowOrigin) {\n await next();\n return;\n }\n\n // 设置 CORS 响应头\n ctx.setHeader('Access-Control-Allow-Origin', allowOrigin);\n\n ctx.setHeader('Access-Control-Allow-Methods', methods.join(', '));\n\n if (allowedHeaders) {\n ctx.setHeader('Access-Control-Allow-Headers', allowedHeaders.join(', '));\n } else {\n // 反射请求头\n const requestHeaders = ctx.headers.get('access-control-request-headers');\n if (requestHeaders) {\n ctx.setHeader('Access-Control-Allow-Headers', requestHeaders);\n }\n }\n\n if (exposeHeaders && exposeHeaders.length > 0) {\n ctx.setHeader('Access-Control-Expose-Headers', exposeHeaders.join(', '));\n }\n\n if (credentials) {\n ctx.setHeader('Access-Control-Allow-Credentials', 'true');\n }\n\n if (maxAge !== undefined) {\n ctx.setHeader('Access-Control-Max-Age', String(maxAge));\n }\n\n // 处理 preflight 请求:直接返回 204,不调用 next()\n if (ctx.method === 'OPTIONS') {\n return new Response(null, { status: 204 });\n }\n\n await next();\n };\n}\n","import type { FaapiMiddleware } from './middlewareTypes';\n\nexport type LoggerFn = (messageOrObj: string | Record<string, unknown>, message?: string) => void;\n\nexport interface LoggerOptions {\n /**\n * 自定义日志函数\n *\n * - 传入 `console.log`(默认):纯文本格式 `GET /api/users 200 12ms`\n * - 传入 pino logger:结构化日志 `logger.info({ method, path, status, durationMs }, 'request completed')`\n * - 传入 winston logger:`logger.info('GET /api/users 200 12ms', { method, path })`\n */\n log?: LoggerFn;\n}\n\n/**\n * 创建请求日志中间件(洋葱模型)\n *\n * 日志格式(文本模式):GET /api/users 200 12ms\n * 错误格式(文本模式):POST /api/users 400 45ms - Error: ...\n *\n * 结构化模式:传入 pino/winston 等 logger 实例时,会自动传递结构化字段。\n *\n * before/after 一体,闭包变量共享开始时间,无需污染 ctx。\n * 错误用 try/catch 捕获,记录后重新抛出(让上层处理)。\n * 成功时从 next() 返回的 Response 读取状态码。\n *\n * log 函数每次请求时读取(options.log ?? console.log),运行时替换 console.log 会生效。\n */\nexport function logger(options: LoggerOptions = {}): FaapiMiddleware {\n return async (ctx, next) => {\n const log = options.log ?? console.log;\n const start = Date.now();\n try {\n const response = await next();\n const duration = Date.now() - start;\n const entry = {\n method: ctx.method,\n path: ctx.path,\n status: response.status,\n durationMs: duration,\n };\n log(entry, `${ctx.method} ${ctx.path} ${response.status} ${duration}ms`);\n return response;\n } catch (err) {\n const duration = Date.now() - start;\n const message = err instanceof Error ? err.message : String(err);\n const status = (err as { statusCode?: number })?.statusCode ?? 500;\n const entry = {\n method: ctx.method,\n path: ctx.path,\n status,\n durationMs: duration,\n error: message,\n };\n log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);\n throw err;\n }\n };\n}\n","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 path from 'node:path';\nimport fs from 'node:fs';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport type { FaapiConfig } from './configTypes';\n\n/**\n * 配置产物文件名(compileConfig 生成)\n */\nconst CONFIG_PRODUCT_FILE = 'faapi-config.js';\n\n/**\n * 加载 faapi 配置文件\n *\n * 统一读取 `<dist>/faapi-config.js` 产物:\n * - dev 模式:`faapi dev` 启动时由 `compileConfig` 生成 `.faapi/faapi-config.js`\n * - prod 模式:`faapi build` 时由 `compileConfig` 生成 `dist/faapi-config.js`\n *\n * 产物由 `compileConfig` 在构建阶段编译,运行时不读源码、不现场编译。\n * 环境变量由 `loadEnv` 从 `.env` 系列文件加载到 `process.env`,配置文件中通过 `process.env.XXX` 读取。\n *\n * - 产物存在 → import 并返回 default\n * - 产物不存在但源码有配置文件 → 抛错(强制 rebuild)\n * - 源码也无配置文件 → 返回 `null`(配置可选)\n *\n * @param rootDir 项目根目录\n * @param dist 产物目录(如 'dist' 或 '.faapi')\n * @returns 配置对象,无配置文件时返回 null\n */\nexport async function loadConfig(\n rootDir: string,\n dist: string,\n): Promise<Partial<FaapiConfig> | null> {\n const configProductPath = path.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);\n\n if (fs.existsSync(configProductPath)) {\n const module = (await importWithCacheBust(configProductPath)) as {\n default?: Partial<FaapiConfig>;\n };\n return module.default ?? {};\n }\n\n // 产物不存在:检查源码是否有配置文件\n const hasSourceConfig =\n fs.existsSync(path.join(rootDir, 'faapi.config.ts')) ||\n fs.existsSync(path.join(rootDir, 'faapi.config.js'));\n\n if (hasSourceConfig) {\n throw new Error(\n `[faapi] ${dist}/${CONFIG_PRODUCT_FILE} 不存在,请先执行 \\`faapi build\\`(或 \\`faapi dev\\`)生成产物。`,\n );\n }\n\n return null;\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * env 决定规则:NODE_ENV || 'development'\n *\n * 调用方应在调 loadEnv 之前自行兜底 NODE_ENV(dev 设 'development',prod 设 'production')。\n */\nfunction resolveEnv(): string {\n return process.env.NODE_ENV || 'development';\n}\n\n/**\n * .env 文件加载顺序(从低到高优先级,后加载的覆盖先加载的)\n *\n * 1. .env — 所有环境共享\n * 2. .env.local — 本地覆盖(不提交 git)\n * 3. .env.{env} — 按环境覆盖\n * 4. .env.{env}.local — 按环境本地覆盖\n */\nfunction getEnvFiles(env: string): string[] {\n return ['.env', '.env.local', `.env.${env}`, `.env.${env}.local`];\n}\n\n/**\n * 解析 .env 文件内容为键值对象\n *\n * 格式:\n * - `KEY=VALUE` 基本格式\n * - `# 注释` 行首注释\n * - `export KEY=VALUE` 支持 export 前缀\n * - 单引号:字面量,不展开变量,不处理转义\n * - 双引号:支持变量展开($VAR / ${VAR})和转义(\\n \\t \\r \\\\ \\\")\n * - 无引号:字面量,` #` 后为行内注释\n *\n * @param content .env 文件内容\n * @param fileVars 本文件之前已解析的变量(用于变量展开)\n * @returns 解析出的键值对象\n */\nfunction parseEnvFile(content: string, fileVars: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n // 兼容 CRLF / LF\n const lines = content.replace(/\\r\\n/g, '\\n').split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n // 空行 / 注释行跳过\n if (!trimmed || trimmed.startsWith('#')) continue;\n\n // 匹配 KEY=VALUE 或 export KEY=VALUE\n // KEY 必须符合 [A-Za-z_][A-Za-z0-9_]*\n const match = /^(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*(.*)$/.exec(trimmed);\n if (!match) continue;\n\n // 正则有两个捕获组,匹配成功则必非空\n const key = match[1]!;\n const rawValue = match[2]!;\n const value = parseValue(rawValue, { ...fileVars, ...result });\n result[key] = value;\n }\n\n return result;\n}\n\n/**\n * 解析单个值的引号/展开/转义\n *\n * - 单引号:字面量(去除首尾单引号)\n * - 双引号:处理转义 + 展开变量\n * - 无引号:字面量,去除行内注释(` #` 后为注释)\n */\nfunction parseValue(raw: string, env: Record<string, string>): string {\n if (raw === '') return '';\n\n // 单引号:字面量(raw 非空已由调用方保证)\n if (raw[0] === \"'\") {\n const end = raw.indexOf(\"'\", 1);\n return end === -1 ? raw.slice(1) : raw.slice(1, end);\n }\n\n // 双引号:转义 + 展开变量(支持 \\\" 转义引号,不作为结束符)\n if (raw[0] === '\"') {\n const match = /^\"((?:\\\\.|[^\"\\\\])*)\"/.exec(raw);\n // 匹配失败时 slice(1) 兜底(无闭合引号)\n const inner = match ? match[1]! : raw.slice(1);\n return expandEscapesAndVars(inner, env);\n }\n\n // 无引号:字面量,处理行内注释\n // ` #` 前必须有空白才算注释;# 紧贴值(如 url#anchor)不算注释\n const commentMatch = /^(.*?)(\\s+#.*)$/.exec(raw);\n const value = commentMatch ? commentMatch[1]! : raw;\n return value.trim();\n}\n\n/**\n * 双引号值:处理转义字符 + 展开变量\n *\n * 转义:\\n \\r \\t \\\\ \\\" (未知转义保留字符本身)\n * 展开:$VAR / ${VAR},优先 env 参数,其次 process.env,未定义返回空字符串\n */\nfunction expandEscapesAndVars(str: string, env: Record<string, string>): string {\n // 先处理转义(避免变量值中的 $ 被二次展开)\n const escaped = str.replace(/\\\\(.)/g, (_, ch: string) => {\n switch (ch) {\n case 'n':\n return '\\n';\n case 'r':\n return '\\r';\n case 't':\n return '\\t';\n case '\\\\':\n return '\\\\';\n case '\"':\n return '\"';\n default:\n return ch; // 未知转义保留字符\n }\n });\n\n // 展开变量:$VAR 或 ${VAR}\n return escaped.replace(\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}|\\$([A-Za-z_][A-Za-z0-9_]*)/g,\n (_, braced, plain) => {\n const varName = braced || plain;\n return env[varName] ?? process.env[varName] ?? '';\n },\n );\n}\n\n/**\n * 加载 `.env` 系列文件到 `process.env`\n *\n * 按 Next.js 约定加载四级文件(从低到高):\n * 1. `.env` — 所有环境共享\n * 2. `.env.local` — 本地覆盖\n * 3. `.env.{env}` — 按环境覆盖\n * 4. `.env.{env}.local` — 按环境本地覆盖\n *\n * env 由 `NODE_ENV || 'development'` 决定。调用方应在调 loadEnv 之前自行兜底 NODE_ENV\n * (dev 设 'development',prod 设 'production')。\n *\n * 合并规则:\n * - 后加载的文件覆盖先加载的同名变量\n * - **shell 已设置的变量不被覆盖**(`process.env` 已有的值优先)\n *\n * @param rootDir 项目根目录(`.env` 文件所在目录)\n */\nexport function loadEnv(rootDir: string): void {\n const env = resolveEnv();\n const files = getEnvFiles(env);\n\n // 收集所有文件解析结果,按优先级合并(后面的覆盖前面的)\n const merged: Record<string, string> = {};\n for (const file of files) {\n const filePath = path.join(rootDir, file);\n if (!fs.existsSync(filePath)) continue;\n const content = fs.readFileSync(filePath, 'utf-8');\n const parsed = parseEnvFile(content, merged);\n Object.assign(merged, parsed);\n }\n\n // 写入 process.env(不覆盖已有的 shell 变量)\n for (const [key, value] of Object.entries(merged)) {\n if (process.env[key] === undefined) {\n process.env[key] = value;\n }\n }\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 { Server } from 'node:http';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { PassThrough, Readable } from 'node:stream';\nimport type { RouteManifest, WsRouteManifest, RoutesRef } from '../router/routeTypes';\nimport { sortRoutes } from '../router/sortRoutes';\nimport { detectRouteConflicts } from '../router/detectRouteConflicts';\nimport { createServer } from '../server/createServer';\nimport { applyPluginWrappers } from '../server/startServer';\nimport { loadConfig } from '../config/loadConfig';\nimport { hydrateRoutes, type SerializedRouteManifest } from './generateRoutes';\nimport { hydrateTools, type SerializedToolRecord } from './generateToolArtifacts';\nimport { hydrateAgents, type SerializedAgentRecord } from './generateAgentArtifacts';\nimport { loadPlugins } from './loadPlugins';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport {\n createAppRegistries,\n defaultRegistries,\n type AppRegistries,\n} from '../injection/registries';\nimport type { ToolMetadata } from '../ast/extractToolMetadata';\nimport type { AgentMetadata } from '../ast/extractAgentMetadata';\nimport type { FaapiConfig } from '../config/configTypes';\nimport { ROUTE_PATTERNS } from '../utils/prodPaths';\n\nexport interface InjectOptions {\n method?: string;\n path?: string;\n headers?: Record<string, string>;\n query?: Record<string, string>;\n body?: unknown;\n}\n\nexport interface InjectResponse {\n status: number;\n headers: Headers;\n body: unknown;\n}\n\n/** 默认产物目录(prod 模式,对应 `faapi build` 默认输出到 `dist`) */\nconst DEFAULT_DIST = 'dist';\n/** 默认端口 */\nconst DEFAULT_PORT = 3000;\n/** 路由清单文件名(build/dev 启动时生成,必需产物) */\nconst ROUTES_FILE = 'faapi-routes.js';\n/** tool 清单文件名(build/dev 启动时生成,可选产物——无 tool 的项目不生成) */\nconst TOOLS_FILE = 'faapi-tools.js';\n/** agent 清单文件名(build/dev 启动时生成,可选产物——无 agent 的项目不生成) */\nconst AGENTS_FILE = 'faapi-agents.js';\n/** 路由源码目录(写死为 src,路由 .ts 文件位于 src/api/ 下) */\n\n/**\n * 加载 faapi-tools.js 并水合到 toolRegistry(单例)\n *\n * 与路由清单不同,tool 是可选能力——纯 API 项目可能没有 `faapi-tools.js`,\n * 此时不报错,返回空数组,toolRegistry 保持空。\n *\n * dev 按需模式下 `reloadTools` 也会调用此函数重新水合(`setLoadTimestamp` 已在外层设置)。\n *\n * @returns 水合后的 ToolMetadata[](供调用方日志/调试)\n */\nexport async function loadAndHydrateTools(\n rootDir: string,\n dist: string,\n registries: AppRegistries = defaultRegistries,\n): Promise<ToolMetadata[]> {\n const toolsPath = path.resolve(rootDir, dist, TOOLS_FILE);\n if (!fs.existsSync(toolsPath)) {\n return [];\n }\n const serialized = (await importWithCacheBust(toolsPath)) as unknown as {\n tools: SerializedToolRecord[];\n };\n const hydrated = hydrateTools(serialized.tools ?? []);\n registries.tool.hydrate(hydrated);\n return hydrated;\n}\n\n/**\n * 加载 faapi-agents.js 并水合到 agentRegistry(单例)\n *\n * 与 `loadAndHydrateTools` 对称——agent 是可选能力,纯 API 项目可能没有 `faapi-agents.js`,\n * 此时不报错,返回空数组,agentRegistry 保持空。\n *\n * dev 按需模式下 `reloadAgents` 也会调用此函数重新水合(`setLoadTimestamp` 已在外层设置)。\n *\n * @returns 水合后的 AgentMetadata[](供调用方日志/调试)\n */\nexport async function loadAndHydrateAgents(\n rootDir: string,\n dist: string,\n registries: AppRegistries = defaultRegistries,\n): Promise<AgentMetadata[]> {\n const agentsPath = path.resolve(rootDir, dist, AGENTS_FILE);\n if (!fs.existsSync(agentsPath)) {\n return [];\n }\n const serialized = (await importWithCacheBust(agentsPath)) as unknown as {\n agents: SerializedAgentRecord[];\n };\n const hydrated = hydrateAgents(serialized.agents ?? []);\n registries.agent.hydrate(hydrated);\n return hydrated;\n}\n\n/**\n * 当前 app 单例的 globalThis key\n *\n * 用 `Symbol.for` 创建全局 symbol,确保跨模块实例共享同一个 key——\n * Next.js 16 默认用 Turbopack 作为 `next dev` 的 bundler,Turbopack dev server runtime\n * 与主进程的 Node.js 原生 module cache 是两套独立缓存,用模块级变量无法跨实例共享,\n * 必须借助 `globalThis`。\n *\n * 注:生产模式(`node dist/main` + `next build`)不受此问题影响——`next build` 产物是\n * 普通 JS 文件,运行时通过 Node.js 原生 require 加载,与主进程共享 module cache。\n * 此机制主要解决 dev 模式下 RSC 调用 `getApp()` 的问题,对生产模式无副作用。\n */\nconst APP_INSTANCE_KEY = Symbol.for('faapi.app.instance');\n\n/**\n * 读取当前 app 单例(从 globalThis 取,跨模块实例共享)\n *\n * 注意:单例仅指向\"最近一次创建且未关闭的 app\"。测试场景下创建多个临时 app 时,\n * 单例会被覆盖,但 close 时只有当单例仍指向当前 app 才置 null,避免被后续 app 误清。\n */\nfunction getCurrentApp(): AppBase | null {\n return (globalThis as Record<symbol, AppBase | undefined>)[APP_INSTANCE_KEY] ?? null;\n}\n\n/** 设置/清除当前 app 单例(写入 globalThis,跨模块实例共享) */\nfunction setCurrentApp(app: AppBase | null): void {\n if (app === null) {\n delete (globalThis as Record<symbol, AppBase | undefined>)[APP_INSTANCE_KEY];\n } else {\n (globalThis as Record<symbol, AppBase | undefined>)[APP_INSTANCE_KEY] = app;\n }\n}\n\n/** 默认优雅关闭信号 handler 是否已安装(写入 globalThis,跨模块实例共享) */\nconst SHUTDOWN_INSTALLED_KEY = Symbol.for('faapi.defaultShutdownInstalled');\n\n/**\n * 注册默认优雅关闭信号(SIGTERM/SIGINT,进程级仅注册一次)\n *\n * 收到信号时关闭当前 app 单例(app.close() 内部完成 drain + onClose 钩子 +\n * 注册表清理)后退出。faapi 单进程单 app 设计——单例即唯一运行中的 app;\n * 测试场景多次 listen 不会堆积 process 监听器(globalThis 标记防重装)。\n */\nfunction registerDefaultShutdownHandlers(): void {\n const g = globalThis as Record<symbol, unknown>;\n if (g[SHUTDOWN_INSTALLED_KEY]) return;\n g[SHUTDOWN_INSTALLED_KEY] = true;\n\n const shutdown = (signal: string): void => {\n console.log(`\\n- Received ${signal}, shutting down...`);\n void (async () => {\n const app = getCurrentApp();\n if (app) await app.close();\n process.exit(0);\n })();\n };\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n process.on('SIGINT', () => shutdown('SIGINT'));\n}\n\n/**\n * 获取当前 faapi app 单例\n *\n * 用于在无法直接拿到 app 引用的场景(如 Next.js Server Component)中访问 app。\n *\n * 通过 `globalThis` 共享单例,确保 Next.js 16 Turbopack dev server runtime 加载的\n * `@faapi/faapi` 模块实例与主进程 `faapi dev` 设置单例的实例能读到同一个 app 引用。\n *\n * @returns 当前 app 实例\n * @throws 未初始化时抛错(需先调 `createProdApp()` / `createDevApp()`,或 `faapi dev` / `node dist/main` 启动)\n *\n * @example\n * ```ts\n * // Next.js RSC 中调用 faapi API(同进程,跳过 HTTP loopback)\n * import { getApp } from '@faapi/faapi';\n * import { headers } from 'next/headers';\n *\n * const app = getApp();\n * const h = await headers();\n * const res = await app.inject({\n * method: 'GET',\n * path: '/api/user',\n * headers: { cookie: h.get('cookie') ?? '', authorization: h.get('authorization') ?? '' },\n * });\n * const data = res.body; // 已解析\n * ```\n */\nexport function getApp(): AppBase {\n const app = getCurrentApp();\n if (!app) {\n throw new Error(\n '[faapi] No app instance. Call createProdApp() / createDevApp() first, or run `faapi dev` / `node dist/main`.',\n );\n }\n return app;\n}\n\n/** FaapiConfig 的内置 key 集合(排除自定义业务配置) */\nconst FAAPI_CONFIG_KEYS = new Set([\n 'cors',\n 'lifecycle',\n 'middlewares',\n 'injectors',\n 'extendContext',\n 'plugins',\n 'helmet',\n 'compression',\n 'etag',\n 'bodyLimit',\n 'logger',\n 'http2',\n 'trustedProxy',\n 'response',\n]);\n\nfunction isFaapiConfigKey(key: string): boolean {\n return FAAPI_CONFIG_KEYS.has(key);\n}\n\nexport interface CreateAppOptions {\n /** 项目根目录,默认 process.cwd() */\n rootDir?: string;\n /** 产物输出目录(如 dist 或 .faapi),覆盖环境变量 FAAPI_DIST,默认 'dist' */\n dist?: string;\n /** 端口号,也可在 listen() 时传入;默认环境变量 PORT 或 3000 */\n port?: number;\n}\n\n/** 应用基础接口(dev/prod 共用,不含 reloadRoutes) */\nexport interface AppBase {\n /** Node.js Server 实例(listen 后可用,close 后置 null) */\n server: Server | null;\n /** app 级注册表(tool/agent/skill/agentHandle 实例,close 时清理) */\n registries: AppRegistries;\n /** 排序后的路由清单 */\n routes: RouteManifest;\n /** WebSocket 路由清单 */\n wsRoutes: WsRouteManifest;\n /** 项目根目录 */\n rootDir: string;\n /** 启动 HTTP server,打印路由表,执行 onReady 钩子 */\n listen(port?: number): Promise<Server>;\n /** 关闭 server,执行 onClose 钩子 */\n close(): Promise<void>;\n /**\n * 无服务器测试注入\n *\n * 构建一个模拟请求直接走完整请求链路(CORS / helmet / logger / 全局中间件 / 路由匹配 /\n * schema 校验 / 目录中间件 / handler),不绑定端口,返回已解析的 `{ status, headers, body }`。\n *\n * `listen()` 前后均可调用——`listen()` 后调用常用于 Next.js Server Component 等同进程场景\n * (配合 `getApp()` 拿到 app 实例)。\n */\n inject(options?: InjectOptions): Promise<InjectResponse>;\n}\n\n/**\n * 内部上下文(供 dev 扩展 reloadRoutes 使用)\n *\n * prod 模式不使用此上下文——createProdApp 直接返回 AppBase。\n */\nexport interface AppContext {\n /** 项目根目录 */\n rootDir: string;\n /** app 级注册表(tool/agent/skill/agentHandle 实例,随 app 生命周期) */\n registries: AppRegistries;\n /** 产物目录 */\n dist: string;\n /** 扫描 patterns(scanRoutes 用) */\n patterns: string[];\n /** Node.js Server 实例(未 listen) */\n server: Server;\n /** 路由可变引用容器(createServer 闭包和 reloadRoutes 共享) */\n routesRef: RoutesRef;\n /** 配置(原始 FaapiConfig 或 null) */\n config: FaapiConfig | null;\n /**\n * 更新路由引用(app.routes/wsRoutes + routesRef + 闭包变量)\n *\n * dev reloadRoutes 调用此方法把新扫描的路由同步到 app 和 server。\n */\n updateRoutes(routes: RouteManifest, wsRoutes: WsRouteManifest): void;\n}\n\n/**\n * 创建应用基础实例(dev/prod 共用逻辑)\n *\n * 完成:配置加载 → 路由清单水合 → 创建 server → 插件加载。\n * 返回 AppBase(listen/close)+ AppContext(供 dev 扩展 reloadRoutes)。\n *\n * dist 由 `process.env.FAAPI_DIST` 决定:\n * - `faapi dev` 启动时固定设为 `.faapi` → 读 dev 产物\n * - `node <dist>/main` 不设 → 默认 `dist`,读 prod 产物\n *\n * 不负责编译 TypeScript——编译由 `faapi dev`(esbuild → `.faapi/`)和 `faapi build`(→ `dist/`)负责。\n * 不负责生成路由清单——`faapi dev`/`faapi build` 启动时生成 `faapi-routes.js`,createAppBase 直接水合。\n */\nexport async function createAppBase(options?: CreateAppOptions): Promise<{\n app: AppBase;\n ctx: AppContext;\n}> {\n const rootDir = options?.rootDir ?? process.cwd();\n const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;\n\n // 校验产物存在性\n const routesPath = path.resolve(rootDir, dist, ROUTES_FILE);\n if (!fs.existsSync(routesPath)) {\n throw new Error(\n `[faapi] ${dist}/${ROUTES_FILE} 不存在,请先执行 \\`faapi build\\`(或 \\`faapi dev\\`)生成产物。`,\n );\n }\n\n // 加载配置(统一读 <dist>/faapi-config.js)\n const config = await loadConfig(rootDir, dist);\n\n // 水合路由清单(统一路径,无 dev/prod 分支)\n const serialized = (await importWithCacheBust(routesPath)) as unknown as SerializedRouteManifest;\n const hydrated = await hydrateRoutes(serialized);\n let sorted = sortRoutes(hydrated.routes);\n let wsRoutes = hydrated.wsRoutes;\n\n // 检测路由冲突\n const conflicts = detectRouteConflicts(sorted);\n if (conflicts.length > 0) {\n for (const conflict of conflicts) {\n console.warn(`! 路由冲突: ${conflict.method} ${conflict.urlPath}`);\n for (const file of conflict.files) {\n console.warn(` - ${file}`);\n }\n }\n }\n\n // app 级注册表(方案 A 实例化):每个 app 持有独立实例,随 app 创建/销毁,\n // 多 app 同进程互不串台。框架链路(请求注入 / 插件 / lifecycle)只读写此实例\n const registries = createAppRegistries();\n\n // 水合 tool 清单(可选产物——无 tool 的项目跳过,tool 注册表保持空)\n const tools = await loadAndHydrateTools(rootDir, dist, registries);\n\n // 水合 agent 清单(可选产物——无 agent 的项目跳过,agent 注册表保持空)\n const agents = await loadAndHydrateAgents(rootDir, dist, registries);\n\n // 自定义业务配置(排除内置 key)\n const pluginConfig: Record<string, unknown> = config\n ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k)))\n : {};\n\n // 创建 server(不 listen)\n const { server, routesRef } = createServer({\n routes: sorted,\n rootDir,\n dist,\n cors: config?.cors ?? true,\n onError: config?.lifecycle?.onError,\n config: (config as Record<string, unknown> | null) ?? undefined,\n wsRoutes,\n middlewares: config?.middlewares,\n injectors: config?.injectors,\n helmet: config?.helmet,\n compression: config?.compression,\n etag: config?.etag,\n logger: config?.logger,\n bodyLimit: config?.bodyLimit,\n http2: config?.http2,\n trustedProxy: config?.trustedProxy,\n registries,\n });\n\n // 加载插件 + 应用 handler/upgrade 包装器(dist 供本地 TS 插件按需编译/产物复用)\n const { handlerWrappers, upgradeWrappers } = await loadPlugins(\n config?.plugins,\n {\n rootDir,\n registries,\n routes: sorted,\n getRoutes: () => sorted,\n server,\n config: pluginConfig,\n },\n rootDir,\n dist,\n );\n applyPluginWrappers(server, handlerWrappers, upgradeWrappers);\n\n // 关闭状态标记(避免重复关闭)\n let closed = false;\n\n const app: AppBase = {\n server: null,\n registries,\n routes: sorted,\n wsRoutes,\n rootDir,\n\n async listen(listenPort?: number): Promise<Server> {\n // 端口优先级:listen() 参数 > options.port > 环境变量 PORT > 默认 3000\n const envPort = process.env.PORT ? Number(process.env.PORT) : undefined;\n const actualPort = listenPort ?? options?.port ?? envPort ?? DEFAULT_PORT;\n\n // onBoot 生命周期钩子(listen 前):启动校验 / DB 迁移等\"失败即不该暴露端口\"的逻辑。\n // 此时 server 已创建但未监听、路由/tool/agent 清单已水合、插件已加载。\n // 抛错 → listen() 以原始错误 reject,server.listen 不会被调用,端口不暴露。\n if (config?.lifecycle?.onBoot) {\n try {\n await config.lifecycle.onBoot({ rootDir, routes: sorted, server, registries });\n console.log('- onBoot hook executed');\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`[faapi] onBoot hook failed: ${message}`);\n throw err;\n }\n }\n\n return new Promise<Server>((resolve, reject) => {\n // listen 阶段错误(端口占用等)转为 Promise reject,避免未监听 'error'\n // 事件以裸堆栈崩掉进程;成功后解除,运行期错误语义不变\n const onListenError = (err: Error): void => {\n if ((err as NodeJS.ErrnoException).code === 'EADDRINUSE') {\n reject(\n new Error(\n `Port ${actualPort} is already in use. ` +\n `Is another faapi instance running? Change the port via the PORT env var.`,\n ),\n );\n return;\n }\n reject(err);\n };\n server.once('error', onListenError);\n server.listen(actualPort, async () => {\n server.off('error', onListenError);\n const address = server.address();\n const p = typeof address === 'object' && address !== null ? address.port : actualPort;\n\n console.log('faapi server started');\n console.log(`- Local: http://localhost:${p}`);\n console.log('- Loaded routes:');\n for (const route of sorted) {\n console.log(` ${route.method.padEnd(6)}${route.urlPath} ${route.filePath}`);\n }\n if (wsRoutes.length > 0) {\n console.log('- WebSocket routes:');\n for (const route of wsRoutes) {\n console.log(` WS ${route.urlPath} ${route.filePath}`);\n }\n }\n if (tools.length > 0) {\n console.log(`- Loaded ${tools.length} tool(s):`);\n for (const tool of tools) {\n console.log(` ${tool.name} ${tool.filePath}`);\n }\n }\n if (agents.length > 0) {\n console.log(`- Loaded ${agents.length} agent(s):`);\n for (const agent of agents) {\n const exports = agent.hasRun ? 'run' : '-';\n console.log(` ${agent.name} [${exports}] ${agent.filePath}`);\n }\n }\n\n // 注册默认优雅关闭信号(进程级仅注册一次,faapi 单进程单 app 设计)\n registerDefaultShutdownHandlers();\n\n // onReady 生命周期钩子\n if (config?.lifecycle?.onReady) {\n await config.lifecycle.onReady({ rootDir, routes: sorted, server, registries });\n console.log('- onReady hook executed');\n }\n\n app.server = server;\n resolve(server);\n });\n });\n },\n\n async inject(injectOpts?: InjectOptions): Promise<InjectResponse> {\n const {\n method = 'GET',\n path: reqPath = '/',\n headers: reqHeaders = {},\n query,\n body,\n } = injectOpts ?? {};\n\n const queryStr = query\n ? '?' +\n new URLSearchParams(Object.entries(query).map(([k, v]) => [k, String(v)])).toString()\n : '';\n\n return new Promise<InjectResponse>((resolve, reject) => {\n // mockRes 需为 Writable Stream(PassThrough)以支持 sendNodeResponse 的 pipe 路径\n // (handler 返回值被自动包裹后产生 JSON body,走 nodeStream.pipe(res))\n const chunks: Buffer[] = [];\n const mockRes = new PassThrough() as PassThrough & {\n statusCode: number;\n _headers: Record<string, string>;\n setHeader(name: string, value: string): void;\n appendHeader(name: string, value: string): void;\n writeHead(status: number, headers?: Record<string, string>): void;\n };\n mockRes.statusCode = 200;\n mockRes._headers = {};\n mockRes.setHeader = function (name: string, value: string) {\n this._headers[name.toLowerCase()] = value;\n };\n mockRes.appendHeader = function (name: string, value: string) {\n const key = name.toLowerCase();\n const existing = this._headers[key];\n this._headers[key] = existing ? `${existing}, ${value}` : value;\n };\n mockRes.writeHead = function (status: number, headers?: Record<string, string>) {\n this.statusCode = status;\n if (headers) {\n Object.assign(this._headers, headers);\n }\n };\n\n mockRes.on('data', (chunk: Buffer) => chunks.push(chunk));\n mockRes.on('error', reject);\n mockRes.on('finish', () => {\n const body = Buffer.concat(chunks);\n let parsed: unknown;\n try {\n parsed = JSON.parse(body.toString());\n } catch {\n parsed = body.toString();\n }\n resolve({\n status: mockRes.statusCode,\n headers: new Headers(mockRes._headers as Record<string, string>),\n body: parsed,\n });\n });\n\n const listeners = server.listeners('request');\n const handler = listeners[listeners.length - 1];\n if (typeof handler !== 'function') {\n reject(new Error('No request handler found'));\n return;\n }\n\n const mockReq: Readable & {\n method?: string;\n url?: string;\n headers?: Record<string, string | undefined>;\n socket?: { remoteAddress?: string };\n } = Readable.from(body !== undefined ? [Buffer.from(JSON.stringify(body))] : []);\n mockReq.method = method;\n mockReq.url = `${reqPath}${queryStr}`;\n mockReq.headers = {\n ...reqHeaders,\n host: 'localhost',\n 'content-type': body !== undefined ? 'application/json' : undefined,\n };\n mockReq.socket = { remoteAddress: '127.0.0.1' };\n\n handler(\n mockReq as unknown as import('node:http').IncomingMessage,\n mockRes as unknown as import('node:http').ServerResponse,\n );\n });\n },\n\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n\n const s = server as unknown as {\n closeIdleConnections?: () => void;\n closeAllConnections?: () => void;\n };\n\n // 停止接受新连接 + 断开空闲 keep-alive 连接;在途请求继续执行直至完成(drain)。\n // 此前 closeAllConnections 在等待之前调用,会直接掐断在途请求(硬关,非优雅停机)\n s.closeIdleConnections?.();\n\n if (config?.lifecycle?.onClose) {\n await config.lifecycle.onClose({ rootDir, routes: sorted, server, registries });\n }\n\n // 注册表(方案 A 实例化):清理的是 app 自己的实例——多 app 同进程\n // 天然隔离,无需所有权守卫;全局默认实例不被 app 生命周期触碰\n registries.tool.clear();\n registries.agent.clear();\n registries.skill.clear();\n registries.agentHandle.clear();\n\n // server 未 listen 时直接清理状态(避免 ERR_SERVER_NOT_RUNNING 错误)\n if (!server.listening) {\n app.server = null;\n // 清理单例(仅当单例仍指向当前 app 时,避免被后续 app 误清)\n if (getCurrentApp() === app) setCurrentApp(null);\n return;\n }\n\n const drained = new Promise<void>((resolve) => {\n server.close((err) => {\n if (err) console.error('Error closing server:', err);\n resolve();\n });\n });\n\n // drain:等在途请求完成。SSE/WS 长连接永不主动结束,超时后强制断开\n // (默认 10s,FAAPI_SHUTDOWN_TIMEOUT_MS 环境变量可调)\n const drainTimeoutMs = Number(process.env.FAAPI_SHUTDOWN_TIMEOUT_MS ?? 10_000);\n if (\n typeof s.closeAllConnections === 'function' &&\n Number.isFinite(drainTimeoutMs) &&\n drainTimeoutMs >= 0\n ) {\n const forceClose = new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n s.closeAllConnections?.();\n resolve();\n }, drainTimeoutMs);\n // 停机定时器不应阻止进程自然退出\n timer.unref?.();\n });\n await Promise.race([drained, forceClose]);\n // 强制断开后 close 回调随即触发;短兜底等待收尾完成\n const tail = new Promise<void>((resolve) => setTimeout(resolve, 250).unref?.());\n await Promise.race([drained, tail]);\n } else {\n await drained;\n }\n\n app.server = null;\n // 清理单例(仅当单例仍指向当前 app 时,避免被后续 app 误清)\n if (getCurrentApp() === app) setCurrentApp(null);\n },\n };\n\n // 设置单例(覆盖之前的实例;测试场景下多次创建会覆盖,close 时只清自己)\n // 通过 globalThis 存储,确保 Next.js Turbopack dev server runtime 加载的模块实例也能读到\n setCurrentApp(app);\n\n /** 更新路由引用(app + routesRef + 闭包变量) */\n const ctx: AppContext = {\n rootDir,\n registries,\n dist,\n patterns: ROUTE_PATTERNS,\n server,\n routesRef,\n config,\n updateRoutes(newRoutes: RouteManifest, newWsRoutes: WsRouteManifest): void {\n sorted = newRoutes;\n wsRoutes = newWsRoutes;\n app.routes = newRoutes;\n app.wsRoutes = newWsRoutes;\n routesRef.current = newRoutes;\n routesRef.wsCurrent = newWsRoutes;\n },\n };\n\n return { app, ctx };\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 type { RouteManifest } from './routeTypes';\n\nexport interface RouteConflict {\n method: string;\n urlPath: string;\n files: string[];\n}\n\n/**\n * 检测路由冲突(相同 method + urlPath 的多个文件)\n */\nexport function detectRouteConflicts(routes: RouteManifest): RouteConflict[] {\n const map = new Map<string, RouteConflict>();\n\n for (const route of routes) {\n const key = `${route.method} ${route.urlPath}`;\n const existing = map.get(key);\n if (existing) {\n existing.files.push(route.filePath);\n } else {\n map.set(key, {\n method: route.method,\n urlPath: route.urlPath,\n files: [route.filePath],\n });\n }\n }\n\n // 只返回有冲突的(文件数 > 1)\n const conflicts: RouteConflict[] = [];\n for (const conflict of map.values()) {\n if (conflict.files.length > 1) {\n conflicts.push(conflict);\n }\n }\n\n return conflicts;\n}\n","import {\n createServer as createHttpServer,\n type Server,\n type IncomingMessage,\n type ServerResponse,\n} from 'node:http';\nimport { createSecureServer as createHttp2SecureServer } from 'node:http2';\nimport { readFileSync } from 'node:fs';\nimport { Readable } from 'node:stream';\nimport path from 'node:path';\nimport type { RouteManifest, RouteMatch, WsRouteManifest, RoutesRef } from '../router/routeTypes';\nimport { matchRoute, findAllowedMethods } from '../router/matchRoute';\nimport { loadRouteModule } from '../loader/loadRouteModule';\nimport { createContextFromUrl } from '../runtime/createContext';\nimport { resolveInputFromUrl, resolveBodyForQueryMethod } from '../runtime/resolveInput';\nimport { invokeHandler, compose, mergeMeta } from '../runtime/invokeHandler';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { sendNodeResponse } from '../response/sendNodeResponse';\nimport {\n RouteNotFoundError,\n MethodNotAllowedError,\n ValidationError,\n PayloadTooLargeError,\n} from '../errors/httpErrors';\nimport { validateInput } from '../validator/validateInput';\nimport { getInputTypeForMethod, hasBody } from '../runtime/inputType';\nimport { getClientIp } from '../utils/getClientIp';\nimport { cors, type CorsOptions } from '../middleware/cors';\nimport { helmet, type HelmetOptions } from '../middleware/helmet';\nimport { compression, type CompressionOptions } from '../middleware/compression';\nimport { etag, type EtagOptions } from '../middleware/etag';\nimport type { AppRegistries } from '../injection/registries';\nimport { logger as loggerMiddleware, type LoggerOptions } from '../middleware/logger';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { attachWebSocket } from './handleWsUpgrade';\nimport { nodeHttpToWebHeaders, buildErrorResponse } from './serverUtils';\nimport { getRuntimeSchemaPath } from '../cli/generateSchemaFiles';\nimport {\n ensureSchemaGenerated,\n ensureMiddlewaresCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n} from '../cli/compileOnDemand';\nimport { loadMergedMiddlewares } from '../middleware/loadMiddlewares';\n\n/**\n * 将 Node.js IncomingMessage 转为 Web Request 对象\n *\n * 协议判断:\n * 1. 优先使用 X-Forwarded-Proto 头(反向代理场景)\n * 2. 回退到 http(HTTPS 由外部代理处理)\n */\nconst DEFAULT_BODY_LIMIT = 10 * 1024 * 1024; // 10MB\n\nfunction toWebRequest(\n req: IncomingMessage,\n bodyLimit: number = DEFAULT_BODY_LIMIT,\n requestSignal?: AbortSignal,\n): {\n request: Request;\n /** 已解析的 URL(pathname/searchParams 由调用方复用,免重复 new URL) */\n url: URL;\n} {\n // 协议判断:优先 X-Forwarded-Proto(反向代理),否则 http\n const forwardedProto = req.headers['x-forwarded-proto'];\n const protocol = Array.isArray(forwardedProto)\n ? (forwardedProto[0]?.split(',')[0]?.trim() ?? 'http')\n : (forwardedProto?.split(',')[0]?.trim() ?? 'http');\n const host = req.headers.host ?? 'localhost';\n // 全请求唯一一次 URL 解析——pathname/searchParams 由 ctx/routePipeline 复用\n const url = new URL(req.url ?? '/', `${protocol}://${host}`);\n\n const headers = nodeHttpToWebHeaders(req);\n\n const method = req.method ?? 'GET';\n\n // GET/HEAD 不应该有 body\n if (method === 'GET' || method === 'HEAD') {\n return {\n request: new Request(url.toString(), { method, headers, signal: requestSignal }),\n url,\n };\n }\n\n // content-length 快速判定:声明长度超限直接抛 PayloadTooLargeError,\n // 免去流包装 + 逐 chunk 读取(chunked 无此头,仍走 limitStreamSize 流式限流)\n const contentLength = req.headers['content-length'];\n if (contentLength !== undefined) {\n const declared = Number(Array.isArray(contentLength) ? contentLength[0] : contentLength);\n if (Number.isFinite(declared) && declared > bodyLimit) {\n throw new PayloadTooLargeError(bodyLimit);\n }\n }\n\n // 将 Node.js IncomingMessage 转为 Web ReadableStream\n // 并限制请求体大小(防止 DoS)\n const stream = Readable.toWeb(req) as ReadableStream<Uint8Array>;\n const limitedStream = limitStreamSize(stream, bodyLimit);\n return {\n request: new Request(url.toString(), {\n method,\n headers,\n body: limitedStream,\n duplex: 'half',\n signal: requestSignal,\n } as RequestInit),\n url,\n };\n}\n\n/**\n * 限制 ReadableStream 的总字节数,超过限制时通过 controller.error 抛 PayloadTooLargeError\n *\n * 错误传播路径:controller.error → Request body 读取方(resolveInput)抛 →\n * handleRequest catch → formatErrorResponse(命中 PayloadTooLargeError 分支)→ 413 响应\n *\n * 健壮性处理:\n * - reader.read() 抛错(客户端断开等)→ controller.error + releaseLock,避免泄漏\n * - 超限或异常后释放 reader lock,避免悬挂引用\n * - 取消时同步 cancel 上游 reader\n */\nfunction limitStreamSize(\n stream: ReadableStream<Uint8Array>,\n maxSize: number,\n): ReadableStream<Uint8Array> {\n let totalSize = 0;\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n let errored = false;\n\n const releaseReader = (): void => {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // 锁已释放或 reader 已 closed,忽略\n }\n reader = undefined;\n }\n };\n\n const failStream = (\n controller: ReadableStreamDefaultController<Uint8Array>,\n err: unknown,\n ): void => {\n if (errored) return;\n errored = true;\n controller.error(err instanceof Error ? err : new Error(String(err)));\n releaseReader();\n };\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (!reader) reader = stream.getReader();\n try {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n releaseReader();\n return;\n }\n totalSize += value.byteLength;\n if (totalSize > maxSize) {\n // error 让流进入 errored 状态,下游 read() 会 reject\n failStream(controller, new PayloadTooLargeError(maxSize));\n return;\n }\n controller.enqueue(value);\n } catch (err) {\n // reader.read() 抛错(客户端断开、底层流异常等)\n failStream(controller, err);\n }\n },\n cancel(reason) {\n if (reader) {\n try {\n reader.cancel(reason);\n } catch {\n // 忽略上游 cancel 失败\n }\n releaseReader();\n }\n },\n });\n}\n\n/**\n * 查找路径的所有允许方法(用于 405 响应)\n *\n * 实现移至 [matchRoute](../router/matchRoute.ts)(共享路由索引:静态段 O(1) 直查,\n * 仅动态段线性扫描)。404 非热路径,但扫描器/探活探测的 404 高频场景下\n * 索引化仍有收益。\n */\n\nexport interface CreateServerOptions {\n routes: RouteManifest;\n rootDir: string;\n /** 产物输出目录(如 '.faapi' 或 'dist'),用于计算 schema 路径 */\n dist: string;\n cors?: CorsOptions | boolean;\n /** 请求错误钩子(在错误响应生成后调用,用于副作用;不修改已发出的响应) */\n onError?: (error: unknown, ctx: FaapiContext) => Promise<void> | void;\n /** 自定义业务配置(来自 faapi.config.ts,注入到 ctx.config) */\n config?: Record<string, unknown>;\n /** WebSocket 路由清单(空数组则不挂载 WS 支持) */\n wsRoutes?: WsRouteManifest;\n /** 全局中间件(来自 faapi.config.ts,对所有路由生效,最外层) */\n middlewares?: FaapiMiddleware[];\n /** 全局注入器(来自 faapi.config.ts,对所有路由 handler 参数注入生效) */\n injectors?: InjectorMap;\n /** 安全头配置 */\n helmet?: HelmetOptions | boolean;\n /** 响应压缩(gzip/deflate/br 协商),默认关闭,详见 middleware/compression.md */\n compression?: CompressionOptions | boolean;\n /** ETag/304 条件请求协商,默认关闭,详见 middleware/etag.md */\n etag?: EtagOptions | boolean;\n /** app 级注册表(tool/agent/skill/agentHandle)——经 FaapiContext 进入请求链路 */\n registries?: AppRegistries;\n /** 请求日志配置,默认启用(与 cors 一致) */\n logger?: LoggerOptions | boolean;\n /** 请求体大小限制(字节) */\n bodyLimit?: number;\n /** HTTP/2 配置,启用时需提供 SSL 证书路径 */\n http2?: Http2Options | boolean;\n /**\n * 是否信任反向代理头(X-Forwarded-For),默认 false\n *\n * true 时 `ctx.ip` 取 `x-forwarded-for` 第一个 IP(nginx/CDN 场景);\n * false(默认)时直取 socket 地址——直连部署下 XFF 可被伪造,安全默认不信任。\n */\n trustedProxy?: boolean;\n}\n\nexport interface Http2Options {\n key?: string;\n cert?: string;\n}\n\n/**\n * 创建 faapi HTTP server\n *\n * @param options 路由清单、根目录\n * @returns Node.js Server 实例\n */\nexport function createServer(options: CreateServerOptions): {\n server: Server;\n routesRef: RoutesRef;\n} {\n const {\n routes,\n rootDir,\n dist,\n cors: corsOption,\n onError,\n config,\n wsRoutes,\n middlewares: globalMiddlewares,\n injectors: globalInjectors,\n helmet: helmetOption,\n compression: compressionOption,\n etag: etagOption,\n registries,\n logger: loggerOption,\n bodyLimit = DEFAULT_BODY_LIMIT,\n http2: http2Option,\n trustedProxy = false,\n } = options;\n\n // 路由可变引用容器(watch 模式热替换时 reloadRoutes 更新 .current/.wsCurrent)\n const routesRef: RoutesRef = { current: routes, wsCurrent: wsRoutes ?? [] };\n\n // Build middleware chain from config options\n const configMiddlewares: FaapiMiddleware[] = [];\n\n // Compression — 显式启用;链最外层,包住完整链路使最终响应被压缩\n if (compressionOption) {\n const compOpts = typeof compressionOption === 'object' ? compressionOption : {};\n configMiddlewares.push(compression(compOpts));\n }\n\n // CORS\n const corsMiddleware: FaapiMiddleware | null =\n corsOption === false\n ? null\n : corsOption === true || corsOption === undefined\n ? cors()\n : cors(corsOption);\n if (corsMiddleware) configMiddlewares.push(corsMiddleware);\n\n // Helmet — enabled only when explicitly configured\n if (helmetOption) {\n const helmOpts = typeof helmetOption === 'object' ? helmetOption : {};\n configMiddlewares.push(helmet(helmOpts));\n }\n\n // Logger — 默认启用(与 cors 一致),false 禁用,LoggerOptions 自定义\n const loggerMiddlewareInst: FaapiMiddleware | null =\n loggerOption === false\n ? null\n : loggerOption === true || loggerOption === undefined\n ? loggerMiddleware()\n : loggerMiddleware(loggerOption);\n if (loggerMiddlewareInst) configMiddlewares.push(loggerMiddlewareInst);\n\n // ETag — 显式启用;位于 compression 内层:先算 ETag/304 再压缩,\n // 弱 ETag 基于未压缩表示计算(304 无 body 时压缩自动跳过)\n if (etagOption) {\n const etagOpts = typeof etagOption === 'object' ? etagOption : {};\n configMiddlewares.push(etag(etagOpts));\n }\n\n // 外层中间件链启动期组装一次(CORS → helmet → logger → 全局),\n // 每请求不再重复 spread 重组数组\n const outerMiddlewares: FaapiMiddleware[] = [...configMiddlewares];\n if (globalMiddlewares && globalMiddlewares.length > 0) {\n outerMiddlewares.push(...globalMiddlewares);\n }\n\n const server = ((): Server => {\n if (http2Option) {\n const h2Opts = typeof http2Option === 'object' ? http2Option : {};\n return createHttp2SecureServer({\n key: h2Opts.key ? readFileSync(h2Opts.key) : undefined,\n cert: h2Opts.cert ? readFileSync(h2Opts.cert) : undefined,\n allowHTTP1: true,\n }) as unknown as Server;\n }\n return createHttpServer();\n })();\n\n server.on('request', (req: IncomingMessage, res: ServerResponse) => {\n // 每次请求读取最新的路由状态(支持 watch 模式热更新)\n const currentRoutes = routesRef.current;\n\n handleRequest(\n currentRoutes,\n rootDir,\n dist,\n req,\n res,\n outerMiddlewares,\n onError,\n config,\n globalInjectors,\n bodyLimit,\n trustedProxy,\n registries,\n ).catch(() => {\n res.statusCode = 500;\n res.end();\n });\n });\n\n // 挂载 WebSocket 升级处理(无条件挂载:upgrade 处理器内部对无匹配路由返回 404,\n // 空清单成本一次函数调用。若按初始清单条件挂载,dev watch 中新增第一个 WS 路由后\n // upgrade 监听器不会补挂,WS 路由永远 404)\n attachWebSocket({\n server,\n routesRef,\n rootDir,\n config,\n globalMiddlewares,\n trustedProxy,\n registries,\n });\n\n return { server, routesRef };\n}\n\n/**\n * 准备请求上下文:Node IncomingMessage → Web Request + FaapiContext\n *\n * 提取为独立函数,让 handleRequest 主流程聚焦于路由 + 中间件调度,\n * 便于单测与未来扩展(如自定义 context 字段来源)。\n *\n * URL 全请求只解析一次(toWebRequest 内),pathname/searchParams\n * 由 ctx / routePipeline 共享,避免一次请求重复 new URL 3~4 次。\n */\nfunction prepareRequest(\n req: IncomingMessage,\n config: Record<string, unknown> | undefined,\n bodyLimit: number,\n trustedProxy: boolean,\n registries?: AppRegistries,\n requestSignal?: AbortSignal,\n): {\n request: Request;\n url: URL;\n ctx: FaapiContext;\n meta: ResponseMeta;\n method: string;\n urlPath: string;\n} {\n const { request, url } = toWebRequest(req, bodyLimit, requestSignal);\n const method = request.method.toUpperCase();\n const urlPath = url.pathname;\n const ctx = createContextFromUrl(\n request,\n url,\n {},\n config,\n getClientIp(req, trustedProxy),\n registries,\n );\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n return { request, url, ctx, meta, method, urlPath };\n}\n\n/**\n * 路由匹配——命中返回 MatchResult,未命中抛 RouteNotFoundError / MethodNotAllowedError\n *\n * 抛错语义让调用方 try/catch 即可,无需在主流程里分支处理 null。\n */\nfunction resolveRouteOrThrow(routes: RouteManifest, method: string, urlPath: string): RouteMatch {\n const match = matchRoute(routes, method, urlPath);\n if (match) return match;\n\n // 检查是否有其他方法匹配该路径 → 405\n const allowedMethods = findAllowedMethods(routes, urlPath);\n if (allowedMethods.length > 0) {\n throw new MethodNotAllowedError(method, urlPath, allowedMethods);\n }\n\n // 路由未匹配 → 404\n throw new RouteNotFoundError(urlPath);\n}\n\n/**\n * 路由执行管线:作为外层中间件链的 finalHandler\n *\n * 包含:路由匹配 → handler.js 加载 → 参数解析 → zod.js 按需生成 + 校验 →\n * 中间件按需加载 → 注入器合并 → handler 调用。\n *\n * 错误抛出由外层 handleRequest 的 try/catch 接管,经 formatErrorResponse 转换为响应。\n */\n// 路由静态派生路径缓存(绝对路径 / schema 路径对固定 route 恒定,免每请求字符串运算)。\n// WeakMap 按 route 对象弱键——reloadRoutes 整体替换清单后旧对象可 GC,无需手动失效\nconst routePathCache = new WeakMap<\n RouteManifest[number],\n { absFilePath: string; schemaPath: string }\n>();\n\nfunction getRoutePaths(\n route: RouteManifest[number],\n rootDir: string,\n dist: string,\n): { absFilePath: string; schemaPath: string } {\n let cached = routePathCache.get(route);\n if (!cached) {\n cached = {\n absFilePath: path.resolve(rootDir, route.filePath),\n schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir),\n };\n routePathCache.set(route, cached);\n }\n return cached;\n}\n\nfunction createRoutePipeline(opts: {\n routes: RouteManifest;\n method: string;\n urlPath: string;\n url: URL;\n ctx: FaapiContext;\n request: Request;\n rootDir: string;\n dist: string;\n globalInjectors: InjectorMap | undefined;\n}): () => Promise<Response> {\n const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;\n return async () => {\n // 1. 路由匹配(未命中抛 RouteNotFound / MethodNotAllowed)\n const match = resolveRouteOrThrow(routes, method, urlPath);\n ctx.params = match.params;\n const { route } = match;\n const { absFilePath, schemaPath } = getRoutePaths(route, rootDir, dist);\n\n // 2. 加载 handler.js(dev 按需编译 + import,prod 直接 import)\n const routeModule = await loadRouteModule(absFilePath, route.method, rootDir);\n\n // 3. 参数解析(query / body / form / files 等)——复用已解析的 URL\n const input = await resolveInputFromUrl(route.method, request, url);\n\n // 4. schema 校验(运行时按 route.filePath 计算 zod.js 路径 + safeParse)\n const inputType = getInputTypeForMethod(route.method);\n // schemaPath 已在 getRoutePaths 中缓存\n\n // Dev 按需模式:zod.js 不存在或 stale 时触发按需生成\n if (isDevOnDemandEnabled()) {\n const devDist = getDevDist();\n if (devDist) {\n await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist);\n }\n }\n\n const result = await validateInput(schemaPath, route.method, inputType, input);\n if (!result.valid) {\n throw new ValidationError('参数校验失败', result.issues);\n }\n // body 计算与主输入分流:\n // - POST/PUT/PATCH:主输入就是 body,用校验后的值\n // - DELETE:主输入是 query(校验 DELETEQuery),body 单独解析注入——\n // 若把校验后的 query 当 body 传入,handler 声明 body 会静默拿到 query;\n // 同时请求体流不被消费,keep-alive 连接无法复用\n // - GET/HEAD:无 body\n const body =\n inputType === 'query' && hasBody(route.method)\n ? await resolveBodyForQueryMethod(request)\n : hasBody(route.method)\n ? result.data\n : undefined;\n\n // 5. 中间件按需加载(Vite 风格):route.middlewares 为 undefined 时从 middlewarePaths 加载\n // 首次请求加载后缓存到 route 上,后续请求直接复用\n if (route.middlewares === undefined && route.injectors === undefined && route.middlewarePaths) {\n // dev 按需模式:先编译中间件源码(含依赖闭包)再 import\n await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);\n const bundle = await loadMergedMiddlewares(route.middlewarePaths);\n if (bundle) {\n route.middlewares = bundle.middlewares;\n route.injectors = bundle.injectors;\n } else {\n // 标记为已加载(空中间件),避免重复加载\n route.middlewares = [];\n route.injectors = {};\n }\n }\n\n // 6. 注入器合并:全局注入器为基线,目录注入器覆盖同名\n const mergedInjectors = globalInjectors\n ? { ...globalInjectors, ...route.injectors }\n : route.injectors;\n\n // 7. handler 调用(含目录中间件洋葱模型 + 自动响应包装)\n return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);\n };\n}\n\n/**\n * 发送成功响应\n */\nasync function sendSuccessResponse(response: Response, res: ServerResponse): Promise<void> {\n await sendNodeResponse(response, res);\n}\n\n/**\n * 发送错误响应 + 触发 onError 副作用\n *\n * 错误处理兜底链(参考 Fastify 语义):\n * 1. 框架内置 formatErrorResponse 兜底(handler 抛错时)——读 ctx.config.response.fail\n * 自定义包装函数,确保错误格式与 ctx.fail() 主动错误响应一致\n * 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n * 3. 响应发出后 → onError 触发副作用(不修改已发出的响应)\n * 注:业务方如需进一步自定义错误响应,在全局中间件中 try/catch next() 即可\n *\n * `ctx` 可为 undefined——请求准备阶段抛错(如 content-length 超限)时尚未构造 ctx,\n * 此时错误格式用默认 fail 包装,onError 不触发(与原\"裸 500\"路径一致)。\n */\nasync function sendErrorResponse(\n err: unknown,\n meta: ResponseMeta,\n res: ServerResponse,\n onError: ((error: unknown, ctx: FaapiContext) => Promise<void> | void) | undefined,\n ctx: FaapiContext | undefined,\n): Promise<void> {\n await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx?.config), meta), res);\n\n // 响应已发出,触发 onError 副作用(日志/告警/链路追踪),自身抛错被忽略\n if (onError && ctx) {\n try {\n await onError(err, ctx);\n } catch {\n // onError 自身抛错不影响已发出的响应\n }\n }\n}\n\nasync function handleRequest(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n req: IncomingMessage,\n res: ServerResponse,\n outerMiddlewares: FaapiMiddleware[],\n onError: ((error: unknown, ctx: FaapiContext) => Promise<void> | void) | undefined,\n config: Record<string, unknown> | undefined,\n globalInjectors: InjectorMap | undefined,\n bodyLimit: number,\n trustedProxy: boolean,\n registries?: AppRegistries,\n): Promise<void> {\n // meta/ctx 兜底:请求准备阶段抛错(如 content-length 超限的 413)时尚无 ctx\n let meta: ResponseMeta = { headers: {}, setCookies: [] };\n let ctx: FaapiContext | undefined;\n // 客户端断连信号:每请求一个 AbortController,signal 进入 Request(ctx.request.signal)\n // res 'close' 在响应正常完成(keep-alive)时也会触发,用 writableEnded 区分——\n // 响应未写完连接就断开(客户端提前断连)才 abort,正常完成不误触发\n const abortController = new AbortController();\n res.on('close', () => {\n if (!res.writableEnded) abortController.abort();\n });\n try {\n // 1. 准备请求上下文(toWebRequest + createContext)——URL 全请求只解析一次\n const prepared = prepareRequest(\n req,\n config,\n bodyLimit,\n trustedProxy,\n registries,\n abortController.signal,\n );\n ctx = prepared.ctx;\n meta = prepared.meta;\n const { request, url, method, urlPath } = prepared;\n\n // 2. 创建路由执行管线(路由匹配 + 校验 + 中间件加载 + handler 调用)\n const routePipeline = createRoutePipeline({\n routes,\n method,\n urlPath,\n url,\n ctx,\n request,\n rootDir,\n dist,\n globalInjectors,\n });\n\n // 3. 执行外层中间件链(CORS → helmet → logger → 全局 → routePipeline,\n // 数组已在 createServer 启动期组装,此处仅按需 compose)\n const response =\n outerMiddlewares.length > 0\n ? await compose(outerMiddlewares, ctx, routePipeline)\n : await routePipeline();\n // 4. 发送响应\n await sendSuccessResponse(response, res);\n } catch (err: unknown) {\n // 客户端已断开或响应已完成:网络中断/连接销毁不是服务端错误,\n // 不向已销毁的连接写 500(写入无效),也不触发 onError 误报\n if (res.destroyed || res.writableEnded) return;\n await sendErrorResponse(err, meta, res, onError, ctx);\n }\n}\n","import type { RouteManifest, RouteMatch, WsRouteManifest, WsRouteMatch } from './routeTypes';\n\n/**\n * 路由匹配索引——把 O(n) 线性扫描降为静态路由 O(1) 命中\n *\n * 每个请求都要做路由匹配,原实现逐条遍历清单(静态路由字符串比较、动态路由\n * split('/') 比对),大清单下是热路径开销。索引把清单分为两部分:\n * - **静态路由**:`method|urlPath` → route 的 Map,一次查找命中\n * - **动态路由**:保持清单顺序的数组(`sortRoutes` 保证静态恒在动态之前,\n * 且同一 `(method, path)` 至多一条静态路由,静态优先查与原 first-match\n * 语义完全等价)\n *\n * 索引按**清单数组身份**缓存(WeakMap):`reloadRoutes` / `hydrateRoutes`\n * 整体替换清单数组(`routesRef.current = newRoutes`),旧数组被替换后索引\n * 自动失效可被 GC,无需手动清理——无生命周期侵入。\n *\n * 详见 [matchRoute.md](./matchRoute.md)。\n */\n\n/**\n * HTTP 路由匹配索引\n */\ninterface HttpRoutesIndex {\n /** 静态路由:`method|urlPath` → route */\n static: Map<string, RouteManifest[number]>;\n /** 静态路径 → 方法集合(405 findAllowedMethods 反查用,免再扫静态段) */\n methodsByStaticPath: Map<string, Set<string>>;\n /** 动态路由(含 catch-all),保持清单顺序,pattern 已预编译 */\n dynamics: DynamicEntry[];\n}\n\n/** 动态路由条目(pattern segments 预编译,免去每请求对固定 pattern 重复 split) */\ninterface DynamicEntry {\n route: RouteManifest[number];\n /** 预编译的模式段(pattern.split('/').filter(Boolean),对固定 route 恒定) */\n segments: string[];\n}\n\n/** WS 路由匹配索引(无 HTTP 方法维度) */\ninterface WsRoutesIndex {\n /** 静态路由:urlPath → route */\n static: Map<string, WsRouteManifest[number]>;\n /** 动态路由(含 catch-all),保持清单顺序 */\n dynamics: WsRouteManifest;\n}\n\nconst httpIndexCache = new WeakMap<RouteManifest, HttpRoutesIndex>();\nconst wsIndexCache = new WeakMap<WsRouteManifest, WsRoutesIndex>();\n\n/** 构建 HTTP 路由索引(惰性,每个清单数组只构建一次) */\nfunction getHttpIndex(routes: RouteManifest): HttpRoutesIndex {\n let index = httpIndexCache.get(routes);\n if (index) return index;\n\n index = { static: new Map(), methodsByStaticPath: new Map(), dynamics: [] };\n for (const route of routes) {\n if (route.isDynamic) {\n index.dynamics.push({\n route,\n segments: route.urlPath.split('/').filter(Boolean),\n });\n } else {\n index.static.set(`${route.method}|${route.urlPath}`, route);\n let methods = index.methodsByStaticPath.get(route.urlPath);\n if (!methods) {\n methods = new Set();\n index.methodsByStaticPath.set(route.urlPath, methods);\n }\n methods.add(route.method);\n }\n }\n\n httpIndexCache.set(routes, index);\n return index;\n}\n\n/** 构建 WS 路由索引(惰性,每个清单数组只构建一次) */\nfunction getWsIndex(routes: WsRouteManifest): WsRoutesIndex {\n let index = wsIndexCache.get(routes);\n if (index) return index;\n\n index = { static: new Map(), dynamics: [] };\n for (const route of routes) {\n if (route.isDynamic) {\n index.dynamics.push(route);\n } else {\n index.static.set(route.urlPath, route);\n }\n }\n\n wsIndexCache.set(routes, index);\n return index;\n}\n\n/**\n * 根据请求路径和方法匹配路由\n *\n * HEAD 回退:无显式 HEAD 路由时复用 GET handler(Node ServerResponse 对 HEAD\n * 自动丢弃 body)。探活、CDN 健康检查、HTTP 客户端预检常用 HEAD——没有回退时\n * 只定义 GET 的路由对 HEAD 返回 405,监控大面积误报。\n *\n * @param routes 已排序的路由清单\n * @param method HTTP 方法\n * @param path 请求路径\n * @returns 匹配结果,包含路由记录和参数;无匹配返回 null\n */\nexport function matchRoute(routes: RouteManifest, method: string, path: string): RouteMatch | null {\n const index = getHttpIndex(routes);\n const upper = method.toUpperCase();\n\n const hit = matchByMethod(index, upper, path);\n if (hit) return hit;\n\n if (upper === 'HEAD') {\n return matchByMethod(index, 'GET', path);\n }\n\n return null;\n}\n\n/** 按方法匹配(静态 O(1) → 动态线性),matchRoute 内部复用(HEAD 回退 GET) */\nfunction matchByMethod(index: HttpRoutesIndex, method: string, path: string): RouteMatch | null {\n // 静态路由 O(1) 命中(sortRoutes 保证静态优先,等价于原遍历的首个命中)\n const staticHit = index.static.get(`${method}|${path}`);\n if (staticHit) {\n return { route: staticHit, params: {} };\n }\n\n // 动态路由按清单顺序匹配(pattern segments 已预编译)\n for (const entry of index.dynamics) {\n const route = entry.route;\n if (route.method !== method) {\n continue;\n }\n const params = matchSegments(entry.segments, path, route.paramNames, route.isCatchAll);\n if (params !== null) {\n return { route, params };\n }\n }\n\n return null;\n}\n\n/**\n * 匹配 WebSocket 路由(无 HTTP 方法维度)\n *\n * WS 路由只按路径匹配,协议升级时调用。\n * @param wsRoutes WebSocket 路由清单\n * @param path 请求路径\n */\nexport function matchWsRoute(wsRoutes: WsRouteManifest, path: string): WsRouteMatch | null {\n const index = getWsIndex(wsRoutes);\n\n const staticHit = index.static.get(path);\n if (staticHit) {\n return { route: staticHit, params: {} };\n }\n\n for (const route of index.dynamics) {\n const params = matchDynamicPath(route.urlPath, path, route.paramNames, route.isCatchAll);\n if (params !== null) {\n return { route, params };\n }\n }\n\n return null;\n}\n\n/**\n * 查找路径的所有允许方法(用于 405 响应)\n *\n * 静态段直接查索引(O(1)),仅动态段线性扫描——原实现对全部路由做\n * O(n) 遍历 + 静态路径比较,404/405 高频场景(扫描器、探活探测)开销显著。\n */\nexport function findAllowedMethods(routes: RouteManifest, path: string): string[] {\n const index = getHttpIndex(routes);\n const methods = new Set<string>();\n\n // 静态路由:索引直查\n const staticMethods = index.methodsByStaticPath.get(path);\n if (staticMethods) {\n for (const method of staticMethods) {\n methods.add(method);\n }\n }\n\n // 动态路由:线性扫描(segments 已预编译)\n for (const entry of index.dynamics) {\n const params = matchSegments(\n entry.segments,\n path,\n entry.route.paramNames,\n entry.route.isCatchAll,\n );\n if (params !== null) {\n methods.add(entry.route.method);\n }\n }\n\n // RFC 9110:GET 允许时 HEAD 必然允许(matchRoute 有 HEAD→GET 回退),\n // 405 的 Allow 头如实反映,避免客户端误判 HEAD 不可用\n if (methods.has('GET')) {\n methods.add('HEAD');\n }\n\n return Array.from(methods);\n}\n\n/**\n * 动态路径匹配\n * 将路由模式(如 /user/:id)与请求路径(如 /user/123)匹配\n * 支持 catch-all 路由(如 /shop/:...slug 匹配 /shop/clothes/tops)\n * 返回提取的参数对象,不匹配返回 null\n */\nexport function matchDynamicPath(\n pattern: string,\n path: string,\n paramNames: string[],\n isCatchAll?: boolean,\n): Record<string, string> | null {\n return matchSegments(pattern.split('/').filter(Boolean), path, paramNames, isCatchAll);\n}\n\n/**\n * 基于**预编译 segments** 的动态匹配(热路径内部用)\n *\n * 与 matchDynamicPath 语义一致,但模式段由索引构建期一次性 split,\n * 免去每请求对固定 pattern 重复字符串分割\n */\nfunction matchSegments(\n patternSegments: string[],\n path: string,\n paramNames: string[],\n isCatchAll?: boolean,\n): Record<string, string> | null {\n const pathSegments = path.split('/').filter(Boolean);\n\n // catch-all 路由:最后一个模式段为 :...slug\n if (isCatchAll) {\n // catch-all 前面的静态/动态段必须匹配\n // catch-all 段至少匹配一个路径段\n const nonCatchAllCount = patternSegments.length - 1;\n if (pathSegments.length <= nonCatchAllCount) {\n return null;\n }\n\n const params: Record<string, string> = {};\n\n for (let i = 0; i < nonCatchAllCount; i++) {\n // i < nonCatchAllCount ≤ 两数组长度,索引必然存在\n const patternSeg = patternSegments[i]!;\n const pathSeg = pathSegments[i]!;\n\n if (patternSeg.startsWith(':')) {\n const paramName = patternSeg.slice(1);\n params[paramName] = pathSeg;\n } else if (patternSeg !== pathSeg) {\n return null;\n }\n }\n\n // catch-all 段:剩余所有路径段用 / 连接\n const catchAllValue = pathSegments.slice(nonCatchAllCount).join('/');\n // 前置检查 pathSegments.length > nonCatchAllCount 保证 catch-all 段存在\n const catchAllParamName = patternSegments[nonCatchAllCount]!.slice(4); // 去掉 ':...'\n params[catchAllParamName] = catchAllValue;\n\n if (Object.keys(params).length !== paramNames.length) {\n return null;\n }\n\n return params;\n }\n\n // 普通动态路由:段数必须一致\n if (patternSegments.length !== pathSegments.length) {\n return null;\n }\n\n const params: Record<string, string> = {};\n\n for (let i = 0; i < patternSegments.length; i++) {\n // 段数一致性检查后索引必然存在\n const patternSeg = patternSegments[i]!;\n const pathSeg = pathSegments[i]!;\n\n if (patternSeg.startsWith(':')) {\n // 动态段,提取参数值\n const paramName = patternSeg.slice(1);\n params[paramName] = pathSeg;\n } else if (patternSeg !== pathSeg) {\n // 静态段不匹配\n return null;\n }\n }\n\n // 确保所有参数名都被提取到\n if (Object.keys(params).length !== paramNames.length) {\n return null;\n }\n\n return params;\n}\n","/**\n * 校验导出值是否为合法的 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","/**\n * SSE(Server-Sent Events)支持\n *\n * 让 handler 能向客户端推送流式事件,用于 LLM token 流、进度通知等场景。\n *\n * 核心导出:\n * - `encodeSseEvent(event)`:把 SSE 事件对象编码为符合 HTML5 SSE 规范的字符串\n * - `createSseWriter()`:创建一个 SseWriter,封装 ReadableStream + Response,提供 send/close/sendError API\n * - `SseWriter`:writer 类型,ctx.sse() 返回此类型\n *\n * 设计要点:\n * - writer 内部用 ReadableStream + TextEncoder,send 时 enqueue,close 时 close controller\n * - response 预设 text/event-stream、no-cache、keep-alive 头,状态码默认 200\n * - close 后再 send 静默忽略,避免 handler 异步流程中误写已关闭的流\n * - sendError 向流写入 event: error 后关闭,用于流式输出中报错的优雅终止\n *\n * 与 ctx 的集成:\n * - ctx.sse() 调用 createSseWriter(),并把 response 缓存到 ctx 内部字段\n * - invokeHandler 在 handler 返回后检查 ctx 是否持有 SSE response,有则优先使用\n */\n\n/**\n * SSE 事件字段\n *\n * 遵循 HTML5 SSE 规范:\n * - `data`:消息数据,多行时每行加 `data: ` 前缀;对象自动 JSON.stringify\n * - `event`:事件类型,客户端可用 addEventListener(event) 监听\n * - `id`:事件 ID,客户端断线重连时通过 Last-Event-ID 头发送\n * - `retry`:重连等待时间(毫秒)\n * - `comment`:注释行(以 `:` 开头),用于 keep-alive 心跳,不传递给客户端消息\n */\nexport interface SseEvent {\n /** 消息数据。字符串原样输出;对象自动 JSON.stringify;多行时每行加 data: 前缀 */\n data?: unknown;\n /** 事件类型,客户端可用 addEventListener 监听 */\n event?: string;\n /** 事件 ID,客户端断线重连时通过 Last-Event-ID 头发送 */\n id?: string | number;\n /** 重连等待时间(毫秒) */\n retry?: number;\n /** 注释行(以 : 开头),用于 keep-alive 心跳 */\n comment?: string;\n}\n\n/**\n * 把 SSE 事件对象编码为符合 HTML5 SSE 规范的字符串\n *\n * 字段顺序固定:comment > event > id > retry > data,末尾空行分隔事件。\n *\n * @param event SSE 事件对象\n * @returns 编码后的字符串(含末尾空行)\n */\nexport function encodeSseEvent(event: SseEvent): string {\n let out = '';\n\n // comment(注释行,以 : 开头)\n if (event.comment !== undefined) {\n out += `: ${event.comment}\\n`;\n }\n\n // event\n if (event.event !== undefined) {\n out += `event: ${event.event}\\n`;\n }\n\n // id\n if (event.id !== undefined) {\n out += `id: ${event.id}\\n`;\n }\n\n // retry\n if (event.retry !== undefined) {\n out += `retry: ${event.retry}\\n`;\n }\n\n // data:对象 JSON.stringify,多行每行加前缀\n if (event.data !== undefined) {\n let dataStr: string;\n if (typeof event.data === 'string') {\n dataStr = event.data;\n } else if (event.data === null) {\n dataStr = 'null';\n } else {\n dataStr = JSON.stringify(event.data);\n }\n // 多行 data:每行加 data: 前缀\n const lines = dataStr.split('\\n');\n for (const line of lines) {\n out += `data: ${line}\\n`;\n }\n }\n\n // 空行结束事件\n out += '\\n';\n return out;\n}\n\n/**\n * SSE writer:封装流式推送 API\n *\n * 通过 `ctx.sse()` 创建,handler 调用 `send` 推送事件,`close` 关闭流。\n * 框架在 handler 返回后,自动使用 writer.response 作为 HTTP 响应。\n */\nexport interface SseWriter {\n /** 推送一个 SSE 事件 */\n send(event: SseEvent): void;\n /**\n * 直接写入原始字节/字符串,不做任何 SSE 序列化\n *\n * 用于透传上游已有的 SSE 原文(如 LLM 中转平台逐 chunk 转发 OpenAI 响应)。\n * 调用方负责保证内容符合 HTML5 SSE 规范;`send` 会再次加 `data: ` 前缀,\n * 不适用于原文透传场景。\n *\n * 接受 string 或 Uint8Array(Buffer 是 Uint8Array 子类,自然兼容)。\n * 与 `send` 一致:close/aborted 后静默忽略,不抛错。\n */\n sendRaw(chunk: string | Uint8Array): void;\n /** 推送一个 error 事件并关闭流(用于流式输出中报错的优雅终止) */\n sendError(error: unknown): void;\n /** 关闭流(多次调用安全) */\n close(): void;\n /** 流是否已关闭(handler 主动 close 或框架自动 close) */\n readonly closed: boolean;\n /** 客户端是否已断开(ReadableStream 被 cancel) */\n readonly aborted: boolean;\n /** 对应的 HTTP Response(由框架使用,用户一般不需要直接访问) */\n readonly response: Response;\n}\n\n/**\n * 创建一个 SSE writer\n *\n * 内部用 ReadableStream + TextEncoder 实现,send 时把编码后的事件 enqueue 到流,\n * close 时关闭 controller。response 预设标准 SSE 头。\n *\n * aborted 检测:监听 ReadableStream 的 cancel 钩子,客户端断开(cancel)时置为 true。\n * 此时 send 静默忽略,handler 可通过 writer.aborted 退出循环。\n */\nexport function createSseWriter(): SseWriter {\n const encoder = new TextEncoder();\n let controller: ReadableStreamDefaultController<Uint8Array> | null = null;\n let closed = false;\n let aborted = false;\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c;\n },\n cancel() {\n // 客户端断开连接(cancel ReadableStream)\n aborted = true;\n closed = true;\n controller = null;\n },\n });\n\n const response = new Response(stream, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n\n const writer: SseWriter = {\n send(event: SseEvent): void {\n if (closed || !controller) return;\n const text = encodeSseEvent(event);\n controller.enqueue(encoder.encode(text));\n },\n\n sendRaw(chunk: string | Uint8Array): void {\n if (closed || !controller) return;\n const bytes = typeof chunk === 'string' ? encoder.encode(chunk) : chunk;\n controller.enqueue(bytes);\n },\n\n sendError(error: unknown): void {\n if (closed || !controller) return;\n const message = error instanceof Error ? error.message : String(error);\n const text = encodeSseEvent({ event: 'error', data: message });\n try {\n controller.enqueue(encoder.encode(text));\n } finally {\n writer.close();\n }\n },\n\n close(): void {\n if (closed) return;\n closed = true;\n if (controller) {\n try {\n controller.close();\n } catch {\n // controller 可能已关闭,忽略\n }\n controller = null;\n }\n },\n\n get closed(): boolean {\n return closed;\n },\n\n get aborted(): boolean {\n return aborted;\n },\n\n get response(): Response {\n return response;\n },\n };\n\n return writer;\n}\n","import type { ResponseConfig } from '../config/configTypes';\nimport { FaapiError } from '../errors/FaapiError';\nimport { ValidationError, MethodNotAllowedError, PayloadTooLargeError } from '../errors/httpErrors';\n\n/**\n * 统一响应格式化中心\n *\n * 集中所有响应包装规则,确保 handler return 值的自动包裹、ctx.fail() 主动错误响应、\n * formatErrorResponse 抛错兜底 三条路径共享同一套 ok/fail 函数,避免响应格式漂移。\n *\n * 路径分工:\n * - 成功路径: handler `return data` → wrapOkResult → toResponse 多类型序列化\n * - 显式成功: handler `return ctx.ok(data)` → wrapOkResult → jsonOk 构造 Response\n * - 主动错误: handler `return ctx.fail({...})` → formatFailResponse → jsonOk\n * - 抛错兜底: handler throw → formatErrorResponse(err, config) → jsonOk\n *\n * 业务方通过 faapi.config.ts 的 `response.ok` / `response.fail` 自定义包装函数,\n * 三条路径自动一致生效。\n */\n\n/** 默认 ok 包装:(data) => ({ data }) */\nexport function defaultOk(data: unknown): Record<string, unknown> {\n return { data };\n}\n\n/**\n * 默认 fail 包装:省略的字段不放入 error 对象\n *\n * `code` 为 undefined 时不放入响应,保持 body 紧凑。\n */\nexport function defaultFail(e: {\n status?: number;\n code?: string;\n message: string;\n}): Record<string, unknown> {\n const error: Record<string, unknown> = { message: e.message };\n if (e.code !== undefined) error.code = e.code;\n return { error };\n}\n\n/** 从 ctx.config 读取 ResponseConfig(业务方在 faapi.config.ts 中配置的 response 字段) */\nexport function getResponseConfig(\n config: Record<string, unknown> | undefined,\n): ResponseConfig | undefined {\n return (config as { response?: ResponseConfig } | undefined)?.response;\n}\n\n/** 解析 ok 函数:业务方自定义 ?? 框架默认 */\nexport function resolveOkFn(config: Record<string, unknown> | undefined) {\n return getResponseConfig(config)?.ok ?? defaultOk;\n}\n\n/** 解析 fail 函数:业务方自定义 ?? 框架默认 */\nexport function resolveFailFn(config: Record<string, unknown> | undefined) {\n return getResponseConfig(config)?.fail ?? defaultFail;\n}\n\n/**\n * 构造 JSON 成功响应(不包裹,原样序列化)\n *\n * 用于 ctx.json / ctx.ok 等显式响应场景。\n */\nexport function jsonOk(body: unknown, status = 200, extraHeaders?: HeadersInit): Response {\n return jsonRaw(body, status, extraHeaders);\n}\n\n/** 内部:构造 JSON Response,不包外层 */\nfunction jsonRaw(body: unknown, status: number, extraHeaders?: HeadersInit): Response {\n const headers = new Headers({ 'Content-Type': 'application/json' });\n if (extraHeaders) {\n const extra = new Headers(extraHeaders);\n extra.forEach((value, key) => headers.set(key, value));\n }\n return new Response(JSON.stringify(body), { status, headers });\n}\n\n/**\n * 成功响应自动包装:handler return 值 → okFn(data) 包裹\n *\n * 规则:\n * - Response 对象:不包裹(ctx.ok/ctx.fail/ctx.json 等返回的 Response 原样透传)\n * - 其他值(含 null/undefined):用 config.response.ok(或默认 (data) => ({ data })) 包裹\n *\n * 注:null/undefined 也会被包裹为 { data: null } / { data: undefined }。\n *\n * 不构造 Response,只返回包装后的值——最终序列化交给 toResponse 处理(支持多类型分发 +\n * 合并 ctx.meta 的 setStatus/setHeader/setCookie)。\n */\nexport function wrapOkResult(\n result: unknown,\n config: Record<string, unknown> | undefined,\n): unknown {\n if (result instanceof Response) return result;\n return resolveOkFn(config)(result);\n}\n\n/**\n * 业务方主动错误响应:FailOptions → Response\n *\n * handler `return ctx.fail({ status, code, message })` 的实现。\n * 用 config.response.fail(或默认实现)包装为 `{ error: { ... } }` 结构。\n *\n * `status` 省略时默认 500;`code` 省略时由 fail 函数决定是否放入 body。\n */\nexport function formatFailResponse(\n options: { status?: number; code?: string; message: string },\n config: Record<string, unknown> | undefined,\n): Response {\n const failFn = resolveFailFn(config);\n const body = failFn({\n status: options.status,\n code: options.code,\n message: options.message,\n });\n return jsonOk(body, options.status ?? 500);\n}\n\n/**\n * 抛错兜底响应:把 handler 抛出的错误转换为统一格式的 Response\n *\n * 覆盖的 FaapiError 子类:\n * - ValidationError → 400/422 + issues\n * - MethodNotAllowedError → 405 + Allow header\n * - PayloadTooLargeError → 413\n * - 其他 FaapiError 子类 → 用 error.statusCode\n * - 未知 Error / 非 Error → 500 INTERNAL_ERROR\n *\n * 与 [formatFailResponse] 共享同一套 fail 函数:业务方在 faapi.config.ts 自定义的\n * `response.fail` 会同时应用到「主动错误响应」和「抛错兜底响应」,确保错误格式一致。\n *\n * @param error handler 抛出的错误\n * @param config 业务方配置(用于读取 response.fail 自定义包装函数,可选)\n */\nexport function formatErrorResponse(\n error: unknown,\n config?: Record<string, unknown> | undefined,\n): Response {\n const failFn = resolveFailFn(config);\n\n if (error instanceof ValidationError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n // ValidationError 需要附加 issues 字段。业务方 failFn 可能返回共享/复用对象\n // (性能优化写法)或冻结对象——就地改写会让 issues 跨请求残留污染或静默失败,\n // 因此浅拷贝后再扩展,不改写业务方返回的原对象\n const bodyObj =\n typeof body === 'object' && body !== null\n ? ({ ...(body as Record<string, unknown>) } as Record<string, unknown>)\n : { error: body };\n const existingError = bodyObj.error as Record<string, unknown> | undefined;\n if (existingError && typeof existingError === 'object') {\n bodyObj.error = { ...existingError, issues: error.issues };\n } else if (typeof body === 'object' && body !== null) {\n // 无 error 包装层:body 本身就是错误对象\n bodyObj.issues = error.issues;\n } else {\n bodyObj.error = { issues: error.issues };\n }\n return jsonOk(bodyObj, error.statusCode);\n }\n\n if (error instanceof MethodNotAllowedError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode, { Allow: error.allowedMethods.join(', ') });\n }\n\n if (error instanceof PayloadTooLargeError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode);\n }\n\n if (error instanceof FaapiError) {\n const body = failFn({\n status: error.statusCode,\n code: error.code,\n message: error.message,\n });\n return jsonOk(body, error.statusCode);\n }\n\n // 未知错误:500 INTERNAL_ERROR\n const message = error instanceof Error ? error.message : 'An unknown error occurred';\n const body = failFn({ status: 500, code: 'INTERNAL_ERROR', message });\n return jsonOk(body, 500);\n}\n","import type { FaapiContext, ResponseMeta, CookieOptions, FailOptions } from './contextTypes';\nimport { createSseWriter, type SseWriter } from './sse';\nimport { wrapOkResult, formatFailResponse, jsonOk } from '../response/responseFormatter';\n\n/**\n * 解析 Cookie 请求头为 Map\n */\nfunction parseCookies(cookieHeader: string): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n for (const pair of cookieHeader.split(';')) {\n const [name, ...rest] = pair.split('=');\n const trimmed = name?.trim();\n if (trimmed) {\n cookies.set(trimmed, rest.join('=').trim());\n }\n }\n return cookies;\n}\n\n/**\n * 格式化 Set-Cookie 值\n */\nfunction formatSetCookie(name: string, value: string, options?: CookieOptions): string {\n let cookie = `${name}=${value}`;\n if (options?.domain) cookie += `; Domain=${options.domain}`;\n if (options?.path) cookie += `; Path=${options.path}`;\n if (options?.maxAge !== undefined) cookie += `; Max-Age=${options.maxAge}`;\n if (options?.expires) cookie += `; Expires=${options.expires.toUTCString()}`;\n if (options?.httpOnly) cookie += `; HttpOnly`;\n if (options?.secure) cookie += `; Secure`;\n if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;\n return cookie;\n}\n\n/**\n * 从 Request 对象创建 FaapiContext\n * @param request Web Request 对象\n * @param params 动态路由参数\n * @param config 自定义业务配置(来自 faapi.config.ts)\n * @param ip 客户端 IP(由调用方从 IncomingMessage 提取,HTTP/WS 握手均通过 utils/getClientIp)\n *\n * ua 不作为参数传入:User-Agent 是标准 HTTP 请求头,createContext 内部直接从\n * request.headers 读取(与 ip 不同,ip 需要从 IncomingMessage 提取故由调用方传入)。\n */\nexport function createContext(\n request: Request,\n params: Record<string, string>,\n config: Record<string, unknown> = {},\n ip: string = '',\n registries?: FaapiContext['registries'],\n): FaapiContext {\n return createContextFromUrl(request, new URL(request.url), params, config, ip, registries);\n}\n\n/**\n * createContext 的热路径变体:URL 由调用方解析一次后传入\n *\n * createServer 每请求需要 pathname/searchParams,若各自 `new URL(request.url)`\n * 一次请求要重复解析 3~4 次。此变体接收已解析的 URL 对象,searchParams 与\n * 调用方共享(只读)。普通场景用 {@link createContext}。\n */\nexport function createContextFromUrl(\n request: Request,\n url: URL,\n params: Record<string, string>,\n config: Record<string, unknown> = {},\n ip: string = '',\n registries?: FaapiContext['registries'],\n): FaapiContext {\n const meta: ResponseMeta = { headers: {}, setCookies: [] };\n const parsedCookies = parseCookies(request.headers.get('cookie') ?? '');\n const cookiesObj: Record<string, string> = {};\n for (const [key, val] of parsedCookies) {\n cookiesObj[key] = val;\n }\n\n const ctx = {\n request,\n params,\n query: url.searchParams,\n headers: request.headers,\n method: request.method,\n path: url.pathname,\n ip,\n ua: request.headers.get('user-agent') ?? '',\n cookies: cookiesObj,\n config,\n meta,\n\n setStatus(status: number) {\n meta.status = status;\n },\n\n setHeader(key: string, value: string) {\n meta.headers[key] = value;\n },\n\n setETag(value: string) {\n meta.headers['etag'] = value;\n },\n\n redirect(url: string, status = 302): Response {\n return new Response(null, {\n status,\n headers: { Location: url },\n });\n },\n\n json(data: unknown, status?: number): Response {\n return jsonOk(data, status ?? 200);\n },\n\n html(html: string, status?: number): Response {\n const headers: Record<string, string> = { 'Content-Type': 'text/html; charset=utf-8' };\n return new Response(html, {\n status: status ?? 200,\n headers,\n });\n },\n\n getCookie(name: string): string | undefined {\n return parsedCookies.get(name);\n },\n\n setCookie(name: string, value: string, options?: CookieOptions): void {\n meta.setCookies.push(formatSetCookie(name, value, options));\n },\n\n deleteCookie(name: string): void {\n meta.setCookies.push(formatSetCookie(name, '', { maxAge: 0 }));\n },\n\n /**\n * 创建 SSE writer,用于流式推送事件\n *\n * handler 调用此方法后,通过返回的 writer 推送事件,框架自动把 writer.response\n * 作为 HTTP 响应(Content-Type: text/event-stream)。\n *\n * 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。\n */\n sse(): SseWriter {\n const writer = createSseWriter();\n const ctxWithSse = ctx as FaapiContext & {\n __sseResponse?: Response;\n __sseWriter?: SseWriter;\n };\n ctxWithSse.__sseResponse = writer.response;\n ctxWithSse.__sseWriter = writer;\n return writer;\n },\n\n /**\n * 显式包装成功响应(返回 Response,不会被自动包裹再次包装)\n *\n * 实现委托给 [responseFormatter.wrapOkResult](../response/responseFormatter.ts),\n * 与 handler `return data` 走的自动包裹路径共享同一套 ok 函数。\n *\n * 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。\n * handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。\n */\n ok(data: unknown): Response {\n const body = wrapOkResult(data, config);\n // wrapOkResult 对 Response 对象直接透传(不会包裹),ctx.ok 接收到的是 okFn(data) 的对象\n // 业务方不应在 ctx.ok 里传 Response,这里 fallback 调 jsonOk 序列化\n return jsonOk(body, 200);\n },\n\n /**\n * 返回错误响应(对象形式参数,status 和 code 均可省略)\n *\n * 实现委托给 [responseFormatter.formatFailResponse](../response/responseFormatter.ts),\n * 与 formatErrorResponse(handler 抛错兜底)共享同一套 fail 函数,确保错误格式一致。\n *\n * - status 省略时 HTTP 状态码默认 500\n * - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)\n * - status 和 code 独立无关联\n *\n * body 用 config.response.fail(或默认实现)包装。\n */\n fail(options: FailOptions): Response {\n return formatFailResponse(options, config);\n },\n } as FaapiContext & { meta: ResponseMeta; __sseResponse?: Response; __sseWriter?: SseWriter };\n\n // app 级注册表(方案 A 实例化)——框架请求链路的 tool/agent/agentHandle 读取来源\n if (registries) {\n ctx.registries = registries;\n }\n\n // 执行用户自定义的 ctx 扩展钩子(config.extendContext)\n const extend = config?.extendContext;\n if (typeof extend === 'function') {\n extend(ctx);\n }\n\n return ctx;\n}\n\n/**\n * 测试专用:从选项对象创建 FaapiContext,免去手写 `new Request(url)` 的样板代码\n *\n * 与 createContext 的关系:createTestContext 内部构造 Request 后调 createContext,\n * 语义完全一致,仅是测试场景的语法糖——不写无意义的 host、query 用对象形式、headers 直接传对象。\n *\n * 为什么不合并进 createContext:createContext 运行时也从真实 HTTP 请求构造 Request,\n * 保持 `(request: Request)` 签名使运行时与测试同构;createTestContext 是纯测试便捷封装,\n * 不引入运行时分支。\n *\n * body 不在此处理:createContext 本身不读 `request.body`,body 注入由 `invokeHandler`\n * 的第 3 个参数负责。POST/PUT/PATCH 测试时 body 单独传给 invokeHandler,避免在两处传 body 产生混淆。\n *\n * @param options 请求选项(path 必填,其余可选)\n * @returns FaapiContext\n */\nexport function createTestContext(options: CreateTestContextOptions): FaapiContext {\n const {\n method = 'GET',\n path,\n query,\n headers,\n params = {},\n config = {},\n ip = '',\n registries,\n } = options;\n\n // 用 URL 解析 + 拼 query,避免手动拼接字符串的转义问题\n const url = new URL(`http://localhost${path}`);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (Array.isArray(value)) {\n for (const item of value) {\n url.searchParams.append(key, String(item));\n }\n } else {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const request = new Request(url.toString(), {\n method,\n headers: headers as HeadersInit | undefined,\n });\n\n return createContext(request, params, config, ip, registries);\n}\n\n/**\n * createTestContext 的选项\n */\nexport interface CreateTestContextOptions {\n /** app 级注册表(测试 handler 声明 agent/agents 参数时注入用,可选) */\n registries?: FaapiContext['registries'];\n /** 请求方法,默认 'GET' */\n method?: string;\n /** 请求路径,必填,如 '/api/user'(无需写 host) */\n path: string;\n /** 查询参数,对象形式,自动拼接到 URL(值会被 String() 转换;数组生成同名多值参数) */\n query?: Record<string, string | number | boolean | Array<string | number | boolean>>;\n /** 请求头 */\n headers?: Record<string, string>;\n /** 动态路由参数,默认 {} */\n params?: Record<string, string>;\n /** 业务配置(来自 faapi.config.ts),默认 {} */\n config?: Record<string, unknown>;\n /** 客户端 IP,默认 '' */\n ip?: string;\n}\n","/**\n * URLSearchParams → query 对象\n *\n * 重复 key 聚合为数组(对齐 Express qs / Hono getAll 语义):\n * - `?page=1` → `{ page: '1' }`\n * - `?tag=a&tag=b` → `{ tag: ['a', 'b'] }`\n *\n * 此前 last-wins 静默丢弃前面的值(`?ids=1&ids=2` 得 `'2'`),且 query schema\n * 的 coerce 管线对 array 元素同样生成 preprocess——声明 `ids: number[]` 的字段\n * 此前永远校验失败(解析端从未产出数组),属于校验管线输入端漏洞。\n */\nexport function queryToObject(params: URLSearchParams): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of params) {\n const existing = result[key];\n if (existing === undefined) {\n result[key] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n result[key] = [existing, value];\n }\n }\n return result;\n}\n","export type JsonParseResult = { success: true; data: unknown } | { success: false; error: string };\n\nexport function parseJsonBody(text: string): JsonParseResult {\n try {\n const data = JSON.parse(text);\n return { success: true, data };\n } catch {\n return { success: false, error: 'Invalid JSON body' };\n }\n}\n","/**\n * 解析 multipart/form-data 请求\n * 使用 Web 标准 Request.formData() API\n */\n\nexport interface UploadedFile {\n name: string; // 字段名\n filename: string; // 原始文件名\n type: string; // MIME 类型\n size: number; // 文件大小(字节)\n arrayBuffer: () => Promise<ArrayBuffer>; // 获取文件内容\n}\n\nexport interface MultipartResult {\n /** 表单字段(同名字段用数组收集,单值保持 string) */\n fields: Record<string, string | string[]>;\n files: UploadedFile[];\n}\n\n/**\n * 解析 multipart/form-data\n *\n * 同名字段的处理:\n * - 第一次出现:string\n * - 第二次出现:string[](包含所有同名字段的值)\n * - 文件始终收集到 files 数组(通过 name 字段标识归属)\n */\nexport async function parseMultipart(request: Request): Promise<MultipartResult> {\n const formData = await request.formData();\n const fields: Record<string, string | string[]> = {};\n const files: UploadedFile[] = [];\n\n for (const [key, value] of formData.entries()) {\n if (value instanceof File) {\n files.push({\n name: key,\n filename: value.name,\n type: value.type,\n size: value.size,\n arrayBuffer: () => value.arrayBuffer(),\n });\n } else {\n // 同名字段收集为数组\n if (key in fields) {\n const existing = fields[key];\n if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n // in 检查已证明存在\n fields[key] = [existing!, value];\n }\n } else {\n fields[key] = value;\n }\n }\n }\n\n return { fields, files };\n}\n","import { queryToObject } from '../utils/queryToObject';\nimport { parseJsonBody } from '../utils/parseJsonBody';\nimport { parseMultipart } from '../utils/parseMultipart';\nimport { getInputTypeForMethod } from './inputType';\nimport { ValidationError } from '../errors/httpErrors';\n\n/**\n * 根据 HTTP 方法解析主输入\n *\n * - GET / DELETE / HEAD:主输入是 query(URL 查询参数)\n * - POST / PUT / PATCH:主输入是 body(请求体)\n *\n * 注意:DELETE 也可能有 body,但主输入(用于校验)是 query。\n * body 由 createServer 调 resolveBodyForQueryMethod 单独解析后注入\n * (同时消费请求体流,保证 keep-alive 连接可复用)。\n *\n * HTTP 传输语义:\n * - 空请求体(或纯空白)视为无 body,返回 null(handler 可不声明 body 参数)\n * - 请求体非空但 JSON 格式非法,抛 ValidationError(code=INVALID_FORMAT),\n * 不再静默返回 null 导致后续报\"字段缺失\"\n *\n * @param method HTTP 方法\n * @param request Request 对象\n * @returns 解析后的输入值(query 对象或 body 对象)\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveInput(method: string, request: Request): Promise<unknown> {\n return resolveInputFromUrl(method, request, new URL(request.url));\n}\n\n/**\n * resolveInput 的热路径变体:URL 由调用方解析一次后传入\n *\n * createServer 每请求已持有解析好的 URL(pathname/searchParams 复用),\n * 通过此变体避免 query 分支的重复 `new URL(request.url)`。\n */\nexport async function resolveInputFromUrl(\n method: string,\n request: Request,\n url: URL,\n): Promise<unknown> {\n const inputType = getInputTypeForMethod(method);\n\n // 主输入是 body 的方法:解析请求体\n if (inputType === 'body') {\n const contentType = request.headers.get('content-type') ?? '';\n\n // multipart/form-data:字段 + 文件\n if (contentType.includes('multipart/form-data')) {\n return parseMultipart(request);\n }\n\n // application/x-www-form-urlencoded:表单字段\n if (contentType.includes('application/x-www-form-urlencoded')) {\n const text = await request.text();\n if (isBlankText(text)) return null;\n const params = new URLSearchParams(text);\n const obj: Record<string, string> = {};\n for (const [key, value] of params) {\n obj[key] = value;\n }\n return obj;\n }\n\n // 默认按 JSON 解析(application/json 及其它未明确类型)\n const text = await request.text();\n\n // 空请求体:视为无 body(handler 可不声明 body 参数)\n if (isBlankText(text)) {\n return null;\n }\n\n // 非空请求体:必须能解析为 JSON,否则是格式错误\n const result = parseJsonBody(text);\n if (!result.success) {\n throw new ValidationError('请求体不是合法的 JSON', [\n {\n path: 'body',\n code: 'INVALID_FORMAT',\n expected: 'JSON',\n received: 'text',\n message: '请求体不是合法的 JSON',\n },\n ]);\n }\n return result.data;\n }\n\n // 主输入是 query 的方法(GET / DELETE / HEAD):从 URL 提取 query\n return queryToObject(url.searchParams);\n}\n\n/** 空白文本判断(length 短路 + 正则扫描,避免 trim 的全量字符串拷贝) */\nfunction isBlankText(text: string): boolean {\n return text.length === 0 || !/\\S/.test(text);\n}\n\n/**\n * 解析\"主输入是 query 但允许携带 body\"的方法(DELETE)的请求体\n *\n * DELETE 的主输入(用于校验)是 query,但请求体流必须被消费(keep-alive 连接\n * 上有未读 body 时 Node 只能断开连接,无法复用),且 handler 声明 `body` 参数\n * 时应注入真正的请求体而非 query。\n *\n * 与主输入 body 的方法不同:DELETE body 无对应 schema(schema 提取只产出\n * DELETEQuery),不做校验,原样注入。空请求体返回 undefined(handler 未声明\n * body 参数时注入 undefined,行为一致)。\n *\n * @throws {ValidationError} 当请求体非空但 JSON 格式非法时\n */\nexport async function resolveBodyForQueryMethod(request: Request): Promise<unknown> {\n const text = await request.text();\n if (isBlankText(text)) return undefined;\n const result = parseJsonBody(text);\n if (!result.success) {\n throw new ValidationError('请求体不是合法的 JSON', [\n {\n path: 'body',\n code: 'INVALID_FORMAT',\n expected: 'JSON',\n received: 'text',\n message: '请求体不是合法的 JSON',\n },\n ]);\n }\n return result.data;\n}\n","export function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') {\n return false;\n }\n\n // 数组不是普通对象\n if (Array.isArray(value)) {\n return false;\n }\n\n // 检查原型链:普通对象的 prototype 是 Object.prototype 或 null(Object.create(null))\n const proto = Object.getPrototypeOf(value);\n return proto === null || proto === Object.prototype;\n}\n","import type { ResponseMeta } from '../runtime/contextTypes';\n\n/**\n * 仅 headers 型 meta 的延迟落头通道\n *\n * Web Response 一旦构造不可变——此前 `ctx.ok()` / `ctx.fail()` 等返回 Response\n * 的场景,即使 meta 只有 headers(helmet 开启后每请求 ~13 个静态头),也要\n * `new Headers()` 拷贝 + `new Response()` 重建一次。改为把 headers 记入\n * WeakMap(按响应对象弱键,响应被 GC 自动清理),由 Node 发送层\n * (sendNodeResponse)最后一次性 `setHeader`——热路径零重建。\n *\n * 中间件若重建 Response(如 compression / etag 的大 body 分支),需用\n * `consumePendingMetaHeaders` 把延迟头并入新 Response 的 headers,避免丢失。\n */\n\nconst pending = new WeakMap<Response, Record<string, string>>();\n\n/**\n * 记录延迟落头(headers-only meta 场景)\n *\n * 同一响应多次记录按 key 合并(后写覆盖,与 mergeMeta 的 headers.set 语义一致)。\n */\nexport function deferMetaHeaders(response: Response, headers: Record<string, string>): void {\n const existing = pending.get(response);\n if (existing) {\n Object.assign(existing, headers);\n return;\n }\n pending.set(response, { ...headers });\n}\n\n/**\n * 取出并清除响应的延迟头(发送层 / 重建方消费)\n */\nexport function consumePendingMetaHeaders(response: Response): Record<string, string> | undefined {\n const headers = pending.get(response);\n if (headers) pending.delete(response);\n return headers;\n}\n\n/** meta 是否仅含 headers(无 status / setCookies,可走延迟通道) */\nexport function isHeadersOnlyMeta(meta: ResponseMeta): boolean {\n return meta.status === undefined && meta.setCookies.length === 0;\n}\n","import { isPlainObject } from '../utils/isPlainObject';\nimport type { ResponseMeta } from '../runtime/contextTypes';\nimport { deferMetaHeaders, isHeadersOnlyMeta } from './pendingMeta';\n\n/**\n * 将 handler 返回值统一转换为 Response\n *\n * 规则:\n * - Response 对象:原样返回\n * - ReadableStream:直接作为 body\n * - Buffer / Uint8Array:作为二进制 body\n * - 普通对象/数组:JSON.stringify,Content-Type: application/json\n * - string:text/plain\n * - number/boolean:text/plain,String(value)\n * - null/undefined:204 No Content\n * - Promise:await 后再处理\n *\n * 如果传入了 meta,会将 ctx.setStatus / ctx.setHeader 的设置合并到 Response 中\n */\nexport async function toResponse(value: unknown, meta?: ResponseMeta): Promise<Response> {\n // Promise:await 后再处理\n if (value instanceof Promise) {\n return toResponse(await value, meta);\n }\n\n // 辅助函数:将 meta headers 和 setCookies 合并到 Headers 对象\n const applyMeta = (headers: Headers): void => {\n if (!meta) return;\n for (const [key, val] of Object.entries(meta.headers)) {\n headers.set(key, val);\n }\n for (const cookie of meta.setCookies ?? []) {\n headers.append('set-cookie', cookie);\n }\n };\n\n // Response 对象:原样返回(但合并 meta headers)\n if (value instanceof Response) {\n if (\n meta &&\n (meta.status !== undefined ||\n Object.keys(meta.headers).length > 0 ||\n meta.setCookies.length > 0)\n ) {\n // 仅 headers 时延迟到发送层落头(避免每请求 Headers 拷贝 + Response 重建,\n // helmet 开启后 meta.headers 恒非空)。status/cookie 需要 Response 级变更,仍重建\n if (isHeadersOnlyMeta(meta)) {\n deferMetaHeaders(value, meta.headers);\n return value;\n }\n const headers = new Headers(value.headers);\n applyMeta(headers);\n return new Response(value.body, {\n status: meta.status ?? value.status,\n headers,\n });\n }\n return value;\n }\n\n // null/undefined:204 No Content\n if (value === null || value === undefined) {\n const status = meta?.status ?? 204;\n const headers = new Headers();\n applyMeta(headers);\n return new Response(null, { status, headers });\n }\n\n // ReadableStream:直接作为 body\n if (typeof ReadableStream !== 'undefined' && value instanceof ReadableStream) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // Buffer / Uint8Array:作为二进制 body\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n if (value instanceof Uint8Array) {\n const headers = new Headers({ 'Content-Type': 'application/octet-stream' });\n applyMeta(headers);\n return new Response(value as BodyInit, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // 普通对象/数组:JSON.stringify,Content-Type: application/json\n if (isPlainObject(value) || Array.isArray(value)) {\n const body = JSON.stringify(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return new Response(body, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // string:text/plain\n if (typeof value === 'string') {\n const headers = new Headers({ 'Content-Type': 'text/plain' });\n applyMeta(headers);\n return new Response(value, {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // number/boolean:text/plain,String(value)\n if (typeof value === 'number' || typeof value === 'boolean') {\n const headers = new Headers({ 'Content-Type': 'text/plain' });\n applyMeta(headers);\n return new Response(String(value), {\n status: meta?.status ?? 200,\n headers,\n });\n }\n\n // 其他类型 fallback:JSON.stringify\n const body = JSON.stringify(value);\n const headers = new Headers({ 'Content-Type': 'application/json' });\n applyMeta(headers);\n return new Response(body, {\n status: meta?.status ?? 200,\n headers,\n });\n}\n","import type { FaapiContext } from '../runtime/contextTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { resolveInjection, type InjectionType } from './resolveInjection';\nimport { queryToObject } from '../utils/queryToObject';\nimport type { MultipartResult } from '../utils/parseMultipart';\nimport { listAgents } from './agentRegistry';\nimport { getAgentHandle } from './agentHandle';\n\n/**\n * 根据注入类型获取对应的值(内置)\n *\n * Phase 2.3 扩展 `agent` / `agents`:\n * - `agents` → `listAgents()`(所有已注册 agent 元数据列表)\n * - `agent` → `getAgentHandle(ctx)`(Phase 3.5:调 `@faapi/agent` 插件注册的工厂)\n *\n * Phase 3.5 的 `@faapi/agent` 插件通过 [agentHandle](./agentHandle.md) 工厂注册机制\n * 提供 `AgentHandle`(含可调用 `run` / `stream`)。未注册工厂时返回 `undefined`。\n */\nfunction getBuiltinInjectionValue(type: InjectionType, ctx: FaapiContext, body?: unknown): unknown {\n switch (type) {\n case 'query':\n return queryToObject(ctx.query);\n case 'params':\n return ctx.params;\n case 'headers':\n return ctx.headers;\n case 'context':\n return ctx;\n case 'cookies':\n return ctx.cookies;\n case 'ip':\n return ctx.ip;\n case 'ua':\n return ctx.ua;\n case 'body':\n return body;\n // form 与 body 共享解析结果(resolveInput 已按 Content-Type 解析 form-urlencoded)\n // 差异仅在 schema 校验(form coerce=true,由 collectRouteSchemaSources 标记)\n case 'form':\n return body;\n case 'files':\n if (body && typeof body === 'object' && 'files' in body) {\n return (body as MultipartResult).files;\n }\n return [];\n case 'fields':\n if (body && typeof body === 'object' && 'fields' in body) {\n return (body as MultipartResult).fields;\n }\n return {};\n // Phase 2.3:注入所有已注册 agent 元数据列表\n // 方案 A:优先读 app 实例注册表,无实例(编程式直调 ctx)回退默认全局实例\n case 'agents':\n return ctx.registries ? ctx.registries.agent.listAgents() : listAgents();\n // Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle\n case 'agent':\n return ctx.registries ? ctx.registries.agentHandle.get(ctx) : getAgentHandle(ctx);\n default:\n return undefined;\n }\n}\n\n/**\n * 根据注入信息,准备参数值并调用 handler(异步版本)\n *\n * 支持 async handler。内置注入优先于注入器(避免 query/body 等被覆盖)。\n * 非内置参数从注入器注册表按参数名查找,按需执行。\n */\nexport async function injectParamsAsync(\n handler: (...args: unknown[]) => unknown,\n ctx: FaapiContext,\n body?: unknown,\n injectors?: InjectorMap,\n): Promise<unknown> {\n const injections = resolveInjection(handler);\n\n if (injections.length === 0) {\n return await handler();\n }\n\n const args = await Promise.all(\n injections.map(async (injection) => {\n // 内置注入优先\n if (injection.type !== 'unknown') {\n return getBuiltinInjectionValue(injection.type, ctx, body);\n }\n // 注入器按参数名匹配\n if (injectors && injection.name in injectors) {\n // in 检查已证明存在\n return await injectors[injection.name]!(ctx);\n }\n return undefined;\n }),\n );\n\n return await handler(...args);\n}\n","import type { FaapiContext } from './contextTypes';\nimport type { ResponseMeta } from './contextTypes';\nimport type { SseWriter } from './sse';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { toResponse } from '../response/toResponse';\nimport { deferMetaHeaders, isHeadersOnlyMeta } from '../response/pendingMeta';\nimport { wrapOkResult } from '../response/responseFormatter';\nimport { injectParamsAsync } from '../injection/injectParams';\n\n/**\n * 自动包裹 handler 返回值(统一响应包装)\n *\n * 实现已移至 [response/responseFormatter.ts](../response/responseFormatter.ts) 的\n * wrapOkResult,与 ctx.ok() / formatFailResponse / formatErrorResponse 共享同一套\n * ok/fail 函数,确保响应格式在所有路径一致。\n *\n * 规则:\n * - Response 对象:不包裹(ctx.ok/ctx.fail/ctx.json 等返回的 Response 原样透传)\n * - 其他值(含 null/undefined):用 config.response.ok(或默认 (data) => ({ data })) 包裹\n *\n * ctx.ok() 返回的是 Response 对象,所以 handler 用 `return ctx.ok(data)` 或 `return data`\n * 最终响应一致(都是 ok(data) 的结果),不会双重包裹。\n *\n * 注意:null/undefined 也会被包裹为 { data: null } / { data: undefined }(JSON.stringify\n * 后者得到 \"{}\"),不再返回 204 No Content。如需 204,显式返回 Response 对象。\n */\nfunction wrapResult(result: unknown, ctx: FaapiContext): unknown {\n return wrapOkResult(result, ctx.config);\n}\n\n/**\n * 将 ctx.meta(setStatus/setHeader/setCookie 设置的响应元数据)合并到 Response\n *\n * 用于中间件返回 Response 时,确保用户通过 ctx.setStatus/setHeader 设置的\n * 状态码、响应头、cookie 能正确附加到中间件返回的 Response 上。\n */\nexport function mergeMeta(response: Response, meta: ResponseMeta): Response {\n const hasMeta =\n meta.status !== undefined || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;\n if (!hasMeta) return response;\n\n // 仅 headers 时延迟到发送层落头(零 Response 重建,见 pendingMeta.ts)\n if (isHeadersOnlyMeta(meta)) {\n deferMetaHeaders(response, meta.headers);\n return response;\n }\n\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(meta.headers)) {\n headers.set(key, value);\n }\n for (const cookie of meta.setCookies) {\n headers.append('set-cookie', cookie);\n }\n return new Response(response.body, {\n status: meta.status ?? response.status,\n headers,\n });\n}\n\n/**\n * 洋葱模型调度:将中间件链包装成 next 函数\n *\n * 每个中间件的 `await next()` 会执行下一个中间件并返回其 Response;\n * 最内层的 next 执行 handler(含参数注入)。\n *\n * 中间件返回 Response 的语义:\n * - 在 await next() 之前返回:拦截请求(不执行后续中间件和 handler)\n * - 在 await next() 之后返回:替换内层返回的响应\n * - 在 catch 块中返回:作为错误响应\n *\n * 中间件返回 void/undefined:使用 await next() 返回的内层响应。\n * 中间件不调用 next() 也不返回 Response:抛错(语义模糊,禁止使用)。\n *\n * 中间件返回 Response 时会合并 ctx.meta(setStatus/setHeader/setCookie 的设置),\n * 确保 ctx 便捷方法在中间件拦截场景下也生效。\n */\nexport async function compose(\n middlewares: FaapiMiddleware[],\n ctx: FaapiContext,\n finalHandler: () => Promise<Response>,\n): Promise<Response> {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n let index = -1;\n\n async function dispatch(i: number): Promise<Response> {\n if (i <= index) {\n throw new Error('next() called multiple times');\n }\n index = i;\n\n // 所有中间件执行完,运行 handler\n if (i >= middlewares.length) {\n return await finalHandler();\n }\n\n // i >= middlewares.length 已早退,此处索引必然存在\n const mw = middlewares[i]!;\n // next() 返回内层 Response,中间件可选择使用或替换\n let innerResponse: Response | undefined;\n const next = async (): Promise<Response> => {\n innerResponse = await dispatch(i + 1);\n return innerResponse;\n };\n\n const result = await mw(ctx, next);\n\n // 中间件返回 Response:使用它(拦截或替换),并合并 ctx.meta\n if (result instanceof Response) {\n return mergeMeta(result, meta);\n }\n // 中间件返回 void:使用 next() 返回的内层响应\n if (innerResponse !== undefined) {\n return innerResponse;\n }\n // 中间件既没返回 Response 也没调用 next():语义模糊,禁止\n throw new Error('中间件必须 await next() 或返回 Response');\n }\n\n return await dispatch(0);\n}\n\n/**\n * 调用路由 handler 并将返回值转为 Response\n *\n * 流程(洋葱模型):\n * 1. 中间件按洋葱模型执行:mw1.before → mw2.before → ... → handler → ... → mw2.after → mw1.after\n * 2. 中间件不调用 next() 即拦截请求(必须返回 Response)\n * 3. 中间件可用 try/catch 捕获内层错误\n * 4. 最内层执行注入器(按需)→ handler\n *\n * 注入器与中间件解耦:\n * - 注入器按 handler 参数名匹配,只执行需要的\n * - 注入器可读取中间件塞进 ctx 的值\n */\nexport async function invokeHandler(\n handler: (...args: unknown[]) => unknown,\n ctx: FaapiContext,\n body?: unknown,\n middlewares?: FaapiMiddleware[],\n injectors?: InjectorMap,\n): Promise<Response> {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n\n /**\n * 若 handler 调用了 ctx.sse(),优先使用 SSE Response\n * (handler 返回值被忽略,SSE 流由 writer 控制)\n *\n * 同时执行自动 close 兜底:若 handler 返回时 writer 仍未关闭,自动调用 close(),\n * 避免连接泄漏。handler 异步循环未退出、忘记 close、或抛错的场景由这里兜底。\n */\n const pickSseAndAutoClose = (): Response | null => {\n const sseWriter = (ctx as FaapiContext & { __sseWriter?: SseWriter }).__sseWriter;\n if (!sseWriter) return null;\n // 兜底:handler 未显式 close 时自动关闭\n if (!sseWriter.closed && !sseWriter.aborted) {\n sseWriter.close();\n }\n return mergeMeta(sseWriter.response, meta);\n };\n\n // 抛错时也保证 writer 被关闭(避免流泄漏)\n const autoCloseSseOnError = (): void => {\n const sseWriter = (ctx as FaapiContext & { __sseWriter?: SseWriter }).__sseWriter;\n if (sseWriter && !sseWriter.closed && !sseWriter.aborted) {\n sseWriter.close();\n }\n };\n\n // 无中间件:直接执行 handler\n if (!middlewares || middlewares.length === 0) {\n try {\n const result = await injectParamsAsync(handler, ctx, body, injectors);\n const sseResponse = pickSseAndAutoClose();\n if (sseResponse) return sseResponse;\n return toResponse(wrapResult(result, ctx), meta);\n } catch (err) {\n // handler 抛错时关闭未完成的 SSE 流,避免泄漏\n autoCloseSseOnError();\n throw err;\n }\n }\n\n // 有中间件:洋葱模型调度\n const finalHandler = async (): Promise<Response> => {\n try {\n const result = await injectParamsAsync(handler, ctx, body, injectors);\n const sseResponse = pickSseAndAutoClose();\n if (sseResponse) return sseResponse;\n return toResponse(wrapResult(result, ctx), meta);\n } catch (err) {\n // handler 抛错时关闭未完成的 SSE 流,避免泄漏\n autoCloseSseOnError();\n throw err;\n }\n };\n\n return await compose(middlewares, ctx, finalHandler);\n}\n","import type { ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { consumePendingMetaHeaders } from './pendingMeta';\n\n/**\n * 将 Web Response 写入 Node.js ServerResponse\n *\n * 使用 stream pipe 处理背压,避免高并发下内存膨胀。\n *\n * 断连语义:客户端中断(res 'error' / 提前 'close')时销毁源流并按正常完成收尾——\n * 网络中断不是服务端错误,不进入 500 兜底、不触发 onError;destroy 使底层\n * web ReadableStream 触发 cancel(),上游生产者(如 SseWriter)感知 aborted\n * 停止推送,避免数据堆积在无消费者的流 buffer 中(内存泄漏)。\n * 源流自身错误(handler 流式写失败)仍 reject,走正常错误响应路径。\n */\nexport async function sendNodeResponse(response: Response, res: ServerResponse): Promise<void> {\n // 设置状态码\n res.statusCode = response.status;\n\n // 设置 headers\n for (const [key, value] of response.headers) {\n if (key.toLowerCase() === 'set-cookie') {\n // Set-Cookie 使用 appendHeader 支持多个值\n res.appendHeader(key, value);\n } else {\n res.setHeader(key, value);\n }\n }\n // 延迟落头(headers-only meta 的零重建通道,见 pendingMeta.ts)——\n // 后写覆盖,与 Headers.set 语义一致\n const deferred = consumePendingMetaHeaders(response);\n if (deferred) {\n for (const [key, value] of Object.entries(deferred)) {\n res.setHeader(key, value);\n }\n }\n\n // 写入 body(使用 pipe 处理背压)\n if (response.body) {\n // TS 5.7 lib.dom 的 ReadableStream 与 node:stream/web 的 ReadableStream 是不同类型,cast 绕过\n const nodeStream = Readable.fromWeb(response.body as never);\n await new Promise<void>((resolve, reject) => {\n // 源流错误:真实错误 → reject(handleRequest catch 走错误响应)\n nodeStream.on('error', reject);\n\n // 客户端断开:销毁源流(触发底层 cancel)+ resolve(不当作服务端错误)\n const abort = () => {\n nodeStream.destroy();\n resolve();\n };\n res.on('error', abort);\n res.on('close', () => {\n // 正常完成也会触发 'close'(在 'finish' 之后,此时 writableEnded=true),不视为断连\n if (!res.writableEnded) abort();\n });\n\n res.on('finish', resolve);\n nodeStream.pipe(res);\n });\n return;\n }\n\n res.end();\n}\n","import { 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 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 zlib from 'node:zlib';\nimport { promisify } from 'node:util';\nimport type { FaapiMiddleware } from './middlewareTypes';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\n\nconst gzipAsync = promisify(zlib.gzip);\nconst deflateAsync = promisify(zlib.deflate);\nconst brotliAsync = promisify(zlib.brotliCompress);\n\nexport interface CompressionOptions {\n /**\n * 最小压缩字节数,body 低于该值不压缩(小 payload 压缩后反而变大)\n * @default 1024\n */\n threshold?: number;\n}\n\nconst DEFAULT_THRESHOLD = 1024;\n\n/** 可压缩的 Content-Type(text/event-stream 除外——流式语义不应缓冲) */\nconst COMPRESSIBLE_CHECKS: Array<(contentType: string) => boolean> = [\n (ct) => ct === 'application/json',\n (ct) => ct === 'application/javascript',\n (ct) => ct === 'image/svg+xml',\n (ct) => ct.startsWith('text/') && ct !== 'text/event-stream',\n];\n\nfunction isCompressible(contentType: string): boolean {\n const ct = contentType.split(';')[0]!.trim().toLowerCase();\n if (ct === '') return false;\n return COMPRESSIBLE_CHECKS.some((check) => check(ct));\n}\n\ninterface AcceptedEncoding {\n encoding: string;\n quality: number;\n}\n\n/**\n * 解析 Accept-Encoding 头(含 q 值)\n *\n * `gzip, br;q=0.8, identity;q=0, *;q=0.5` → [{gzip,1},{br,0.8},{identity,0},{*,0.5}]\n */\nexport function parseAcceptEncoding(header: string): AcceptedEncoding[] {\n return header\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part !== '')\n .map((part) => {\n const [encoding, ...params] = part.split(';');\n let quality = 1;\n for (const param of params) {\n const trimmed = param.trim();\n if (trimmed.startsWith('q=')) {\n const q = Number(trimmed.slice(2));\n if (Number.isFinite(q)) quality = q;\n }\n }\n return { encoding: (encoding ?? '').trim().toLowerCase(), quality };\n })\n .filter((e) => e.encoding !== '' && e.quality > 0);\n}\n\n/**\n * 从已接受的编码中选择压缩算法\n *\n * 服务器偏好 br > gzip > deflate(同等 q 值下);`*` 通配按 gzip 处理。\n * 未命中返回 null(客户端不接受压缩)。\n */\nexport function selectEncoding(accepted: AcceptedEncoding[]): 'br' | 'gzip' | 'deflate' | null {\n const byName = new Map<string, number>();\n for (const { encoding, quality } of accepted) {\n // 同一编码出现多次取最高 q\n byName.set(encoding, Math.max(byName.get(encoding) ?? 0, quality));\n }\n // q=0 表示明确不接受\n for (const [encoding, quality] of byName) {\n if (quality === 0) byName.delete(encoding);\n }\n\n for (const candidate of ['br', 'gzip', 'deflate'] as const) {\n if ((byName.get(candidate) ?? 0) > 0) return candidate;\n }\n if ((byName.get('*') ?? 0) > 0) return 'gzip';\n return null;\n}\n\nasync function compressBody(\n encoding: 'br' | 'gzip' | 'deflate',\n body: string,\n): Promise<Uint8Array> {\n const buf = Buffer.from(body, 'utf-8');\n switch (encoding) {\n case 'br':\n return brotliAsync(buf);\n case 'gzip':\n return gzipAsync(buf);\n case 'deflate':\n return deflateAsync(buf);\n }\n}\n\n/** 向 meta.headers 合并 Vary 值(CORS 等中间件可能已设置 Vary: Origin,不能覆盖) */\nfunction mergeVary(meta: ResponseMeta, value: string): void {\n const existing = meta.headers['Vary'] ?? meta.headers['vary'];\n if (!existing) {\n meta.headers['Vary'] = value;\n return;\n }\n // 大小写不敏感地检查是否已包含\n if (!existing.toLowerCase().includes(value.toLowerCase())) {\n const key = meta.headers['Vary'] !== undefined ? 'Vary' : 'vary';\n meta.headers[key] = `${existing}, ${value}`;\n }\n}\n\n/**\n * 创建响应压缩中间件(内建,默认关闭,`config.compression` 显式启用)\n *\n * 位于外层中间件链最前(包住 CORS/helmet/logger/全局),`await next()` 后拿到\n * 最终 Response:满足压缩条件(可压缩类型 + 大小达标 + 客户端接受)时缓冲 body\n * 异步压缩后构造新 Response 替换;否则透传。`Vary: Accept-Encoding` 通过\n * meta.headers 合并(与 CORS 的 Vary: Origin 共存),两种路径都生效。\n */\nexport function compression(options: CompressionOptions = {}): FaapiMiddleware {\n const threshold = options.threshold ?? DEFAULT_THRESHOLD;\n\n return async (ctx, next) => {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n const response = await next();\n if (!response) return response;\n\n // 无条件协商 Vary(响应内容随 Accept-Encoding 变化,与是否压缩本响应无关)\n mergeVary(meta, 'Accept-Encoding');\n\n // 跳过条件:客户端不接受 / 不可压缩类型 / 已编码 / no-transform / 204、304 等\n const acceptEncoding = ctx.request.headers.get('accept-encoding') ?? '';\n const encoding = selectEncoding(parseAcceptEncoding(acceptEncoding));\n const contentType = response.headers.get('content-type') ?? '';\n if (\n !encoding ||\n !isCompressible(contentType) ||\n response.headers.has('content-encoding') ||\n (response.headers.get('cache-control') ?? '').includes('no-transform') ||\n response.status === 204 ||\n response.status === 304 ||\n response.body === null\n ) {\n return response;\n }\n\n // 缓冲 body(toResponse 的 JSON body 本就是字符串);SSE 已被 content-type 排除\n // 延迟落头(headers-only meta)需并入重建的 Response,否则发送层取不到\n const deferredHeaders = consumePendingMetaHeaders(response);\n\n const bodyText = await response.text();\n if (bodyText.length < threshold) {\n // 未达阈值:body 已被 text() 消费,需重建等价 Response\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n const compressed = await compressBody(encoding, bodyText);\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n headers.set('Content-Encoding', encoding);\n // Content-Length 由 Response 构造时按压缩后字节数自动设置(先删避免重复)\n headers.delete('Content-Length');\n return new Response(compressed as unknown as BodyInit, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n","import { createHash } from 'node:crypto';\nimport type { FaapiMiddleware } from './middlewareTypes';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\n\nexport interface EtagOptions {\n /**\n * 生成弱 ETag(`W/\"<hash>\"`)。弱校验器允许压缩等表示差异下的 304 协商,\n * 与 compression 中间件配合正确;强 ETag 需对每个表示(编码)单独生成\n * @default true\n */\n weak?: boolean;\n}\n\nconst DEFAULTS: Required<EtagOptions> = { weak: true };\n\n/** 计算 ETag:SHA-1 指纹 base64;weak=true 生成弱校验器(W/ 前缀) */\nfunction computeEtag(body: string, weak: boolean): string {\n const hash = createHash('sha1').update(body).digest('base64');\n return weak ? `W/\"${hash}\"` : `\"${hash}\"`;\n}\n\n/** If-None-Match 比对:weak 模式用 RFC 7232 弱比较(忽略 W/ 前缀);strong 模式精确比对 */\nfunction ifNoneMatchMatches(ifNoneMatch: string, etag: string, weak: boolean): boolean {\n const normalize = (tag: string) => tag.trim().replace(/^W\\//i, '');\n if (weak) {\n const target = normalize(etag);\n return ifNoneMatch.split(',').some((tag) => {\n const trimmed = tag.trim();\n return trimmed === '*' || normalize(trimmed) === target;\n });\n }\n // 强比较:ETag 原样相等(含 W/ 前缀语义,弱 ETag 永不匹配强比较)\n return ifNoneMatch.split(',').some((tag) => tag.trim() === etag || tag.trim() === '*');\n}\n\n/**\n * 创建 ETag/304 协商中间件(内建,默认关闭,`config.etag` 显式启用)\n *\n * `await next()` 后对 GET/HEAD 2xx 响应计算弱 ETag 并写入 meta(handler 显式\n * `ctx.setETag()` 时不覆盖);请求 `If-None-Match` 弱比较命中时返回 304(无 body)。\n *\n * 中间件位置在 compression 内层:先算 ETag/304 再压缩——304 无 body 压缩自动跳过;\n * 弱 ETag 基于未压缩表示计算,语义正确。\n */\nexport function etag(options: EtagOptions = {}): FaapiMiddleware {\n const opts = { ...DEFAULTS, ...options };\n\n return async (ctx, next) => {\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n const response = await next();\n if (!response) return response;\n\n // 仅协商幂等方法的 2xx 响应\n const method = ctx.request.method.toUpperCase();\n if (method !== 'GET' && method !== 'HEAD') return response;\n if (response.status < 200 || response.status >= 300) return response;\n\n // handler 已显式设置 ETag(ctx.setETag 写 meta.headers.etag)→ 控制权归 handler\n if (meta.headers['etag'] !== undefined || response.headers.has('etag')) {\n return response;\n }\n // 流式响应(SSE 等)不缓冲\n if (\n response.body === null ||\n (response.headers.get('content-type') ?? '').includes('text/event-stream')\n ) {\n return response;\n }\n\n const bodyText = await response.text();\n const etagValue = computeEtag(bodyText, opts.weak);\n\n // If-None-Match 弱比较命中 → 304(无 body,携带 ETag)\n const ifNoneMatch = ctx.request.headers.get('if-none-match');\n if (ifNoneMatch && ifNoneMatchMatches(ifNoneMatch, etagValue, opts.weak)) {\n return new Response(null, {\n status: 304,\n statusText: response.statusText,\n headers: { ETag: etagValue },\n });\n }\n\n // 200 路径:body 已被 text() 消费,重建响应;ETag 经 meta 传递(mergeMeta 兜底应用)\n meta.headers['etag'] = etagValue;\n const headers = new Headers(response.headers);\n // 延迟落头并入重建的 Response(etag 中间件位于 compression 内层,先消费)\n const deferredHeaders = consumePendingMetaHeaders(response);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n headers.set(key, value);\n }\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n","/**\n * WebSocket 协议升级处理\n *\n * 监听 HTTP server 的 upgrade 事件,匹配 WS 路由后调用 ws 库完成协议升级,\n * 加载 handler.ts 的 WS 导出,绑定事件回调。\n *\n * 握手阶段复用洋葱中间件链(鉴权/CORS/限流/日志等),与同目录 HTTP 路由共享中间件。\n * 中间件塞入 ctx 的值(如 ctx.user)传入 WS handler。连接建立后切到事件模型,不走中间件。\n *\n * @see wsHandler.md WS handler 约定与握手中间件链设计\n */\nimport type { IncomingMessage } from 'node:http';\nimport type { Server } from 'node:http';\nimport type { Socket } from 'node:net';\nimport fs from 'node:fs';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport type { RawData } from 'ws';\nimport path from 'node:path';\nimport type { WsRouteMatch, RoutesRef } from '../router/routeTypes';\nimport { matchWsRoute } from '../router/matchRoute';\nimport { createContext } from '../runtime/createContext';\nimport { compose, mergeMeta } from '../runtime/invokeHandler';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport { getClientIp } from '../utils/getClientIp';\nimport { nodeHttpToWebHeaders, buildErrorResponse } from './serverUtils';\nimport {\n ensureCompiled,\n ensureMiddlewaresCompiled,\n isDevOnDemandEnabled,\n getDevDist,\n prodPathToSourcePath,\n} from '../cli/compileOnDemand';\nimport { loadMergedMiddlewares } from '../middleware/loadMiddlewares';\nimport type { AppRegistries } from '../injection/registries';\nimport { consumePendingMetaHeaders } from '../response/pendingMeta';\nimport {\n wrapWsSocket,\n type WsContext,\n type WsEventHandlers,\n type WsHandler,\n} from '../runtime/wsHandler';\n\n/**\n * 从 IncomingMessage 提取 URL pathname(不含 query)\n */\nfunction getPathname(req: IncomingMessage): string {\n const url = req.url ?? '/';\n const idx = url.indexOf('?');\n return idx >= 0 ? url.slice(0, idx) : url;\n}\n\n/**\n * 加载 WS handler 并执行,返回事件回调对象(可能为空)\n *\n * 直接动态 import handler.ts 并取 `WS` 导出(不经过 loadRouteModule,\n * 因为 WS 不是 HTTP 方法,loadRouteModule 的 method 维度不适用)。\n * watch 模式下通过时间戳 query string 绕过 ESM 缓存。\n *\n * Dev 按需编译模式:先确保编译再 import,避免 import 不存在的文件污染 Vite SSR 状态。\n */\nasync function loadWsHandler(\n filePath: string,\n ctx: WsContext,\n rootDir?: string,\n): Promise<WsEventHandlers | void> {\n // Dev 按需模式:先确保编译再 import(同 loadRouteModule 逻辑)\n if (isDevOnDemandEnabled() && rootDir) {\n const dist = getDevDist();\n if (dist) {\n const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);\n if (sourcePath && fs.existsSync(sourcePath)) {\n await ensureCompiled(sourcePath, rootDir, dist);\n }\n }\n }\n\n const module = (await importWithCacheBust(filePath, isDevOnDemandEnabled())) as Record<\n string,\n unknown\n >;\n\n const handler = module['WS'];\n if (typeof handler !== 'function') {\n throw new Error(`WS export not found in ${filePath}`);\n }\n return (handler as WsHandler)(ctx);\n}\n\n/**\n * 绑定 ws 库原生事件到 faapi 的 WsEventHandlers\n *\n * 注意:wss.handleUpgrade 回调触发时 socket 已经处于 OPEN 状态,\n * 'open' 事件不会再触发,需要直接同步调用 onOpen。\n */\nfunction bindEvents(rawSocket: WebSocket, handlers: WsEventHandlers | void): void {\n if (!handlers) return;\n const ws = wrapWsSocket(rawSocket);\n\n if (handlers.onOpen) {\n // handleUpgrade 回调触发时 socket 已是 OPEN,直接同步调用\n if (rawSocket.readyState === WebSocket.OPEN) {\n handlers.onOpen!(ws);\n } else {\n rawSocket.once('open', () => handlers.onOpen!(ws));\n }\n }\n if (handlers.onMessage) {\n rawSocket.on('message', (data: RawData, isBinary: boolean) => {\n // ws 库 message 事件传 Buffer | ArrayBuffer | Buffer[],归一化为 Buffer;\n // 二进制帧透传 Buffer(utf8 解码不可逆,protobuf/msgpack 等二进制协议会损坏),\n // 文本帧转 utf8 字符串——与 onMessage 类型声明 string | Buffer 一致\n const buf = toWsBuffer(data);\n handlers.onMessage!(ws, isBinary ? buf : buf.toString('utf8'));\n });\n }\n if (handlers.onClose) {\n rawSocket.on('close', (code: number, reason: Buffer) => {\n handlers.onClose!(ws, code, reason.toString('utf8'));\n });\n }\n if (handlers.onError) {\n rawSocket.on('error', (err: Error) => {\n handlers.onError!(ws, err);\n });\n }\n}\n\n/** ws 库 RawData(Buffer | ArrayBuffer | Buffer[])归一化为 Buffer */\nfunction toWsBuffer(data: RawData): Buffer {\n if (Buffer.isBuffer(data)) return data;\n if (Array.isArray(data)) return Buffer.concat(data);\n return Buffer.from(data as ArrayBuffer);\n}\n\n/**\n * 把 Web Response 写回原始 socket(握手被中间件拦截或出错时使用)\n *\n * socket 未升级为 WebSocket 时,按 HTTP 响应格式写回并销毁。\n */\nasync function sendResponseToSocket(socket: Socket, response: Response): Promise<void> {\n const body = await response.text().catch(() => '');\n const statusLine = `HTTP/1.1 ${response.status} ${response.statusText || ''}\\r\\n`;\n const headerLines: string[] = [];\n let hasContentLength = false;\n // 延迟落头(headers-only meta)并入写回的 header 行\n const deferredHeaders = consumePendingMetaHeaders(response);\n for (const [key, value] of Object.entries(deferredHeaders ?? {})) {\n if (key.toLowerCase() === 'content-length') hasContentLength = true;\n headerLines.push(`${key}: ${value}`);\n }\n for (const [key, value] of response.headers) {\n if (key.toLowerCase() === 'content-length') {\n hasContentLength = true;\n }\n headerLines.push(`${key}: ${value}`);\n }\n if (!hasContentLength) {\n headerLines.push(`Content-Length: ${Buffer.byteLength(body)}`);\n }\n socket.write(statusLine + headerLines.join('\\r\\n') + '\\r\\n\\r\\n' + body);\n socket.destroy();\n}\n\nexport interface AttachWsOptions {\n /** HTTP server 实例(已由 createServer 创建) */\n server: Server;\n /** 路由可变引用容器(watch 模式热替换时 routesRef.wsCurrent 被更新) */\n routesRef: RoutesRef;\n /** 项目根目录,用于解析路由文件绝对路径 */\n rootDir: string;\n /** 业务配置,注入到 WsContext.config */\n config?: Record<string, unknown>;\n /** 全局中间件(来自 faapi.config.ts,WS 握手最外层) */\n globalMiddlewares?: FaapiMiddleware[];\n /** 全局注入器(来自 faapi.config.ts,WS handler 可通过 ctx 间接访问全局依赖) */\n globalInjectors?: InjectorMap;\n /**\n * 是否信任反向代理头(X-Forwarded-For),默认 false\n *\n * true 时 `ctx.ip` 取 `x-forwarded-for` 第一个 IP;false(默认)时直取 socket 地址。\n */\n trustedProxy?: boolean;\n /** app 级注册表——经 WsContext 进入 WS 握手链路 */\n registries?: AppRegistries;\n}\n\n/**\n * 在 HTTP server 上挂载 WebSocket 升级处理\n *\n * 创建独立的 WebSocketServer(noServer 模式),监听 server 的 upgrade 事件。\n * 路由匹配成功后,走洋葱中间件链:\n * - 中间件放行(await next):finalHandler 内调用 ws.handleUpgrade 完成协议升级、绑定事件回调\n * - 中间件拦截(返回 Response):把 Response 写回 socket 后销毁,不进行协议升级\n * - 中间件抛错:由 formatErrorResponse/内置兜底生成错误 Response,写回 socket 后销毁\n *\n * 中间件链顺序:全局中间件(外)→ 目录中间件(内)→ finalHandler。\n * watch 模式下 wsRoutes 通过 routesRef 引用更新。\n */\nexport function attachWebSocket(options: AttachWsOptions): WebSocketServer {\n const {\n server,\n routesRef,\n rootDir,\n config,\n globalMiddlewares,\n trustedProxy = false,\n registries,\n } = options;\n\n const wss = new WebSocketServer({ noServer: true });\n\n server.on('upgrade', async (req: IncomingMessage, socket: Socket, head: Buffer) => {\n try {\n await handleUpgradeRequest(req, socket, head);\n } catch (err) {\n // 兜底 catch:upgrade 监听器是 async 函数,路由匹配 / header 构造 / 上下文创建等\n // 抛出的未捕获错误会变成 unhandled rejection(Node 15+ 默认崩掉进程)。\n // HTTP 路径有 catch 兜底,WS 路径保持对称——写回 500 后销毁 socket。\n console.error('[faapi] WS upgrade 处理失败:', err);\n if (!socket.destroyed) {\n try {\n if (!socket.writableEnded) {\n socket.write('HTTP/1.1 500 Internal Server Error\\r\\n\\r\\n');\n }\n } catch {\n // socket 已不可写,忽略\n }\n socket.destroy();\n }\n }\n });\n\n async function handleUpgradeRequest(\n req: IncomingMessage,\n socket: Socket,\n head: Buffer,\n ): Promise<void> {\n const currentWsRoutes = routesRef.wsCurrent;\n const pathname = getPathname(req);\n const match: WsRouteMatch | null = matchWsRoute(currentWsRoutes, pathname);\n\n if (!match) {\n // 路由不匹配:返回 404 并销毁 socket\n socket.write('HTTP/1.1 404 Not Found\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n\n const { route, params } = match;\n const headers = nodeHttpToWebHeaders(req);\n const host = req.headers.host ?? 'localhost';\n const url = `http://${host}${req.url ?? '/'}`;\n\n // 构造 Web Request 与 FaapiContext(与 HTTP 请求一致,供中间件使用)\n const request = new Request(url, { method: 'GET', headers });\n const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n\n // 标记握手是否已完成协议升级(用于判断 socket 是否可写)\n let upgraded = false;\n\n // finalHandler:加载 WS handler + 协议升级 + 绑定事件\n const finalHandler = async (): Promise<Response> => {\n let handlers: WsEventHandlers | void;\n try {\n const absoluteFilePath = path.resolve(rootDir, route.filePath);\n // WsContext 是 FaapiContext 的结构子集,直接复用 ctx 实例\n // 中间件塞入的字段(如 ctx.user)对 WS handler 可见\n handlers = await loadWsHandler(absoluteFilePath, ctx as unknown as WsContext, rootDir);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.error(`[faapi] WS handler 加载失败 ${route.filePath}: ${reason}`);\n return new Response('Internal Server Error', { status: 500 });\n }\n\n // 协议升级:等 wss.handleUpgrade 回调完成 bindEvents\n await new Promise<void>((resolve, reject) => {\n wss.handleUpgrade(req, socket, head, (rawSocket: WebSocket) => {\n try {\n bindEvents(rawSocket, handlers);\n wss.emit('connection', rawSocket, req);\n upgraded = true;\n resolve();\n } catch (err) {\n reject(err);\n }\n });\n });\n\n // 占位 Response,表示握手成功(socket 已升级,不会被写出)\n return new Response(null, { status: 200 });\n };\n\n // 执行中间件链:全局中间件(外)+ 目录中间件(内)\n let response: Response;\n try {\n // 按需加载中间件:route.middlewares 为 undefined 时从 middlewarePaths 加载(Vite 风格)\n if (route.middlewares === undefined && route.middlewarePaths) {\n // dev 按需模式:先编译中间件源码(含依赖闭包)再 import\n await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);\n const bundle = await loadMergedMiddlewares(route.middlewarePaths);\n if (bundle) {\n route.middlewares = bundle.middlewares;\n } else {\n route.middlewares = [];\n }\n }\n const dirMiddlewares = route.middlewares ?? [];\n const allMiddlewares =\n globalMiddlewares && globalMiddlewares.length > 0\n ? [...globalMiddlewares, ...dirMiddlewares]\n : dirMiddlewares;\n if (allMiddlewares.length > 0) {\n response = await compose(allMiddlewares, ctx, finalHandler);\n } else {\n response = await finalHandler();\n }\n } catch (err) {\n if (upgraded) {\n // 握手已完成(socket 已被 ws 库接管),中间件 after 阶段抛错无法改写响应\n console.error('[faapi] WS 握手后中间件抛错:', err);\n return;\n }\n // 错误兜底链:内置 formatErrorResponse 兜底\n // 业务方如需自定义错误响应,在全局中间件中 try/catch next() 即可\n response = buildErrorResponse(err);\n }\n\n // 握手成功:socket 已升级,中间件 after 阶段已执行完,无需再写\n if (upgraded) {\n return;\n }\n\n // 中间件拦截或错误:把 Response 写回 socket 后销毁\n await sendResponseToSocket(socket, mergeMeta(response, meta));\n }\n\n return wss;\n}\n","import type { IncomingMessage } from 'node:http';\nimport { formatErrorResponse } from '../errors/formatErrorResponse';\n\n/**\n * 将 Node.js IncomingMessage 的 headers 转为 Web Headers\n *\n * 处理数组型 header 值(如 set-cookie)和 undefined 值。\n */\nexport function nodeHttpToWebHeaders(req: IncomingMessage): Headers {\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const v of value) headers.append(key, v);\n } else {\n headers.set(key, value);\n }\n }\n return headers;\n}\n\n/**\n * 构建错误响应(兜底链)\n *\n * 1. 框架内置 formatErrorResponse 处理(handler 抛错时)——读 config.response.fail\n * 自定义包装函数,确保错误格式与 ctx.fail() 主动错误响应一致\n * 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n *\n * 业务方如需进一步自定义错误响应,在全局中间件中 try/catch next() 即可。\n *\n * @param err handler 抛出的错误\n * @param config 业务方配置(用于读取 response.fail,可选——未传时走框架默认 fail 函数)\n */\nexport function buildErrorResponse(\n err: unknown,\n config?: Record<string, unknown> | undefined,\n): Response {\n try {\n return formatErrorResponse(err, config);\n } catch {\n // 极端情况:内置兜底也失败,返回最简 500\n return new Response(\n JSON.stringify({ error: { code: 'INTERNAL_ERROR', message: 'Internal Server Error' } }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n}\n","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","/**\n * WebSocket Handler 类型定义与 socket 封装\n *\n * 用户在 handler.ts 中导出 WS 函数,返回事件回调对象。\n * 框架在协议升级成功后调用对应回调,传递封装后的 WsSocket。\n *\n * @see wsHandler.md 功能说明\n */\n\n/**\n * faapi 封装的 WebSocket socket 抽象\n *\n * 不直接暴露 ws 库的原生 socket,提供更安全、易用的 API。\n * send 对象时自动 JSON.stringify。\n */\nexport interface WsSocket {\n /** 发送数据(string/Buffer 直发,对象自动 JSON.stringify) */\n send(data: string | Buffer | object): void;\n /** 关闭连接 */\n close(code?: number, reason?: string | Buffer): void;\n /** 连接状态:0=connecting, 1=open, 2=closing, 3=closed */\n readonly readyState: number;\n}\n\n/**\n * WebSocket 事件回调集合\n *\n * 用户在 WS handler 中返回此对象,框架在对应事件触发时调用。\n * 所有回调可选,未提供则忽略事件。\n */\nexport interface WsEventHandlers {\n /** 连接建立时触发 */\n onOpen?: (ws: WsSocket) => void;\n /** 收到客户端消息时触发 */\n onMessage?: (ws: WsSocket, message: string | Buffer) => void;\n /** 连接关闭时触发 */\n onClose?: (ws: WsSocket, code: number, reason: string) => void;\n /** 发生错误时触发 */\n onError?: (ws: WsSocket, error: Error) => void;\n}\n\n/**\n * WebSocket 上下文(握手阶段构造)\n *\n * 与 HTTP FaapiContext 类似但精简,提供路由参数、查询参数、请求头、配置。\n * 可通过 declare module '@faapi/faapi' 增强自定义字段。\n */\nexport interface WsContext {\n /** 动态路由参数(如 [id] → params.id) */\n params: Record<string, string>;\n /** URL 查询参数 */\n query: URLSearchParams;\n /** 请求头 */\n headers: Headers;\n /** 业务配置(来自 faapi.config.ts) */\n config: Record<string, unknown>;\n /** 中间件塞入的用户信息(鉴权等) */\n user?: unknown;\n /** 允许通过 declare module 扩展 */\n [key: string]: unknown;\n}\n\n/**\n * WS handler 签名:接收 WsContext,返回事件回调对象(或无返回)\n */\nexport type WsHandler = (ctx: WsContext) => WsEventHandlers | void;\n\n/**\n * 将 ws 库的原生 WebSocket 封装为 faapi 的 WsSocket\n *\n * - send 对象时自动 JSON.stringify\n * - close/re.readyState 透传原生 socket\n */\nexport function wrapWsSocket(rawSocket: {\n send: (data: string | Buffer, cb?: (err?: Error) => void) => void;\n close: (code?: number, reason?: string | Buffer) => void;\n readonly readyState: number;\n}): WsSocket {\n return {\n send(data: string | Buffer | object): void {\n const payload =\n typeof data === 'string' || Buffer.isBuffer(data) ? data : JSON.stringify(data);\n rawSocket.send(payload);\n },\n close(code?: number, reason?: string | Buffer): void {\n rawSocket.close(code, reason);\n },\n get readyState(): number {\n return rawSocket.readyState;\n },\n };\n}\n","import type { Server } from 'node:http';\nimport type { RouteManifest, WsRouteManifest } from '../router/routeTypes';\nimport type { CorsOptions } from '../middleware/cors';\nimport type { FaapiContext } from '../runtime/contextTypes';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport type { RequestHandler, UpgradeHandler } from '../config/pluginTypes';\nimport { createServer } from './createServer';\n\nexport interface StartOptions {\n port: number;\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 路由清单 */\n wsRoutes?: WsRouteManifest;\n /** 全局中间件(来自 faapi.config.ts,对所有路由生效,最外层) */\n middlewares?: FaapiMiddleware[];\n /** 全局注入器(来自 faapi.config.ts,对所有路由 handler 参数注入生效) */\n injectors?: InjectorMap;\n /**\n * server.listen 之前的钩子(用于加载插件、应用 handler 包装等)\n *\n * 钩子在 server 创建后、listen 之前调用,可修改 server 的 request/upgrade listener。\n */\n beforeListen?: (server: Server) => Promise<void> | void;\n}\n\n/**\n * 启动 HTTP server 并打印路由表\n *\n * 流程:createServer → beforeListen(可选,加载插件/应用包装)→ listen → 打印路由表\n */\nexport function startServer(options: StartOptions): Promise<Server> {\n const {\n port,\n routes,\n rootDir,\n dist,\n cors,\n onError,\n config,\n wsRoutes,\n middlewares,\n injectors,\n beforeListen,\n } = options;\n const { server } = createServer({\n routes,\n rootDir,\n dist,\n cors,\n onError,\n config,\n wsRoutes,\n middlewares,\n injectors,\n });\n\n return (async () => {\n // beforeListen 钩子:加载插件、应用 handler 包装等\n if (beforeListen) {\n await beforeListen(server);\n }\n\n return new Promise<Server>((resolve) => {\n server.listen(port, () => {\n const address = server.address();\n const actualPort = typeof address === 'object' && address !== null ? address.port : port;\n\n console.log('faapi dev server started');\n console.log(`- Local: http://localhost:${actualPort}`);\n console.log('- Loaded routes:');\n\n for (const route of routes) {\n const method = route.method.padEnd(6);\n console.log(` ${method}${route.urlPath} ${route.filePath}`);\n }\n\n if (wsRoutes && wsRoutes.length > 0) {\n console.log('- WebSocket routes:');\n for (const route of wsRoutes) {\n console.log(` WS ${route.urlPath} ${route.filePath}`);\n }\n }\n\n resolve(server);\n });\n });\n })();\n}\n\n/**\n * 应用 handler / upgrade 包装器到 server\n *\n * 在 server.listen 之前调用,替换 server 的 request/upgrade listener。\n * 包装器按数组顺序嵌套:finalHandler = wrap1(wrap2(originalHandler))。\n *\n * @param server 目标 server(未 listen)\n * @param handlerWrappers HTTP handler 包装器数组\n * @param upgradeWrappers WS upgrade handler 包装器数组\n */\nexport function applyPluginWrappers(\n server: Server,\n handlerWrappers: Array<(original: RequestHandler) => RequestHandler>,\n upgradeWrappers: Array<(original: UpgradeHandler | undefined) => UpgradeHandler>,\n): void {\n // 应用 HTTP handler 包装\n if (handlerWrappers.length > 0) {\n const listeners = server.listeners('request');\n const original = listeners[0] as RequestHandler | undefined;\n if (original) {\n server.removeAllListeners('request');\n let handler: RequestHandler = original;\n for (const wrap of handlerWrappers) {\n handler = wrap(handler);\n }\n server.on('request', handler);\n }\n }\n\n // 应用 WS upgrade handler 包装\n if (upgradeWrappers.length > 0) {\n const listeners = server.listeners('upgrade');\n const original = listeners[0] as UpgradeHandler | undefined;\n server.removeAllListeners('upgrade');\n let upgrade: UpgradeHandler | undefined = original;\n for (const wrap of upgradeWrappers) {\n upgrade = wrap(upgrade);\n }\n if (upgrade) {\n server.on('upgrade', upgrade);\n }\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type {\n RouteManifest,\n WsRouteManifest,\n RouteRecord,\n WsRouteRecord,\n} from '../router/routeTypes';\nimport type { HttpMethod } from '../router/constants';\nimport { toProdFilePath } from '../utils/prodPaths';\nimport { atomicWriteFile } from '../utils/atomicWrite';\n\n/**\n * 序列化路由记录(可写入 JS 模块,无函数引用)\n *\n * build 时生成,start 时读取。filePath/middlewarePaths 已转为 prd 形式\n * (打平 src/ 前缀 + dist 前缀 + .js)。\n */\nexport interface SerializedRouteRecord {\n method: HttpMethod;\n urlPath: string;\n filePath: string;\n paramNames: string[];\n isDynamic: boolean;\n isCatchAll?: boolean;\n /** 从根到路由目录的中间件文件绝对路径列表(已排序,根在前) */\n middlewarePaths: string[];\n}\n\nexport interface SerializedWsRouteRecord {\n urlPath: string;\n filePath: string;\n paramNames: string[];\n isDynamic: boolean;\n isCatchAll?: boolean;\n middlewarePaths: string[];\n}\n\nexport interface SerializedRouteManifest {\n routes: SerializedRouteRecord[];\n wsRoutes: SerializedWsRouteRecord[];\n}\n\n/**\n * 序列化路由清单为可写入 JS 模块的结构\n *\n * - filePath 转为产物路径(打平 src/ 前缀 + dist 前缀 + .js)\n * - middlewares/injectors 不序列化(函数无法序列化),改为 middlewarePaths(中间件文件绝对路径列表)\n * - middlewarePaths 已排序(根在前,路由目录在后),start 时按序加载即可还原洋葱模型\n *\n * @param rootDir 项目根目录\n * @param dist 产物目录(dist 或 .faapi),用于转换 filePath 和查找中间件\n */\nexport function serializeRoutes(\n routes: RouteManifest,\n wsRoutes: WsRouteManifest,\n rootDir: string,\n dist: string = 'dist',\n): SerializedRouteManifest {\n const serialize = <T extends RouteRecord | WsRouteRecord>(\n route: T,\n ): T extends RouteRecord ? SerializedRouteRecord : SerializedWsRouteRecord => {\n const middlewarePaths = extractMiddlewarePaths(route.filePath, rootDir, dist);\n const serialized = {\n urlPath: route.urlPath,\n filePath: toProdFilePath(route.filePath, dist),\n paramNames: route.paramNames,\n isDynamic: route.isDynamic,\n isCatchAll: route.isCatchAll,\n middlewarePaths,\n };\n if ('method' in route) {\n (serialized as SerializedRouteRecord).method = (route as RouteRecord).method;\n }\n return serialized as T extends RouteRecord ? SerializedRouteRecord : SerializedWsRouteRecord;\n };\n\n return {\n routes: routes.map(serialize) as SerializedRouteRecord[],\n wsRoutes: wsRoutes.map(serialize) as SerializedWsRouteRecord[],\n };\n}\n\n/**\n * 从路由文件路径,向上查找所有中间件文件绝对路径(根在前,路由目录在后)\n *\n * 与 scanRoutes.findMergedMiddlewares 的收集逻辑一致,但只返回路径不加载。\n * 检查源码形式(.ts)中间件文件是否存在,返回产物形式(.js)绝对路径。\n *\n * @param routeFilePath 源码相对路径(如 src/api/hello/handler.ts)\n * @param rootDir 项目根目录\n * @param dist 产物目录(dist 或 .faapi)\n */\nfunction extractMiddlewarePaths(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 // 检查源码 middlewares.ts 是否存在,回退 middlewares.js(prd 残留场景)\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/ 前缀)\n const relMwPath = path.relative(rootDir, absMwPath);\n const prodAbsPath = path.resolve(rootDir, toProdFilePath(relMwPath, dist));\n paths.push(prodAbsPath);\n }\n if (currentDir === resolvedRoot) break;\n // 不超出 src 目录(避免向上查到非源码目录)\n // 到达 src 的父目录(rootDir)时停止\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 * 把序列化的路由清单写入 JS 模块\n *\n * 生成 ESM 模块,start 时通过 import() 加载。\n */\nexport async function writeRoutesModule(\n manifest: SerializedRouteManifest,\n outputPath: string,\n): Promise<void> {\n // 用 JSON.stringify 嵌入,保证字符串转义安全\n const content = `// 自动生成,请勿手动编辑(faapi build 产物)\nexport const routes = ${JSON.stringify(manifest.routes, null, 2)};\nexport const wsRoutes = ${JSON.stringify(manifest.wsRoutes, null, 2)};\n`;\n\n await atomicWriteFile(outputPath, content);\n}\n\n/**\n * 从序列化清单水合路由(还原 RouteRecord,不预加载中间件)\n *\n * 中间件改为按需加载:只把 `middlewarePaths` 存到 `route.middlewarePaths`,\n * 请求阶段由 `createServer` / `handleWsUpgrade` 调 `loadMergedMiddlewares` 按需加载。\n * 这样 dev/prod 启动时都不全量加载中间件模块(Vite 风格),首次请求时才加载。\n */\nexport async function hydrateRoutes(\n manifest: SerializedRouteManifest,\n): Promise<{ routes: RouteManifest; wsRoutes: WsRouteManifest }> {\n const hydrateRoute = (serialized: SerializedRouteRecord): RouteRecord => ({\n method: serialized.method,\n urlPath: serialized.urlPath,\n filePath: serialized.filePath,\n paramNames: serialized.paramNames,\n isDynamic: serialized.isDynamic,\n isCatchAll: serialized.isCatchAll,\n middlewarePaths: serialized.middlewarePaths,\n });\n\n const hydrateWsRoute = (serialized: SerializedWsRouteRecord): WsRouteRecord => ({\n urlPath: serialized.urlPath,\n filePath: serialized.filePath,\n paramNames: serialized.paramNames,\n isDynamic: serialized.isDynamic,\n isCatchAll: serialized.isCatchAll,\n middlewarePaths: serialized.middlewarePaths,\n });\n\n const routes = manifest.routes.map(hydrateRoute);\n const wsRoutes = manifest.wsRoutes.map(hydrateWsRoute);\n return { routes, wsRoutes };\n}\n","import path from 'node:path';\nimport type { AgentManifestList } from '../agents/agentTypes';\nimport type { AgentMetadata } from '../ast/extractAgentMetadata';\nimport { extractAgentMetadata } from '../ast/extractAgentMetadata';\nimport { createPrograms } from '../ast/createProgram';\nimport { toProdFilePath } from '../utils/prodPaths';\nimport { atomicWriteFile } from '../utils/atomicWrite';\n\n/**\n * 序列化的 agent manifest 记录(可写入 JS 模块,无函数引用)\n *\n * 与 [AgentMetadata](../ast/extractAgentMetadata.md) 字段一一对应,仅 `filePath`\n * 由源码形式(`src/...`)转为产物形式(`<dist>/...`,打平 `src/` 前缀 + dist 前缀 + `.js`)。\n *\n * `undefined` 字段(description / systemPrompt / tools / agents / model / maxTurns)\n * 在 JSON.stringify 时自动省略,水合时通过 `??` 兜底为 undefined。\n *\n * > `hasConfig` 字段已移除——它原本用于控制 `loadAgentModule` 是否提取 `config` 对象,\n * > 但 `AgentModule.config` 已废弃(`executeSubAgent` 拿到后从不读取),属于死链路。\n */\nexport interface SerializedAgentRecord {\n /** agent 名(`@agent` 覆盖值 或 目录推导值) */\n name: string;\n /** JSDoc 描述(对 LLM 可见),无则省略 */\n description?: string;\n /** 是否导出 run 函数(从 manifest 透传) */\n hasRun: boolean;\n /** 系统提示词(config 块字面量提取),无/非字面量时省略 */\n systemPrompt?: string;\n /** agent 显式声明可用的 tool 引用列表(config 块字面量提取),无/含非字面量元素时省略 */\n tools?: string[];\n /** 可调用的其他 agent 名列表(config 块字面量提取),无/含非字面量元素时省略 */\n agents?: string[];\n /** LLM 模型名(config 块字面量提取),无/非字面量时省略 */\n model?: string;\n /** 最大对话轮数(config 块字面量提取),无/非字面量时省略 */\n maxTurns?: number;\n /** 产物形式路径(如 `dist/agents/researcher/handler.js`),供运行时 import agent.js */\n filePath: string;\n}\n\n/**\n * faapi-agents.js 文件名(与 faapi-routes.js / faapi-tools.js 同构)\n */\nconst AGENTS_FILE = 'faapi-agents.js';\n\n/**\n * 序列化 agent 清单为可写入 JS 模块的结构\n *\n * - `filePath` 转为产物形式(打平 `src/` 前缀 + dist 前缀 + `.js`)\n * - 其他字段(name/description/hasRun/systemPrompt/tools/agents/model/maxTurns)直接透传\n * - `undefined` 字段在 JSON.stringify 时自动省略\n *\n * @param agents AST 增强后的 AgentMetadata[](由 generateAgentArtifacts 内部从 AgentManifest 增强)\n * @param dist 产物目录(默认 `dist`),用于转换 filePath\n */\nexport function serializeAgents(\n agents: AgentMetadata[],\n dist: string = 'dist',\n): SerializedAgentRecord[] {\n return agents.map((a) => ({\n name: a.name,\n description: a.description,\n hasRun: a.hasRun,\n systemPrompt: a.systemPrompt,\n tools: a.tools,\n agents: a.agents,\n model: a.model,\n maxTurns: a.maxTurns,\n filePath: toProdFilePath(a.filePath, dist),\n }));\n}\n\n/**\n * 把序列化的 agent 清单写入 faapi-agents.js\n *\n * 生成 ESM 模块,运行时 `createAppCore` 通过 `importWithCacheBust` 加载。\n * 用 JSON.stringify 嵌入,保证字符串转义安全(与 `writeRoutesModule` / `writeToolsModule` 一致)。\n */\nexport async function writeAgentsModule(\n manifest: SerializedAgentRecord[],\n outputPath: string,\n): Promise<void> {\n const content = `// 自动生成,请勿手动编辑(faapi build/dev 产物)\nexport const agents = ${JSON.stringify(manifest, null, 2)};\n`;\n await atomicWriteFile(outputPath, content);\n}\n\n/**\n * 从序列化清单水合还原 AgentMetadata[]\n *\n * 字段一一对应(无函数引用需还原)。`undefined` 字段在 JSON.parse 时缺失,\n * 通过 `??` 兜底为 undefined(保证 `AgentMetadata` 类型完整)。\n */\nexport function hydrateAgents(manifest: SerializedAgentRecord[]): AgentMetadata[] {\n return manifest.map((a) => ({\n name: a.name,\n description: a.description ?? undefined,\n filePath: a.filePath,\n hasRun: a.hasRun,\n systemPrompt: a.systemPrompt ?? undefined,\n tools: a.tools ?? undefined,\n agents: a.agents ?? undefined,\n model: a.model ?? undefined,\n maxTurns: a.maxTurns ?? undefined,\n }));\n}\n\n/**\n * 清单级校验:跨 agent 组合才暴露的问题,静默水合会让注册表处于与声明意图不符的状态\n *\n * - **agent 名重复**——水合语义是后者覆盖前者,静默覆盖丢失 agent\n * - **`agents` 引用不存在的 agent 名**——sub-agent 递归到运行时首次调用才失败,\n * 错误被推迟且不带构建上下文\n *\n * `tools` 引用不做构建期校验——业务方 plugin 可在运行时注册额外 tool\n * (`PluginContext.registries`),构建期校验会误报。\n */\nfunction validateAgentList(metadata: AgentMetadata[]): void {\n const names = new Map<string, string>();\n for (const a of metadata) {\n const prev = names.get(a.name);\n if (prev) {\n throw new Error(\n `agent 名重复: \"${a.name}\"(${prev} 与 ${a.filePath} 冲突——目录推导名或 @agent 覆盖名撞名)`,\n );\n }\n names.set(a.name, a.filePath);\n }\n\n for (const a of metadata) {\n for (const ref of a.agents ?? []) {\n if (!names.has(ref)) {\n const available = [...names.keys()].join(', ') || '无';\n throw new Error(\n `agent \"${a.name}\" 的 agents 引用了不存在的 agent: \"${ref}\"(清单中可用: ${available})`,\n );\n }\n }\n }\n}\n\n/**\n * 主入口:从 AgentManifest[] 生成 faapi-agents.js\n *\n * agent **不生成 zod.js**——与 tool 不同,agent 没有用户输入参数(config 块字段\n * 已在 [extractAgentMetadata](../ast/extractAgentMetadata.md) AST 阶段提取为字面量),\n * 运行时无需 schema 校验。`run` 函数参数由 [agentRegistry](../injection/agentRegistry.md)\n * 的 `asTool()` 在 Phase 2.2 处理(若需输入校验,由 agent 子包在 Phase 3.x 自行生成)。\n *\n * 内部流程:\n * 1. 对每个 AgentManifest 调 `createProgram` + `extractAgentMetadata` → AgentMetadata[]\n * (AST 增强:补全 description / `@agent` 覆盖名 / config 块字段)\n * 2. `validateAgentList` 清单级校验(名重复 / agents 互引存在性)\n * 3. `serializeAgents(metadata, dist)` → SerializedAgentRecord[](filePath 转产物形式)\n * 4. `writeAgentsModule(serialized, faapiAgentsPath)` → 写入 `<dist>/faapi-agents.js`\n *\n * 与 [generateToolArtifacts](./generateToolArtifacts.md) 的差异:\n * - 不生成 zod.js(agent 无输入参数)\n * - 无 `skipSchema` 选项(没有 schema 可跳过)\n * - 文件名常量为 `faapi-agents.js`,导出 `agents` 而非 `tools`\n *\n * @param agents scanAgents 产出的 AgentManifest[](仅路径推导字段)\n * @param rootDir 项目根目录\n * @param dist 产物目录(`.faapi` 或 `dist`)\n * @returns AST 增强后的 AgentMetadata[](供调用方日志/调试)\n */\nexport async function generateAgentArtifacts(\n agents: AgentManifestList,\n rootDir: string,\n dist: string,\n): Promise<AgentMetadata[]> {\n // 1. AST 增强:对每个 manifest 调 extractAgentMetadata(批量共享 Program)\n const metadata: AgentMetadata[] = [];\n const programByFile = createPrograms(agents.map((m) => path.resolve(rootDir, m.filePath)));\n for (const manifest of agents) {\n const absPath = path.resolve(rootDir, manifest.filePath);\n const program = programByFile.get(absPath)!;\n const result = extractAgentMetadata(program, absPath, {\n name: manifest.name,\n filePath: manifest.filePath,\n hasRun: manifest.hasRun,\n });\n // 正常构建链路不该发生(createPrograms 按同一批 filePath 建 Program)——\n // 静默跳过会让 agent 从清单里无声消失\n if (!result) {\n throw new Error(\n `agent \"${manifest.name}\" 源文件不在 Program 中: ${manifest.filePath}——无法生成清单`,\n );\n }\n metadata.push(result);\n }\n\n // 2. 清单级校验:名重复 / agents 互引存在性\n validateAgentList(metadata);\n\n // 3. 序列化 + 写入 faapi-agents.js\n const serialized = serializeAgents(metadata, dist);\n const agentsPath = path.resolve(rootDir, dist, AGENTS_FILE);\n await writeAgentsModule(serialized, agentsPath);\n\n return metadata;\n}\n","import ts from 'typescript';\nimport {\n extractDescription,\n extractJSDocTagValue,\n getJSDocFromNode,\n hasExportModifier,\n} from './jsDocMetadata';\nimport { SchemaExtractionError } from './resolveTypeNode';\n\n/**\n * Agent 的 LLM 可见核心字段\n *\n * 描述\"agent 是什么\"——LLM 真正需要消费的字段,**不含**代码本体加载细节\n * (filePath / hasRun)。文件型 agent 与 DB-driven skill 都实现此接口。\n *\n * - 文件型 agent:由 [AgentMetadata](./extractAgentMetadata.md) 继承扩展,\n * 额外含 `filePath` / `hasRun`(代码本体加载用)\n * - DB-driven skill:业务方 plugin 从 DB 字段映射到本接口即可,无需填占位值\n * (skill 无源文件,不走 `loadAgentModule`,自然不读 filePath / hasRun)\n *\n * `@faapi/agent` 子包的 `Agent` 类、`agentRegistry` 查询入口、`asTool` 包装\n * 都消费 `AgentCore`,实现\"agent 与 skill 走同一运行时链路\"。\n */\nexport interface AgentCore {\n /** agent 名(`@agent` JSDoc 覆盖值 或 目录推导值) */\n name: string;\n /** JSDoc 描述(agent 描述,对 LLM 可见),无 JSDoc 或 JSDoc 无自由文本时为 `undefined` */\n description?: string;\n /** 系统提示词(config 块字面量提取);文件型 agent 必填(构建期校验),DB skill 由业务方自治 */\n systemPrompt?: string;\n /** agent 显式声明可用的 tool 引用列表(config 块字面量提取),未声明时为 `undefined`;声明了但含无法静态求值的元素在构建期抛错 */\n tools?: string[];\n /** 可调用的其他 agent 名列表(config 块字面量提取),未声明时为 `undefined`;声明了但含无法静态求值的元素在构建期抛错 */\n agents?: string[];\n /** LLM 模型名(config 块字面量提取),未声明时为 `undefined`;声明了但非字面量在构建期抛错 */\n model?: string;\n /** 最大对话轮数(config 块字面量提取),未声明时为 `undefined`;声明了但非数字字面量在构建期抛错 */\n maxTurns?: number;\n}\n\n/**\n * Agent 完整元数据(文件型 agent)\n *\n * 继承 [AgentCore](./extractAgentMetadata.md) 的 LLM 字段,额外扩展**代码本体加载细节**:\n * - `filePath` — `loadAgentModule` 加载 `handler.js` 产物提取 `run` 函数用\n * - `hasRun` — 是否导出 `run` 函数(`Agent.executeSubAgent` 据此决定走自定义 run\n * 还是默认 reactLoop)\n *\n * DB-driven skill 不实现此接口(无源文件,无需加载),只实现 `AgentCore`。\n *\n * 由 [extractAgentMetadata](./extractAgentMetadata.md) 产出,合并路径推导字段\n * (来自 [scanAgents](../agents/scanAgents.md) 的 `AgentManifest`)与 AST 提取字段\n * (JSDoc 描述、`@agent` 覆盖名、config 块字段)。\n *\n * 字段来源:\n * - `name` — `@agent` JSDoc 覆盖值,或 `pathMeta.name`(目录推导)\n * - `filePath` / `hasRun` — 由 `pathMeta` 透传\n * - `description` — JSDoc 注释块自由文本(对 LLM 可见)\n * - `systemPrompt` / `tools` / `agents` / `model` / `maxTurns` — config 块字面量提取\n */\nexport interface AgentMetadata extends AgentCore {\n /** 源码相对路径(从 `pathMeta` 透传),`loadAgentModule` 据此加载 `handler.js` 提取 `run` */\n filePath: string;\n /** 是否导出 `run` 函数(从 `pathMeta` 透传),`Agent.executeSubAgent` 据此选择自定义 run / 默认 reactLoop */\n hasRun: boolean;\n}\n\n/**\n * 路径推导的 agent 元数据(由 [scanAgents](../agents/scanAgents.ts) 计算)\n *\n * 透传到 [AgentMetadata](./extractAgentMetadata.ts) 输出,与 AST 提取字段合并。\n * 与 [ToolPathMeta](./extractToolMetadata.md) 对称。\n */\nexport interface AgentPathMeta {\n /** 目录推导的 agent 名(如 `researcher`) */\n name: string;\n /** 源码相对路径(如 `src/agents/researcher/handler.ts`) */\n filePath: string;\n /** 是否导出 `run` 函数(scanAgents 正则检测) */\n hasRun: boolean;\n}\n\n/** config 导出查找结果:JSDoc 持有节点 + 对象字面量(可能为 null) */\ninterface FoundConfig {\n /** JSDoc 持有节点(VariableStatement 或 FunctionDeclaration) */\n jsDocOwner: ts.Node;\n /** config 对象字面量(export const config = {...} 的 initializer 或 export function config() 的 return 值) */\n objectLiteral: ts.ObjectLiteralExpression | null;\n}\n\n/**\n * 从 agent handler.ts 提取 agent 的完整元数据\n *\n * 提取内容:\n * 1. **JSDoc 描述** — config 导出的 JSDoc 自由文本\n * 2. **`@agent` 覆盖名** — JSDoc 中 `@agent` 标签后的文本,覆盖目录推导的 `name`\n * 3. **config 块字段** — systemPrompt / tools / agents / model / maxTurns\n *\n * 不提取(由 `pathMeta` 透传):`filePath` / `hasRun`\n *\n * config 查找支持两种导出形式,与 [scanAgents](../agents/scanAgents.md) 的 `CONFIG_EXPORT_RE` 正则同构:\n * - `export const config = { ... }` — 对象字面量(最常见)\n * - `export function config() { return { ... } }` — 函数返回对象\n *\n * JSDoc 查找对箭头函数/函数表达式自动回溯到外层 `VariableStatement`\n * (JSDoc 通常写在 `export const` 上方,而非箭头函数本身)。\n * 与 [extractToolMetadata](./extractToolMetadata.md) 的 JSDoc 查找同构。\n *\n * config 块字段提取仅接受静态可求值的字符串值——字符串字面量、无插值模板字符串及\n * 其 `+` 拼接同等提取;声明了字段但值提取失败(变量引用/含插值模板字符串/拼接混入\n * 数字/混合数组元素等)抛 `SchemaExtractionError`。\n * `systemPrompt` 必填——无 config 导出、config 无 return 对象、config 缺该 key 均抛错,\n * 提示词是 agent 的必要组成。\n *\n * @param program TypeScript Program\n * @param filePath 源文件**绝对路径**(AST 用,需与 `program.getSourceFile` 一致)\n * @param pathMeta 路径推导的元数据(scanAgents 已计算)\n * @returns `AgentMetadata` 或 `null`(源文件不在 Program 中)\n */\nexport function extractAgentMetadata(\n program: ts.Program,\n filePath: string,\n pathMeta: AgentPathMeta,\n): AgentMetadata | null {\n const sourceFile = program.getSourceFile(filePath);\n if (!sourceFile) return null;\n\n // systemPrompt 必填——config 是提示词的唯一载体,无 config 导出时直接抛错。\n // 不存在合法的无提示词文件型 agent(JSDoc description 只是用途说明,不构成提示词)。\n const configFound = findConfigExport(sourceFile);\n if (!configFound) {\n throw SchemaExtractionError.at(\n sourceFile,\n 'config.systemPrompt',\n 'agent 必填——请在 config 块声明系统提示词(字符串字面量、无插值模板字符串或其 + 拼接)',\n sourceFile,\n );\n }\n // 声明了 config 但提取不出对象字面量——形式不支持,与\"未声明\"分开提示,\n // 避免\"请在 config 块声明\"误导已声明 config 的用户\n if (!configFound.objectLiteral) {\n throw SchemaExtractionError.at(\n configFound.jsDocOwner,\n 'config',\n '仅支持对象字面量、箭头函数返回对象字面量或函数返回对象字面量',\n sourceFile,\n );\n }\n const objectLiteral = configFound.objectLiteral;\n\n const jsDoc = getJSDocFromNode(configFound.jsDocOwner);\n const description = extractDescription(jsDoc);\n const agentNameOverride = extractJSDocTagValue(jsDoc, 'agent');\n\n // config 块字段提取(extractConfigFields 保证 systemPrompt 非空——requireStringValue 失败即抛)\n const { systemPrompt, tools, agents, model, maxTurns } = extractConfigFields(\n objectLiteral,\n sourceFile,\n );\n\n return {\n name: agentNameOverride ?? pathMeta.name,\n description,\n filePath: pathMeta.filePath,\n hasRun: pathMeta.hasRun,\n systemPrompt,\n tools,\n agents,\n model,\n maxTurns,\n };\n}\n\n/**\n * 在源文件中查找 `config` 导出\n *\n * 支持两种形式:\n * - `export const config = { ... }` → VariableStatement,对象字面量是 initializer\n * - `export function config() { return { ... } }` → FunctionDeclaration,对象字面量是 return 表达式\n *\n * 返回 JSDoc 持有节点和对象字面量(可能为 null——函数无 return / return 非对象字面量 /\n * const initializer 非对象字面量与箭头函数)。`objectLiteral` 为 null 而 `jsDocOwner`\n * 已定位时,表达\"声明了 config 但形式不支持\",与\"未声明 config\"(返回 null)区分。\n *\n * 不依赖 `node.parent` 链——`ts.createProgram` 配置 `noEmit: true` 时不会设置父指针。\n */\nfunction findConfigExport(sourceFile: ts.SourceFile): FoundConfig | null {\n let result: FoundConfig | null = null;\n\n ts.forEachChild(sourceFile, (node) => {\n if (result) return;\n\n // export const config = { ... }\n if (ts.isVariableStatement(node) && hasExportModifier(node)) {\n for (const decl of node.declarationList.declarations) {\n if (result) break;\n const nameText = ts.isIdentifier(decl.name) ? decl.name.text : '';\n if (nameText !== 'config' || !decl.initializer) continue;\n\n // 记录 config 导出——形式是否支持由 objectLiteral 是否为 null 表达,\n // 调用方据此区分\"未声明 config\"与\"声明了但形式不支持\"\n if (ts.isObjectLiteralExpression(decl.initializer)) {\n result = { jsDocOwner: node, objectLiteral: decl.initializer };\n } else if (ts.isArrowFunction(decl.initializer)) {\n result = { jsDocOwner: node, objectLiteral: getReturnObjectLiteral(decl.initializer) };\n } else {\n result = { jsDocOwner: node, objectLiteral: null };\n }\n }\n }\n\n // export function config() { return { ... } }\n if (ts.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === 'config') {\n const returnObj = getReturnObjectLiteral(node);\n result = { jsDocOwner: node, objectLiteral: returnObj };\n }\n });\n\n return result;\n}\n\n/**\n * 在源文件中查找 `config` 导出\n *\n * - `() => ({ ... })` — 箭头函数直接返回对象字面量(body 是 ObjectLiteralExpression)\n * - `() => { return { ... }; }` — block body 中的 return 语句\n * - `function f() { return { ... }; }` — block body 中的 return 语句\n *\n * 无 return / return 非对象字面量 → null\n */\nfunction getReturnObjectLiteral(\n fn: ts.FunctionDeclaration | ts.ArrowFunction,\n): ts.ObjectLiteralExpression | null {\n const body = fn.body;\n if (!body) return null;\n\n // 箭头函数直接返回对象字面量:() => ({ ... })\n if (ts.isObjectLiteralExpression(body)) {\n return body;\n }\n\n // block body:查找 return 语句\n if (ts.isBlock(body)) {\n for (const stmt of body.statements) {\n if (\n ts.isReturnStatement(stmt) &&\n stmt.expression &&\n ts.isObjectLiteralExpression(stmt.expression)\n ) {\n return stmt.expression;\n }\n }\n }\n\n return null;\n}\n\n/**\n * 从 config 对象字面量提取 config 块字段\n *\n * 遍历对象属性,按属性名匹配提取对应字段。仅接受静态可求值的字符串值——\n * 字符串字面量、无插值模板字符串(`NoSubstitutionTemplateLiteral`)及其 `+` 拼接\n * 同等提取;**声明了字段但值提取失败**(变量引用/含插值模板字符串/拼接混入数字/\n * 混合数组元素等)抛 `SchemaExtractionError`——静默降级为 `undefined` 后,运行时与\n * \"合法地未声明该字段\"不可区分,只能在构建期拦截。\n *\n * 属性名匹配支持 Identifier 和 StringLiteral 两种形式:\n * - `{ systemPrompt: 'x' }` — Identifier 属性名\n * - `{ 'systemPrompt': 'x' }` — StringLiteral 属性名\n *\n * SpreadAssignment(`...other`)跳过——不声明任何具名字段,无法静态归属。\n * 未知字段(拼写错误/框架不读的 key)与不支持的属性形式(shorthand/方法/\n * getter/computed 名)抛 `SchemaExtractionError`,不静默忽略。\n *\n * 返回类型的 `systemPrompt` 必填——属性存在但提取失败由 `requireStringValue`\n * 抛错,属性未声明(空对象/缺 key/仅 spread)由返回前校验抛错,正常返回时必有值。\n *\n * @param objLit config 对象字面量\n * @param sourceFile 所在源文件(报错定位用——纯语法遍历路径无 parent 指针,\n * `node.getSourceFile()` 不可用,需显式传入)\n */\nfunction extractConfigFields(\n objLit: ts.ObjectLiteralExpression,\n sourceFile: ts.SourceFile,\n): {\n systemPrompt: string;\n tools?: string[];\n agents?: string[];\n model?: string;\n maxTurns?: number;\n} {\n let systemPrompt: string | undefined;\n let tools: string[] | undefined;\n let agents: string[] | undefined;\n let model: string | undefined;\n let maxTurns: number | undefined;\n\n for (const prop of objLit.properties) {\n // 跳过 SpreadAssignment(...other)\n if (ts.isSpreadAssignment(prop)) continue;\n\n // shorthand / 方法 / getter 等形式——config 只支持 key: value 字面量属性\n if (!ts.isPropertyAssignment(prop)) {\n throw SchemaExtractionError.at(\n prop,\n 'config',\n '仅支持 key: value 属性形式(不支持 shorthand/方法/getter)',\n sourceFile,\n );\n }\n\n const propName = getPropertyName(prop.name);\n if (!propName) {\n throw SchemaExtractionError.at(prop.name, 'config', '不支持 computed 属性名', sourceFile);\n }\n\n switch (propName) {\n case 'systemPrompt':\n systemPrompt = requireStringValue(prop, 'systemPrompt', sourceFile);\n break;\n case 'tools':\n tools = requireStringArrayValue(prop, 'tools', sourceFile);\n break;\n case 'agents':\n agents = requireStringArrayValue(prop, 'agents', sourceFile);\n break;\n case 'model':\n model = requireStringValue(prop, 'model', sourceFile);\n break;\n case 'maxTurns':\n maxTurns = requireNumberValue(prop, 'maxTurns', sourceFile);\n break;\n default:\n // 框架不读的字段几乎必然是拼写错误或误解——静默忽略后运行时按默认值\n // 跑,与声明意图不符(如 maxTurn 拼错后轮数走默认值)\n throw SchemaExtractionError.at(\n prop,\n `config.${propName}`,\n '未知 config 字段——仅支持 systemPrompt / tools / agents / model / maxTurns',\n sourceFile,\n );\n }\n }\n\n if (systemPrompt === undefined) {\n throw SchemaExtractionError.at(\n objLit,\n 'config.systemPrompt',\n 'agent 必填——请在 config 块声明系统提示词(字符串字面量、无插值模板字符串或其 + 拼接)',\n sourceFile,\n );\n }\n\n return { systemPrompt, tools, agents, model, maxTurns };\n}\n\n/**\n * 从属性名节点提取字符串名\n *\n * 支持:\n * - Identifier(`systemPrompt` → `'systemPrompt'`)\n * - StringLiteral(`'systemPrompt'` → `'systemPrompt'`)\n *\n * ComputedPropertyName / NumericLiteral / PrivateIdentifier → null\n */\nfunction getPropertyName(name: ts.PropertyName): string | null {\n if (ts.isIdentifier(name)) return name.text;\n if (ts.isStringLiteral(name)) return name.text;\n return null;\n}\n\n/**\n * 从表达式提取字符串值(StringLiteral / NoSubstitutionTemplateLiteral / 其 + 拼接)\n *\n * - `'hello'` / `\"hello\"` → `'hello'`\n * - `` `hello` `` → `'hello'`(无插值模板字符串语义等价字符串字面量,多行人设的常见写法)\n * - `'a' + 'b'`(含链式 `'a' + 'b' + 'c'`,左结合递归展开)→ `'abc'`——纯字面量拼接\n * 静态可求值,多行提示词的常见写法\n * - 含插值模板字符串 / 变量引用 / 数字(`'a' + 1`)/ 非 `+` 运算符 → `undefined`\n */\nfunction extractStringValue(expr: ts.Expression): string | undefined {\n if (isStringLikeLiteral(expr)) return expr.text;\n // 字符串字面量的 + 拼接:两侧递归求值,任一侧不可求值即整体失败\n if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.PlusToken) {\n const left = extractStringValue(expr.left);\n if (left === undefined) return undefined;\n const right = extractStringValue(expr.right);\n if (right === undefined) return undefined;\n return left + right;\n }\n return undefined;\n}\n\n/**\n * 从属性赋值提取字符串字段值,声明了但提取失败抛 `SchemaExtractionError`\n *\n * 与直接用 `extractStringValue` 的区别:失败即抛错(带字段名与 file:line:column),\n * 不静默返回 `undefined`——\"声明了却提取不出\"是确定的构建错误。\n */\nfunction requireStringValue(\n prop: ts.PropertyAssignment,\n fieldName: string,\n sourceFile: ts.SourceFile,\n): string {\n const value = extractStringValue(prop.initializer);\n if (value === undefined) {\n throw SchemaExtractionError.at(\n prop.initializer,\n `config.${fieldName}`,\n '仅支持字符串字面量、无插值模板字符串或其 + 拼接(含插值的模板字符串/变量引用/数字无法静态求值为字符串)',\n sourceFile,\n );\n }\n return value;\n}\n\n/**\n * 从属性赋值提取字符串数组字段值,声明了但提取失败抛 `SchemaExtractionError`\n *\n * 覆盖两种失败形态:值不是数组表达式、数组含非字符串字面量元素。\n */\nfunction requireStringArrayValue(\n prop: ts.PropertyAssignment,\n fieldName: string,\n sourceFile: ts.SourceFile,\n): string[] {\n const value = extractStringArrayValue(prop.initializer);\n if (value === undefined) {\n throw SchemaExtractionError.at(\n prop.initializer,\n `config.${fieldName}`,\n '仅支持全字符串字面量数组(元素为字符串字面量、无插值模板字符串或其 + 拼接)',\n sourceFile,\n );\n }\n return value;\n}\n\n/**\n * 从属性赋值提取数字字段值,声明了但提取失败抛 `SchemaExtractionError`\n */\nfunction requireNumberValue(\n prop: ts.PropertyAssignment,\n fieldName: string,\n sourceFile: ts.SourceFile,\n): number {\n const value = extractNumberValue(prop.initializer);\n if (value === undefined) {\n throw SchemaExtractionError.at(\n prop.initializer,\n `config.${fieldName}`,\n '仅支持数字字面量',\n sourceFile,\n );\n }\n return value;\n}\n\n/**\n * 从表达式提取数字值(仅 NumericLiteral)\n *\n * - `10` → `10`\n * - `'10'` / 变量引用 → `undefined`\n */\nfunction extractNumberValue(expr: ts.Expression): number | undefined {\n if (ts.isNumericLiteral(expr)) {\n const num = Number(expr.text);\n return Number.isNaN(num) ? undefined : num;\n }\n return undefined;\n}\n\n/**\n * 字符串字面量形态判断(StringLiteral 或无插值模板字符串)\n *\n * 两者 `.text` 均为去引号后的源码文本,语义等价。\n */\nfunction isStringLikeLiteral(\n node: ts.Expression,\n): node is ts.StringLiteral | ts.NoSubstitutionTemplateLiteral {\n return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node);\n}\n\n/**\n * 从表达式提取字符串数组(ArrayLiteralExpression,全元素静态可求值为字符串)\n *\n * - `['a', 'b']` → `['a', 'b']`\n * - `` [`a`, `b`] `` → `['a', 'b']`(无插值模板字符串元素)\n * - `['a' + 'b', 'c']` → `['ab', 'c']`(元素支持字面量拼接)\n * - `['a', someVar]` → `undefined`(含无法静态求值的元素)\n * - `[]` → `[]`(空数组)\n * - 非数组 → `undefined`\n */\nfunction extractStringArrayValue(expr: ts.Expression): string[] | undefined {\n if (!ts.isArrayLiteralExpression(expr)) return undefined;\n const values: string[] = [];\n for (const element of expr.elements) {\n const value = extractStringValue(element);\n if (value === undefined) return undefined;\n values.push(value);\n }\n return values;\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport { pathToFileURL } from 'node:url';\nimport type { FaapiPlugin, PluginDeclaration } from '../config/pluginTypes';\nimport type { RequestHandler, UpgradeHandler } from '../config/pluginTypes';\nimport { ensureCompiled, isDevOnDemandEnabled } from './compileOnDemand';\nimport { compileProjectModules } from './compileConfig';\n\n/** loadPlugins 的输入上下文(不含 wrap 能力,由 loadPlugins 内部注入) */\ntype LoadPluginsContext = Omit<\n import('../config/pluginTypes').PluginContext,\n 'wrapHandler' | 'wrapUpgradeHandler'\n>;\n\n/** loadPlugins 返回的包装器收集结果 */\nexport interface PluginLoadResult {\n /** 插件注册的 HTTP handler 包装器(按注册顺序) */\n handlerWrappers: Array<(original: RequestHandler) => RequestHandler>;\n /** 插件注册的 WS upgrade handler 包装器(按注册顺序) */\n upgradeWrappers: Array<(original: UpgradeHandler | undefined) => UpgradeHandler>;\n /** 加载失败的插件(specifier + 原因)——调用方据此汇总报告或 fail-fast */\n failures: Array<{ specifier: string; reason: string }>;\n}\n\n/**\n * 加载并执行插件列表\n *\n * 遍历 config.plugins,动态 import 插件包,调用 setup(ctx)。\n * 插件在 server 创建后、listen 之前按声明顺序执行。\n *\n * 插件可通过 ctx.wrapHandler / ctx.wrapUpgradeHandler 注册包装函数,\n * 本函数收集后返回,由调用方在 listen 之前应用。\n *\n * 错误口径(单一语义,不再静默降级):\n * - 单个插件加载/setup 失败不中断其他插件,但失败明细收集进 `failures` 并\n * 在加载完成后统一 `console.error` 汇总——鉴权/CORS 类插件静默丢失等同裸奔,\n * 必须对业务方可见\n * - 非法声明(resolveDeclaration 失败)与其他插件错误同口径收集,不崩启动\n * - `path` 声明相对项目根目录解析(`pathToFileURL(path.resolve(rootDir, path))`),\n * 此前直接 import 会相对 faapi 包自身产物解析,几乎必然失败且报错指向\n * node_modules 深处\n *\n * 本地 TS 插件(`{ path: './plugins/xxx' }`,`.ts` 源码)的加载路径:\n * - 探测源文件(原样 → `.ts`/`.js` → `/index.ts`/`/index.js`),Node ESM 不做\n * 扩展名补全,无扩展名 file URL 直接 import 必然 `Cannot find module`\n * - 产物存在且不比源码旧 → import 产物(build 固化 / dev 已编译产物复用)\n * - `.ts` 源码 + dev 按需模式 → esbuild 逐文件编译到产物目录后 import 产物\n * (src 内插件走 ensureCompiled 打平产物与 routes 同一运行时对象;src 外插件\n * 走 compileProjectModules,含依赖闭包与 specifier 重写——插件内部\n * `import '../src/xxx'` 无扩展名由此生效)\n * - `.ts` 源码 + 非按需模式(prod / 编程式)且产物 stale → 明确报错指引 `faapi build`,\n * 不静默使用旧产物(与 handler 的 prod 语义一致:产物固化,import 失败即报错)\n *\n * @param declarations 插件声明列表\n * @param ctx 插件上下文(不含 wrap 能力,由本函数注入)\n * @param rootDir 项目根目录(`path` 声明解析基准)\n * @param dist 产物目录(dev 为 `.faapi`,prod 为 `dist`——本地 TS 插件按需编译/产物复用基准)\n * @returns 包装器收集结果 + 失败清单\n */\nexport async function loadPlugins(\n declarations: PluginDeclaration[] | undefined,\n ctx: LoadPluginsContext,\n rootDir?: string,\n dist?: string,\n): Promise<PluginLoadResult> {\n const handlerWrappers: Array<(original: RequestHandler) => RequestHandler> = [];\n const upgradeWrappers: Array<(original: UpgradeHandler | undefined) => UpgradeHandler> = [];\n const failures: Array<{ specifier: string; reason: string }> = [];\n\n if (!declarations || declarations.length === 0) {\n return { handlerWrappers, upgradeWrappers, failures };\n }\n\n // 注入 wrap 能力到 ctx\n const fullCtx = {\n ...ctx,\n wrapHandler: (fn: (original: RequestHandler) => RequestHandler) => {\n handlerWrappers.push(fn);\n },\n wrapUpgradeHandler: (fn: (original: UpgradeHandler | undefined) => UpgradeHandler) => {\n upgradeWrappers.push(fn);\n },\n };\n\n const loaded = new Set<string>();\n\n for (const decl of declarations) {\n // 非法声明与其他插件错误同口径:收集进 failures,不崩启动\n let specifier: string;\n let options: unknown;\n let enable: boolean | undefined;\n try {\n ({ specifier, options, enable } = resolveDeclaration(decl));\n } catch (err) {\n failures.push({\n specifier: JSON.stringify(decl),\n reason: err instanceof Error ? err.message : String(err),\n });\n continue;\n }\n\n // enable 检查\n if (enable === false) continue;\n\n // 去重\n if (loaded.has(specifier)) {\n console.warn(`! Plugin already loaded: ${specifier}, skipping`);\n continue;\n }\n loaded.add(specifier);\n\n try {\n const mod = await importPluginModule(specifier, rootDir ?? process.cwd(), dist);\n const plugin: FaapiPlugin = mod.default ?? (mod as unknown as FaapiPlugin);\n\n if (typeof plugin.setup !== 'function') {\n failures.push({ specifier, reason: 'plugin has no setup function' });\n continue;\n }\n\n await plugin.setup({ ...fullCtx, options });\n console.log(`- Plugin loaded: ${plugin.name ?? specifier}`);\n } catch (err) {\n failures.push({\n specifier,\n reason: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n // 失败汇总:单一出口,业务方启动日志一眼可见(鉴权类插件静默丢失不可接受)\n if (failures.length > 0) {\n console.error(\n `[faapi] ${failures.length} plugin(s) failed to load:\\n` +\n failures.map((f) => ` - ${f.specifier}: ${f.reason}`).join('\\n'),\n );\n }\n\n return { handlerWrappers, upgradeWrappers, failures };\n}\n\n/** TS 源文件后缀(需 esbuild 编译才能保证加载) */\nconst TS_EXTS = /\\.(ts|tsx|mts|cts)$/;\n/** JS 源文件后缀(Node ESM 可直接加载) */\nconst JS_EXTS = /\\.(js|mjs|cjs|jsx)$/;\n\n/**\n * 探测本地插件源文件\n *\n * 探测顺序:specifier 原样(已带扩展名)→ `.ts` → `.js` → `/index.ts` → `/index.js`。\n * Node ESM 对 file URL 不做扩展名补全,无扩展名声明必须由框架探测到实际文件。\n *\n * @param specifier 插件声明路径(相对项目根目录或绝对路径)\n * @param baseDir 项目根目录\n * @returns 源文件绝对路径,或 null(未找到)\n */\nexport function resolveLocalPluginSource(specifier: string, baseDir: string): string | null {\n const base = path.isAbsolute(specifier) ? specifier : path.resolve(baseDir, specifier);\n if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs.existsSync(base)) return base;\n for (const ext of ['.ts', '.js']) {\n if (fs.existsSync(base + ext)) return base + ext;\n }\n for (const indexExt of ['/index.ts', '/index.js']) {\n if (fs.existsSync(base + indexExt)) return base + indexExt;\n }\n return null;\n}\n\n/**\n * 从源文件绝对路径推算产物绝对路径\n *\n * - src 内源文件:打平 src/ 前缀(与 compileDevRoutes/compileBuildRoutes 产物同一,\n * 运行时对象同一,instanceof 跨边界生效)\n * - src 外源文件:保留相对 rootDir 的路径结构(compileProjectModules 以 rootDir 为\n * outbase,产物在 `<dist>/` 下保留结构)\n */\nfunction mirrorProductPath(sourcePath: string, baseDir: string, distDir: string): string {\n let rel = path.relative(baseDir, sourcePath).replace(/\\\\/g, '/');\n if (rel.startsWith('src/')) rel = rel.slice(4);\n return path.resolve(distDir, rel.replace(TS_EXTS, '.js'));\n}\n\n/** 插件模块形态:default 导出优先 */\ntype PluginModule = { default?: FaapiPlugin } & Record<string, unknown>;\n\n/** 产物是否比源码旧(stale) */\nfunction isSourceNewer(sourcePath: string, productPath: string): boolean {\n try {\n return fs.statSync(sourcePath).mtimeMs > fs.statSync(productPath).mtimeMs;\n } catch {\n return true;\n }\n}\n\n/**\n * 加载插件模块\n *\n * - 包名(非相对/绝对路径):原样 import,Node 按包解析\n * - 相对/绝对路径:探测源文件与产物,按「产物 fresh → 源码编译/直载」决策,\n * 详见 loadPlugins 注释的「本地 TS 插件的加载路径」\n *\n * @throws 找不到可加载目标或 prod 模式产物 stale 时抛错(带修复指引),进 failures 汇总\n */\nasync function importPluginModule(\n specifier: string,\n baseDir: string,\n dist: string | undefined,\n): Promise<PluginModule> {\n const isRelative = specifier.startsWith('./') || specifier.startsWith('../');\n if (!isRelative && !path.isAbsolute(specifier)) {\n return import(specifier);\n }\n\n const distDir = dist ? path.resolve(baseDir, dist) : null;\n const sourcePath = resolveLocalPluginSource(specifier, baseDir);\n const productPath =\n sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;\n\n // 产物可用:存在 且(源码不存在——部署机只发产物,或 产物不比源码旧)\n if (\n productPath !== null &&\n fs.existsSync(productPath) &&\n (sourcePath === null || !isSourceNewer(sourcePath, productPath))\n ) {\n return import(pathToFileURL(productPath).href);\n }\n\n // TS 源码:dev 按需编译后 import 产物\n if (sourcePath && TS_EXTS.test(sourcePath)) {\n if (!isDevOnDemandEnabled() || !distDir) {\n throw new Error(\n `Local plugin \"${specifier}\" has no up-to-date build artifact (dist is stale or missing). ` +\n `Run \"faapi build\" first, or use \"faapi dev\" for on-demand compilation.`,\n );\n }\n const relFromRoot = path.relative(baseDir, sourcePath).replace(/\\\\/g, '/');\n if (relFromRoot.startsWith('src/')) {\n // src 内插件:打平产物与 routes 同一(同一运行时对象)\n await ensureCompiled(sourcePath, baseDir, dist!);\n } else {\n // src 外插件:编译入口 + 依赖闭包(含插件 import 的 src 内模块)\n await compileProjectModules([sourcePath], baseDir, dist!);\n }\n if (productPath && fs.existsSync(productPath)) {\n return import(pathToFileURL(productPath).href);\n }\n // 编译已完成但产物仍缺失:落到统一报错(理论不可达,防御性兜底)\n }\n\n // JS 源码:Node 可直接加载,无需产物\n if (sourcePath && JS_EXTS.test(sourcePath)) {\n return import(pathToFileURL(sourcePath).href);\n }\n\n // 找不到可加载目标:给明确修复指引(此前仅报 Cannot find module,无法定位原因)\n const candidates = [\n `${specifier}.ts`,\n `${specifier}/index.ts`,\n `${specifier}.js`,\n `${specifier}/index.js`,\n ].map((c) => (path.isAbsolute(c) ? c : path.join(baseDir, c)));\n throw new Error(\n `Cannot find local plugin \"${specifier}\" (resolved from ${baseDir}). ` +\n `Expected one of:\\n ${candidates.join('\\n ')}\\n` +\n `Create the plugin file (export default { name, setup(ctx) {...} }) or fix the path.`,\n );\n}\n\n/**\n * 解析插件声明为统一格式\n */\nfunction resolveDeclaration(decl: PluginDeclaration): {\n specifier: string;\n options?: unknown;\n enable?: boolean;\n} {\n if (typeof decl === 'string') {\n return { specifier: decl };\n }\n\n if (Array.isArray(decl)) {\n const [specifier, options] = decl;\n return { specifier, options };\n }\n\n if ('package' in decl) {\n return { specifier: decl.package, options: decl.options, enable: decl.enable };\n }\n\n if ('path' in decl) {\n return { specifier: decl.path, options: decl.options, enable: decl.enable };\n }\n\n throw new Error(`Invalid plugin declaration: ${JSON.stringify(decl)}`);\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport type { Plugin } from 'esbuild';\nimport { buildAliasPlugins } from './aliasPlugin';\nimport { collectRelativeImports } from './collectImports';\nimport { toProdExtension, toRealPath } from '../utils/prodPaths';\n\n/**\n * 基础配置文件查找顺序(与 loadConfig 保持一致)\n */\nconst BASE_CONFIG_FILES = ['faapi.config.ts', 'faapi.config.js'];\n\n/**\n * 查找基础配置文件(返回源文件名,含后缀)\n *\n * 返回相对 rootDir 的路径(如 'faapi.config.ts'),供 esbuild 作为 entryPoint。\n */\nfunction findBaseConfig(rootDir: string): string | null {\n for (const f of BASE_CONFIG_FILES) {\n if (fs.existsSync(path.join(rootDir, f))) {\n return f;\n }\n }\n return null;\n}\n\n/**\n * 配置文件名 → 产物 import 名(如 'faapi.config.ts' → 'faapi.config.js')\n */\nfunction toProdImport(filename: string): string {\n return toProdExtension(filename);\n}\n\nexport interface CompileConfigOptions {\n /** 项目根目录 */\n rootDir: string;\n /** 输出目录(如 dist) */\n dist: string;\n}\n\nexport interface CompileConfigResult {\n /** 是否生成了配置产物(无基础配置文件时不生成) */\n generated: boolean;\n /** 输出文件绝对路径(generated=false 时为空字符串) */\n outputFile: string;\n /** 是否命中 mtime 短路缓存跳过了编译(watcher 重建场景,输入无变化时省 3 次 esbuild build) */\n skipped: boolean;\n}\n\n/**\n * compileConfig 的 mtime 短路缓存\n *\n * watcher 每次重建都会调 compileConfig;若无短路,即使配置源毫无变化,\n * 也要付出 3 次 esbuild build + collectRelativeImports 递归读整个依赖图的开销。\n *\n * key 为 `rootDir::dist`,value 记录上次编译的全部输入文件 mtime。\n * 再次调用时输入文件集合 mtime 全部一致且产物仍存在 → 跳过编译;\n * 任一变化/缺失 → 全量重编译并更新缓存。编译抛错时不写缓存(下次可重试)。\n */\ninterface CompileConfigCacheEntry {\n /** 上次编译的输入文件(绝对路径 → mtimeMs) */\n inputs: Map<string, number>;\n /** 主产物绝对路径(faapi-config.js) */\n outputFile: string;\n}\n\nconst compileConfigCache = new Map<string, CompileConfigCacheEntry>();\n\n/** 检查缓存是否仍然新鲜:产物存在 + 全部输入文件存在且 mtime 一致 */\nasync function isCacheFresh(entry: CompileConfigCacheEntry): Promise<boolean> {\n try {\n await fs.promises.stat(entry.outputFile);\n } catch {\n return false;\n }\n for (const [filePath, mtimeMs] of entry.inputs) {\n try {\n const stat = await fs.promises.stat(filePath);\n if (stat.mtimeMs !== mtimeMs) return false;\n } catch {\n return false;\n }\n }\n return true;\n}\n\n/**\n * 匹配 from '...' / from \"...\" / import('...') / import(\"...\") 中的 specifier\n * 与递归收集逻辑见 collectImports.ts(compileConfig 与 compileOnDemand 共用)\n */\n\n/**\n * 创建 external 相对路径插件(步骤 2 用)\n *\n * 在 `bundle: true` 模式下,把所有相对路径 import(./xxx 或 ../xxx)标记为 external,\n * 阻止 esbuild 把已编译的 config 产物 inline 进 faapi-config.js。\n *\n * 这使 faapi-config.js 保留 `import base from './faapi.config.js'` 语句,运行时\n * Node.js ESM loader 加载 faapi.config.js,后者再 import 项目模块产物——\n * 与 routes 编译的同一文件共享,instanceof 生效。\n */\nfunction createExternalRelativePlugin(): Plugin {\n return {\n name: 'faapi-external-relative',\n setup(build) {\n // 相对路径(./ 或 ../开头)标记为 external\n build.onResolve({ filter: /^\\.{1,2}\\// }, (args) => ({\n path: args.path,\n external: true,\n }));\n },\n };\n}\n\n/**\n * 编译 src 外入口文件及其项目内依赖闭包(compileConfig / 本地 TS 插件编译共用)\n *\n * - 入口 + src 外依赖:outbase=rootDir,产物在 `<dist>/` 下保留相对结构\n * (如 `plugins/db-skills.ts` → `dist/plugins/db-skills.js`)\n * - src 内依赖:outbase=rootDir/src(打平前缀,与 compileDevRoutes 一致),\n * 使入口产物 import 的 src 内模块与 routes/config 共享同一份产物(instanceof 生效)\n * - aliasPlugin 重写 specifier:相对路径加 .js 后缀;src 外 importer 引用 src 内\n * 模块时剥离前缀(import 路径相对 importer 产物位置计算,见 aliasPlugin 的\n * toProdImportFromImporter)\n *\n * @param entryPoints src 外入口文件(绝对路径);src 内入口请走 compileDevRoutes /\n * compileBuildRoutes / ensureCompiled(打平产物 + 同一运行时对象)\n * @returns 收集到的 src 内/外依赖文件(供调用方记录 mtime 缓存)\n */\nexport async function compileProjectModules(\n entryPoints: string[],\n rootDir: string,\n dist: string,\n): Promise<{ insideFiles: string[]; outsideFiles: string[] }> {\n const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);\n\n const esbuild = await import('esbuild');\n const aliasPlugins = buildAliasPlugins(rootDir);\n const absDist = path.resolve(rootDir, dist);\n\n // src 外文件(outbase=rootDir):入口 + src 外依赖,产物保留相对结构\n await esbuild.build({\n entryPoints: [...entryPoints, ...outsideFiles],\n outdir: absDist,\n outbase: rootDir,\n bundle: false,\n platform: 'node',\n format: 'esm',\n sourcemap: true,\n packages: 'external',\n plugins: aliasPlugins,\n logLevel: 'silent',\n });\n\n // src 内依赖(outbase=rootDir/src,打平前缀,与 compileDevRoutes 一致)\n if (insideFiles.length > 0) {\n const appOutbase = path.resolve(rootDir, 'src');\n await esbuild.build({\n entryPoints: insideFiles,\n outdir: absDist,\n outbase: appOutbase,\n bundle: false,\n platform: 'node',\n format: 'esm',\n sourcemap: true,\n packages: 'external',\n plugins: aliasPlugins,\n logLevel: 'silent',\n });\n }\n\n return { insideFiles, outsideFiles };\n}\n\n/**\n * build 时编译配置文件,生成 `dist/faapi-config.js`\n *\n * 采用两步编译,使 config 引用的项目模块与 routes 共享同一份运行时对象(instanceof 跨边界生效):\n *\n * **步骤 1:逐文件编译 config 源文件(`bundle: false`)**\n * - 编译 `faapi.config.ts` → `dist/faapi.config.js`\n * - 递归收集 config 引用的项目模块,按 src 内/外分别编译:\n * - src 内:outbase=rootDir/src(打平前缀,与 compileDevRoutes 一致)→ `dist/lib/errors.js`\n * - src 外:outbase=rootDir → `dist/base.js`\n * - aliasPlugin 重写 specifier:相对路径加 .js 后缀;config 引用 src 内模块时剥离前缀\n *\n * **步骤 2:编译合并入口(`bundle: true` + external 相对路径)**\n * - 生成虚拟入口源码(import 已编译的 config 产物 + export base)\n * - 相对路径 import 标记为 external(不 inline config 产物)\n * - 第三方依赖(`packages: 'external'`)也保持 external\n * - 产物 `dist/faapi-config.js` 保留 `import base from './faapi.config.js'`\n *\n * 产物由 `loadConfig` 在运行时统一 import。环境变量通过 `.env` 文件加载(见 `loadEnv`),\n * 配置文件中通过 `process.env.XXX` 读取,运行时取值。不传 `define`,保留 `process.env` 表达式。\n */\nexport async function compileConfig(options: CompileConfigOptions): Promise<CompileConfigResult> {\n const { rootDir, dist } = options;\n\n const baseConfigName = findBaseConfig(rootDir);\n if (!baseConfigName) {\n // 无基础配置文件:不生成产物,loadConfig 会返回 null\n return { generated: false, outputFile: '', skipped: false };\n }\n\n const absDist = path.resolve(rootDir, dist);\n await fs.promises.mkdir(absDist, { recursive: true });\n\n const outputFile = path.resolve(absDist, 'faapi-config.js');\n const cacheKey = `${toRealPath(rootDir)}::${dist}`;\n\n // mtime 短路:上次编译的全部输入文件无变化且产物仍存在 → 跳过编译\n const cached = compileConfigCache.get(cacheKey);\n if (cached && (await isCacheFresh(cached))) {\n return { generated: true, outputFile: cached.outputFile, skipped: true };\n }\n compileConfigCache.delete(cacheKey);\n\n // 收集 config 入口文件(绝对路径)\n const configEntryPoints: string[] = [path.resolve(rootDir, baseConfigName)];\n\n // 步骤 1:逐文件编译 config 源 + 项目模块(共享实现见 compileProjectModules)\n const { insideFiles: appDirFiles, outsideFiles: nonAppDirFiles } = await compileProjectModules(\n configEntryPoints,\n rootDir,\n dist,\n );\n\n // 步骤 2:编译入口(bundle:true + external 相对路径)\n // 入口源码 import 已编译的 config 产物(带 .js 后缀)+ export base\n const baseImport = `import base from './${toProdImport(baseConfigName)}';`;\n const exportDefault = 'export default base;';\n\n const entryCode = [baseImport, exportDefault].join('\\n');\n\n const esbuild = await import('esbuild');\n await esbuild.build({\n stdin: { contents: entryCode, resolveDir: absDist, loader: 'ts' },\n outfile: outputFile,\n bundle: true,\n format: 'esm',\n platform: 'node',\n target: 'node20',\n sourcemap: true,\n packages: 'external',\n plugins: [createExternalRelativePlugin()],\n logLevel: 'silent',\n });\n\n // 编译成功:记录全部输入文件的 mtime(config 源 + src 内/外依赖模块 + tsconfig——\n // alias 重写依赖 tsconfig paths,tsconfig 变化必须触发重编译),\n // 供下次调用的 mtime 短路判断。编译抛错时不走此处(下次可重试)。\n const inputs = new Map<string, number>();\n const tsconfigPath = path.resolve(rootDir, 'tsconfig.json');\n const inputCandidates = [\n ...configEntryPoints,\n ...appDirFiles,\n ...nonAppDirFiles,\n ...(fs.existsSync(tsconfigPath) ? [tsconfigPath] : []),\n ];\n for (const filePath of inputCandidates) {\n try {\n const stat = await fs.promises.stat(filePath);\n inputs.set(filePath, stat.mtimeMs);\n } catch {\n // 收集阶段存在的文件理论上不会消失;消失则不缓存该文件,下次触发重编译\n }\n }\n compileConfigCache.set(cacheKey, { inputs, outputFile });\n\n return { generated: true, outputFile, skipped: false };\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 fg from 'fast-glob';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport type { ToolManifestList } from './toolTypes';\n\n/**\n * tool 扫描 patterns(框架约定,非用户可配置项)\n *\n * 与路由的 src/api 扫描对称——tool 文件约定放在 `src/tools` 下任意层级子目录的 handler.ts。\n * 所有 tool 都是共享的,agent 通过 config 块的 `tools` 字段显式声明引用哪些。\n */\nexport const TOOL_PATTERNS = ['src/tools/**/*.ts'];\n\n/**\n * 匹配源码中导出的函数名(任意合法标识符,区别于路由的固定 HTTP 方法名)\n *\n * 支持:\n * - `export function getWeather(input) {}`\n * - `export async function getWeather(input) {}`\n * - `export const getWeather = (input) => {}`\n * - `export const getWeather = async (input) => {}`\n *\n * 不匹配:\n * - `export interface WeatherInput {}`(interface 关键字)\n * - `export type X = ...`(type 关键字)\n * - `export { getWeather }`(命名导出引用,非声明)\n * - `export default function ...`(default 后非 function/const)\n *\n * 不通过运行时 import 提取函数名,避免启动时全量加载 tool 模块(Vite 风格:\n * tool 发现与 tool.js 加载解耦,tool.js 按需编译/导入)。\n */\nconst TOOL_EXPORT_RE = new RegExp(\n String.raw`export\\s+(?:async\\s+)?(?:function\\s+|const\\s+)([A-Za-z_$][\\w$]*)\\s*(?:\\(|=)`,\n 'g',\n);\n\n/**\n * 保留导出名(即使匹配正则也不识别为 tool)\n *\n * - `default` — 默认导出保留\n * - `config` — agent 配置块(scanAgents 使用)\n * - `run` — agent 自定义运行函数(scanAgents 使用)\n */\nconst RESERVED_EXPORTS = new Set(['default', 'config', 'run']);\n\n/**\n * 从源码内容中提取所有 tool 函数导出名(去重 + 排除保留名)\n */\nfunction extractToolExportsFromSource(source: string): Set<string> {\n const names = new Set<string>();\n let match: RegExpExecArray | null;\n TOOL_EXPORT_RE.lastIndex = 0;\n while ((match = TOOL_EXPORT_RE.exec(source)) !== null) {\n const name = match[1]!;\n if (!RESERVED_EXPORTS.has(name)) {\n names.add(name);\n }\n }\n return names;\n}\n\n/**\n * 从相对路径(已剥离前缀)提取命名空间\n *\n * weather/handler.ts → 'weather'\n * handler.ts → ''\n * a/b/handler.ts → 'a.b'\n */\nfunction extractNamespaceFromRelPath(relPath: string): string {\n const lastSlash = relPath.lastIndexOf('/');\n const dirPath = lastSlash === -1 ? '' : relPath.slice(0, lastSlash);\n if (!dirPath) return '';\n return dirPath.split('/').join('.');\n}\n\n/**\n * 从文件路径生成 tool 命名空间(子目录,用 . 连接,不含函数名)\n *\n * 命名空间生成规则:\n * 1. 剥离 `tools/` 前缀(找第一个 `tools/`)\n * 2. 剥离文件名(handler.ts)\n * 3. 剩余路径段用 `.` 连接\n *\n * 例子:\n * - src/tools/weather/handler.ts → 'weather'\n * - src/tools/handler.ts → ''\n * - src/tools/a/b/handler.ts → 'a.b'\n */\nfunction filePathToToolNamespace(filePath: string): string {\n const normalized = filePath.replace(/\\\\/g, '/');\n\n // 剥离 tools/ 前缀(找第一个 tools/)\n const toolsMatch = normalized.match(/(?:^|\\/)tools\\/(.+)$/);\n if (toolsMatch) {\n return extractNamespaceFromRelPath(toolsMatch[1]!);\n }\n\n // 不在 tools/ 下,返回空(不应发生)\n return '';\n}\n\n/**\n * 生成 tool 名:命名空间.函数名 或 纯函数名\n */\nfunction buildToolName(namespace: string, functionName: string): string {\n return namespace ? `${namespace}.${functionName}` : functionName;\n}\n\n/**\n * 扫描 tools 目录,生成 tool 清单\n *\n * Vite 风格:启动时只读源码 + 正则提取函数导出名,不 import tool.js。\n * tool.js 加载延后到 loadToolModule 请求阶段(详见 compileOnDemand)。\n *\n * tool 文件格式:handler.ts,导出任意函数即声明一个 tool(一个文件可多个)。\n * 排除保留导出名:default / config / run(agent 系统保留)。\n *\n * 所有 tool 都是共享的——放在 `src/tools` 下任意层级子目录的 handler.ts。\n * agent 通过 config 块的 `tools` 字段显式声明引用哪些 tool。\n *\n * tool 命名规则:子目录.函数名(如 weather.getWeather),无子目录时纯函数名。\n *\n * 重名检测:同名 tool 抛错。\n *\n * @param rootDir 项目根目录\n * @param patterns glob patterns(源码 .ts 路径,匹配 src/tools 下 tool 文件)\n */\nexport async function scanTools(rootDir: string, patterns: string[]): Promise<ToolManifestList> {\n const files = await fg(patterns, {\n cwd: rootDir,\n onlyFiles: true,\n absolute: false,\n });\n\n const tools: ToolManifestList = [];\n // 重名检测:toolName → filePath\n const seen = new Map<string, string>();\n\n for (const file of files) {\n const normalizedFile = file.replace(/\\\\/g, '/');\n const fileName = normalizedFile.split('/').pop()!;\n\n // 只处理 handler.ts(与路由 handler.ts 对称),其他 .ts 文件跳过\n if (fileName !== 'handler.ts' && fileName !== 'handler.js') {\n continue;\n }\n\n const absPath = path.resolve(rootDir, normalizedFile);\n const source = await fs.promises.readFile(absPath, 'utf8').catch(() => '');\n const exportNames = extractToolExportsFromSource(source);\n\n const namespace = filePathToToolNamespace(normalizedFile);\n\n for (const fnName of exportNames) {\n const toolName = buildToolName(namespace, fnName);\n\n // 重名检测:同名 tool 报错\n const prevFile = seen.get(toolName);\n if (prevFile) {\n throw new Error(\n `Tool conflict: \"${toolName}\" declared in both ${prevFile} and ${normalizedFile}`,\n );\n }\n\n seen.set(toolName, normalizedFile);\n\n tools.push({\n name: toolName,\n functionName: fnName,\n filePath: normalizedFile,\n });\n }\n }\n\n return tools;\n}\n","import fg from 'fast-glob';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport type { AgentManifestList } from './agentTypes';\n\n/**\n * 默认 agent 扫描 patterns\n *\n * 与 tool 扫描([TOOL_PATTERNS](../tools/scanTools.md))对称——\n * agent 定义文件约定放在 `src/agents/<agentName>/handler.ts`(一级目录)。\n *\n * 由 devCommand / buildCommand / createDevApp.reloadAgents(Phase 1.9)共享,\n * 避免多处重复定义。\n */\nexport const DEFAULT_AGENT_PATTERNS = ['src/agents/*/handler.ts'];\n\n/**\n * 检测 `run` 函数导出\n *\n * 匹配:\n * - `export function run() { ... }`\n * - `export async function run() { ... }`\n * - `export const run = () => { ... }`\n * - `export const run = async () => { ... }`\n *\n * 不匹配(`\\b` 词边界保证精确匹配 `run`):\n * - `export const runtime = ...`(run 后跟 `time`)\n *\n * > `config` 导出检测已移除(`hasConfig` 字段已废弃,见 [agentTypes](./agentTypes.md))。\n * > AST 提取阶段 [extractAgentMetadata](../ast/extractAgentMetadata.md) 仍会查找 config\n * > 导出(用于提取 JSDoc 描述 + config 块字段),但运行时不再需要 `hasConfig` 标志。\n */\nconst RUN_EXPORT_RE = /export\\s+(?:async\\s+)?(?:function\\s+|const\\s+)run\\b/;\n\n/**\n * 从文件路径提取 agent 名\n *\n * 匹配 `src/agents/<agentName>/handler.ts` 模式,提取 `<agentName>`。\n *\n * - `src/agents/researcher/handler.ts` → `researcher`\n * - `backup/agents/researcher/handler.ts` → `researcher`(任意前缀,只要匹配 agents/<name>/handler.ts)\n *\n * @throws 路径不匹配 agent 模式时抛错(不应发生——glob pattern 已限制)\n */\nfunction extractAgentNameFromPath(filePath: string): string {\n const normalized = filePath.replace(/\\\\/g, '/');\n const match = normalized.match(/(?:^|\\/)agents\\/([^/]+)\\/handler\\.ts$/);\n if (!match) {\n throw new Error(\n `Not an agent path: \"${filePath}\". Expected pattern: src/agents/<name>/handler.ts`,\n );\n }\n return match[1]!;\n}\n\n/**\n * 从源码检测 `run` 导出是否存在\n *\n * 不 import 模块——正则匹配源码文本(Vite 风格,与 [scanTools](../tools/scanTools.md) 的\n * `extractToolExportsFromSource` 同构)。\n */\nfunction detectAgentExports(source: string): { hasRun: boolean } {\n return {\n hasRun: RUN_EXPORT_RE.test(source),\n };\n}\n\n/**\n * 扫描 agents 目录,生成 agent 清单\n *\n * Vite 风格:启动时只读源码 + 正则检测 `run` 导出,不 import agent.js。\n * agent.js 加载延后到 [loadAgentModule](../loader/loadAgentModule.md) 请求阶段(Phase 1.9)。\n *\n * agent 文件格式:`src/agents/<agentName>/handler.ts`,导出 `config` 块(可选)和\n * `run` 函数(可选)。一个目录一份 handler.ts = 一个 agent。\n *\n * 重名检测:同 agent 名出现在多个文件 → 抛错(agent 名全局唯一,无作用域维度)。\n *\n * @param rootDir 项目根目录\n * @param patterns glob patterns(源码 .ts 路径,匹配 agent handler 文件)\n * @returns `AgentManifestList`\n */\nexport async function scanAgents(rootDir: string, patterns: string[]): Promise<AgentManifestList> {\n const files = await fg(patterns, {\n cwd: rootDir,\n onlyFiles: true,\n absolute: false,\n });\n\n const agents: AgentManifestList = [];\n // 重名检测:agentName → filePath\n const seen = new Map<string, string>();\n\n for (const file of files) {\n const normalizedFile = file.replace(/\\\\/g, '/');\n const fileName = normalizedFile.split('/').pop()!;\n\n // 只处理 handler.ts(与路由/tool handler.ts 对称),其他 .ts 文件跳过\n if (fileName !== 'handler.ts' && fileName !== 'handler.js') {\n continue;\n }\n\n const absPath = path.resolve(rootDir, normalizedFile);\n const source = await fs.promises.readFile(absPath, 'utf8').catch(() => '');\n const { hasRun } = detectAgentExports(source);\n const name = extractAgentNameFromPath(normalizedFile);\n\n // 重名检测:同 agent 名报错\n const prevFile = seen.get(name);\n if (prevFile) {\n throw new Error(\n `Agent conflict: \"${name}\" declared in both ${prevFile} and ${normalizedFile}`,\n );\n }\n seen.set(name, normalizedFile);\n\n agents.push({\n name,\n filePath: normalizedFile,\n hasRun,\n });\n }\n\n return agents;\n}\n","import type { AppBase, CreateAppOptions } from './createAppCore';\nimport { createAppBase, loadAndHydrateTools, loadAndHydrateAgents } from './createAppCore';\nimport { scanRoutes } from '../router/scanRoutes';\nimport { sortRoutes } from '../router/sortRoutes';\nimport { scanTools } from '../tools/scanTools';\nimport { TOOL_PATTERNS } from '../tools/scanTools';\nimport { generateToolArtifacts } from './generateToolArtifacts';\nimport { scanAgents } from '../agents/scanAgents';\nimport { DEFAULT_AGENT_PATTERNS } from '../agents/scanAgents';\nimport { generateAgentArtifacts } from './generateAgentArtifacts';\nimport { invalidateMiddlewareCache } from '../middleware/loadMiddlewares';\nimport { invalidateProgramCache } from '../ast/createProgram';\nimport { invalidateSchemaCache } from '../validator/validateInput';\nimport { setLoadTimestamp } from '../utils/importWithCacheBust';\nimport {\n clearCompiledFiles,\n clearGeneratedSchemas,\n deleteSchemaFiles,\n isDevOnDemandEnabled,\n} from './compileOnDemand';\n\n/** dev 应用接口(AppBase + reloadRoutes/reloadTools/reloadAgents 热替换) */\nexport interface DevApp extends AppBase {\n /** 重新水合路由清单 + 清 schema 缓存 + 更新 server 路由引用(dev 热替换用) */\n reloadRoutes(): Promise<void>;\n /** 重新扫描 tools + 重生成 faapi-tools.js + 清缓存(dev 热替换用) */\n reloadTools(): Promise<void>;\n /** 重新扫描 agents + 重生成 faapi-agents.js + 清缓存(dev 热替换用) */\n reloadAgents(): Promise<void>;\n}\n\n/**\n * dev 模式应用启动 API\n *\n * 在 createAppBase(共享逻辑)基础上增加 `reloadRoutes` 热替换能力,供 `faapi dev` watcher 调用。\n *\n * 与 createProdApp 的区别:\n * - dev:含 reloadRoutes(重新扫描路由 + 重新生成 schema + 清缓存 + 更新 server 路由引用)\n * - prod:精简,无 reloadRoutes(产物已固化,运行时不重建)\n *\n * 由 `devCommand` 直接调用,devCommand 持有 app 引用并传给 watcher。\n *\n * @example\n * ```ts\n * // devCommand 内部\n * const app = await createDevApp();\n * await app.listen();\n * startWatcher({ rootDir, app, devDist });\n * ```\n */\nexport async function createDevApp(options?: CreateAppOptions): Promise<DevApp> {\n const { app, ctx } = await createAppBase(options);\n\n const devApp = app as DevApp;\n\n devApp.reloadRoutes = async (): Promise<void> => {\n // 更新模块加载时间戳(ESM import 绕过缓存)\n setLoadTimestamp(Date.now());\n // 清理缓存(中间件/schema 已被 watcher 重新生成)\n invalidateMiddlewareCache();\n invalidateProgramCache();\n invalidateSchemaCache();\n // 清按需编译缓存(让被修改的 handler.js 重新编译)\n clearCompiledFiles();\n // 重新扫描路由\n // 不走 faapi-routes.js 重新 import——ESM 模块缓存难以可靠绕过,直接 scanRoutes 更稳定\n const reScanned = await scanRoutes(ctx.rootDir, ctx.patterns, ctx.dist);\n const sorted = sortRoutes(reScanned.routes);\n\n if (isDevOnDemandEnabled()) {\n // 按需模式:删除 stale zod.js(类型引用变化等),后台批量预生成 + 请求兜底\n // 不在 reload 路径上 await 生成——保持热替换轻快\n await deleteSchemaFiles(sorted, ctx.rootDir, ctx.dist);\n clearGeneratedSchemas();\n // 后台预生成全量 zod.js:此前只删不生成,每次保存后所有路由的首个请求都要\n // 在请求路径上同步付全项目 Program 创建 + schema 生成的代价(p99 尖刺)。\n // 预生成不阻塞 reload 与请求;请求路径的 ensureSchemaGenerated(mtime 缓存 +\n // in-flight mutex + 原子写)与之自然协同——已生成命中 mtime 跳过,并发生成\n // 各自原子落盘内容一致\n void (async (): Promise<void> => {\n try {\n const { generateSchemaFiles } = await import('./generateSchemaFiles');\n await generateSchemaFiles(sorted, ctx.rootDir, ctx.dist);\n } catch (err) {\n // 后台失败不崩进程——请求路径的按需生成仍会兜底\n console.error('[faapi] Background schema regeneration failed:', err);\n }\n })();\n } else {\n // 非按需模式(兼容旧路径):全量重新生成 zod.js\n const { generateSchemaFiles } = await import('./generateSchemaFiles');\n await generateSchemaFiles(sorted, ctx.rootDir, ctx.dist);\n }\n\n // 更新 app 和 server 路由引用\n ctx.updateRoutes(sorted, reScanned.wsRoutes);\n };\n\n devApp.reloadTools = async (): Promise<void> => {\n // 更新模块加载时间戳(ESM import 绕过缓存,让 faapi-tools.js 重新读取)\n setLoadTimestamp(Date.now());\n // 清 Program 缓存(tool 源码可能变化,AST 需重新分析)\n invalidateProgramCache();\n // 重新扫描 tools(零 import,仅读源码 + 正则提取函数名)\n const tools = await scanTools(ctx.rootDir, TOOL_PATTERNS);\n // 重生成 faapi-tools.js(含 AST 增强:description / inputTypeName)\n // 按需模式跳过 zod.js 生成——首次请求时按需生成(与 reloadRoutes 的策略一致)\n await generateToolArtifacts(tools, ctx.rootDir, ctx.dist, {\n skipSchema: isDevOnDemandEnabled(),\n });\n // 重新水合 faapi-tools.js 到 toolRegistry(reload 后需更新注册表)\n await loadAndHydrateTools(ctx.rootDir, ctx.dist, ctx.registries);\n };\n\n devApp.reloadAgents = async (): Promise<void> => {\n // 更新模块加载时间戳(ESM import 绕过缓存,让 faapi-agents.js 重新读取)\n setLoadTimestamp(Date.now());\n // 清 Program 缓存(agent 源码可能变化,AST 需重新分析)\n invalidateProgramCache();\n // 重新扫描 agents(零 import,仅读源码 + 正则检测 config/run 导出)\n const agents = await scanAgents(ctx.rootDir, DEFAULT_AGENT_PATTERNS);\n // 重生成 faapi-agents.js(含 AST 增强:description / @agent 覆盖 / config 块字段)\n // agent 不生成 zod.js(无输入参数),无 skipSchema 选项\n await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);\n // 重新水合 faapi-agents.js 到 agentRegistry(reload 后需更新注册表)\n await loadAndHydrateAgents(ctx.rootDir, ctx.dist, ctx.registries);\n };\n\n return devApp;\n}\n","import { createAppBase } from './createAppCore';\nimport type { AppBase, CreateAppOptions } from './createAppCore';\n\n/** prod 应用接口(AppBase,无 reloadRoutes) */\nexport type ProdApp = AppBase;\n\n// 重新导出 CreateAppOptions 供 createApp.ts 别名使用\nexport type { CreateAppOptions };\n\n/**\n * prod 模式应用启动 API\n *\n * 直接返回 createAppBase 结果(共享逻辑),不含 dev 专用能力(reloadRoutes、缓存失效)。\n * 产物在 `faapi build` 阶段已固化,运行时不重建。\n *\n * 框架采用零入口设计——用户无需编写 main.ts:\n * - `faapi build` 自动生成 `dist/main.js` 启动入口,内部调用 `createProdApp()` + `listen()` 启动生产服务器\n * - 用户自定义启动逻辑通过 `faapi.config.ts` 的 `lifecycle.onReady` / `onClose` 钩子实现\n *\n * 编程式调用场景(如自定义 CLI 启动器)也可直接调用:\n *\n * @example\n * ```ts\n * import { createProdApp } from '@faapi/faapi';\n * const app = await createProdApp();\n * await app.listen();\n * ```\n */\nexport async function createProdApp(options?: CreateAppOptions): Promise<ProdApp> {\n const { app } = await createAppBase(options);\n return app;\n}\n"],"mappings":"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","names":["ts","ts","ts","ts","path","path","fs","unionInner","path","getAgent","fs","ts","url","path","fs","path","fs","ts","path","fs","path","fs","ts","path","fs","path","fs","ts","path","fs","path","fs","path","fs","fs","inFlight","fs","path","fs","fs","fs","existsSync","path","ts","ts","ts","path","existsSync","path","fs","path","fs","fs","path","path","fs","path","Readable","Readable","path","path","params","body","url","text","headers","body","path","headers","DEFAULTS","etag","fs","path","fs","path","Readable","path","fs","path","path","ts","ts","path","path","fs","pathToFileURL","path","fs","path","path","fs","pathToFileURL","TOOLS_FILE","AGENTS_FILE","path","fs","body","Readable","fg","path","fs","path","path","fs","fg","fg","path","fs","fg","path","fs","generateSchemaFiles"]}
|