@faapi/faapi 1.0.2 → 1.1.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 +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +4 -0
- package/dist/index.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/extractHandlerTypes.ts","../src/ast/resolveTypeNode.ts","../src/runtime/inputType.ts","../src/validator/schemaName.ts","../src/injection/analyzeInjection.ts","../src/injection/resolveInjection.ts","../src/cli/collectRouteSchemaSources.ts","../src/middleware/cors.ts","../src/middleware/logger.ts","../src/middleware/helmet.ts","../src/config/loadConfig.ts","../src/utils/importWithCacheBust.ts","../src/cli/loadEnv.ts","../src/errors/FaapiError.ts","../src/errors/httpErrors.ts","../src/runtime/sse.ts","../src/runtime/createContext.ts","../src/utils/isPlainObject.ts","../src/response/toResponse.ts","../src/utils/queryToObject.ts","../src/injection/injectParams.ts","../src/runtime/invokeHandler.ts","../src/testServer.ts","../src/router/scanRoutes.ts","../src/router/constants.ts","../src/utils/normalizePath.ts","../src/router/parseRouteFile.ts","../src/middleware/loadMiddlewares.ts","../src/router/sortRoutes.ts","../src/cli/generateSchemaFiles.ts","../src/ast/generateZodSchema.ts","../src/validator/validateInput.ts","../src/server/createServer.ts","../src/router/matchRoute.ts","../src/loader/resolveExports.ts","../src/loader/validateRouteModule.ts","../src/loader/loadRouteModule.ts","../src/utils/parseJsonBody.ts","../src/utils/parseMultipart.ts","../src/runtime/resolveInput.ts","../src/response/sendNodeResponse.ts","../src/utils/getClientIp.ts","../src/server/handleWsUpgrade.ts","../src/errors/formatErrorResponse.ts","../src/server/serverUtils.ts","../src/runtime/wsHandler.ts","../src/wsTestClient.ts","../src/cli/createAppCore.ts","../src/router/detectRouteConflicts.ts","../src/server/startServer.ts","../src/cli/generateRoutes.ts","../src/cli/loadPlugins.ts","../src/cli/createDevApp.ts","../src/cli/createProdApp.ts"],"sourcesContent":["import ts from 'typescript';\n\n/**\n * Program 缓存,key 为文件路径\n *\n * watch 模式下通过 invalidateProgramCache() 全量清理。\n * 同一文件多次创建 Program 时复用缓存,避免重复解析。\n */\nconst programCache = new Map<string, ts.Program>();\n\n/**\n * 清理所有 Program 缓存(watch 模式下文件变化时调用)\n *\n * 全量清理而非增量清理,理由:\n * - 简单可靠,无状态一致性问题\n * - 跨文件类型引用需要所有文件的 Program 同步更新\n * - dev 模式文件量有限,全量重建在百毫秒级\n */\nexport function invalidateProgramCache(): void {\n programCache.clear();\n}\n\n/**\n * 为指定文件创建 TypeScript 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 = ts.createProgram([filePath], {\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 programCache.set(filePath, program);\n return program;\n}\n","import ts from 'typescript';\nimport {\n resolveTypeNode,\n resolveInterfaceDeclaration,\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 *\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 // 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 return result;\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 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}\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 );\n throw enriched;\n }\n throw err;\n }\n}\n","import ts from 'typescript';\n\n/**\n * Schema 提取错误\n *\n * 遇到无法解析或不支持运行时校验的类型时抛出,\n * 避免静默降级为 any 导致用户不知情。\n */\nexport class SchemaExtractionError extends Error {\n constructor(\n public readonly typeText: string,\n public readonly reason: string,\n options?: ErrorOptions,\n ) {\n super(`无法解析类型 \"${typeText}\": ${reason}`, options);\n this.name = 'SchemaExtractionError';\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[] }\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 */\nexport function resolveTypeNode(\n typeNode: ts.TypeNode,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\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),\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);\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),\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),\n optional: true,\n rest: false,\n };\n }\n // 普通元素\n return {\n type: resolveTypeNode(e, checker, visited),\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));\n return { kind: 'union', members };\n }\n\n // 交叉类型:A & B → 合并对象属性\n if (ts.isIntersectionTypeNode(typeNode)) {\n const properties: PropertyType[] = [];\n for (const t of typeNode.types) {\n const resolved = resolveTypeNode(t, checker, visited);\n if (resolved.kind === 'object') {\n properties.push(...resolved.properties);\n }\n }\n return { kind: 'object', properties };\n }\n\n // 内联对象类型:{ name: string; age?: number }\n if (ts.isTypeLiteralNode(typeNode)) {\n return resolveTypeLiteral(typeNode, checker, visited);\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);\n }\n\n // 引用类型:Date / 自定义 interface / Array<T> / Record<K,V> / Partial<T> 等\n if (ts.isTypeReferenceNode(typeNode)) {\n return resolveTypeReference(typeNode, checker, visited);\n }\n\n // 其他无法识别的语法节点\n throw new SchemaExtractionError(typeNode.getText(), '不支持的类型语法');\n}\n\n/**\n * 解析内联对象类型字面量\n */\nfunction resolveTypeLiteral(\n typeNode: ts.TypeLiteralNode,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n): RuntimeType {\n const properties: PropertyType[] = [];\n\n for (const member of typeNode.members) {\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)\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 → 转为 record\n if (ts.isIndexSignatureDeclaration(member)) {\n const keyType = member.parameters[0]?.type\n ? resolveTypeNode(member.parameters[0].type, checker, visited)\n : { kind: 'any' as const };\n const valueType = member.type\n ? resolveTypeNode(member.type, checker, visited)\n : { kind: 'any' as const };\n return { kind: 'record', key: keyType, value: valueType };\n }\n }\n\n return { 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 * 解析类型引用(Date / 自定义 interface / Array<T> / Record<K,V> 等)\n */\nfunction resolveTypeReference(\n typeNode: ts.TypeReferenceNode,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n): RuntimeType {\n const typeName = typeNode.typeName.getText();\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 return {\n kind: 'array',\n element: resolveTypeNode(typeNode.typeArguments[0], checker, visited),\n };\n }\n\n // Record<K, V>\n if (typeName === 'Record' && typeNode.typeArguments?.length === 2) {\n return {\n kind: 'record',\n key: resolveTypeNode(typeNode.typeArguments[0], checker, visited),\n value: resolveTypeNode(typeNode.typeArguments[1], checker, visited),\n };\n }\n\n // Partial<T> / Required<T> / Readonly<T> — best effort,解析内部类型\n if (\n (typeName === 'Partial' || typeName === 'Required' || typeName === 'Readonly') &&\n typeNode.typeArguments?.length === 1\n ) {\n const inner = resolveTypeNode(typeNode.typeArguments[0], checker, visited);\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 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);\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));\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 return {\n kind: 'map',\n key: resolveTypeNode(typeNode.typeArguments[0], checker, visited),\n value: resolveTypeNode(typeNode.typeArguments[1], checker, visited),\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 return {\n kind: 'set',\n element: resolveTypeNode(typeNode.typeArguments[0], checker, visited),\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 typeNode.typeName.kind === ts.SyntaxKind.Identifier\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(declaration, checker, visited);\n }\n // type 别名声明\n if (ts.isTypeAliasDeclaration(declaration)) {\n return resolveTypeNode(declaration.type, checker, visited);\n }\n // enum 声明 → 字面量联合\n if (ts.isEnumDeclaration(declaration)) {\n return resolveEnumDeclaration(declaration);\n }\n }\n }\n }\n\n throw new SchemaExtractionError(typeNode.getText(), `无法解析的引用类型 \"${typeName}\"`);\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 */\nfunction 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 */\nexport function resolveInterfaceDeclaration(\n node: ts.InterfaceDeclaration,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n): RuntimeType {\n const properties: PropertyType[] = [];\n const propMap = new Map<string, PropertyType>();\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);\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 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)\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 // 索引签名 → record\n if (ts.isIndexSignatureDeclaration(member)) {\n const keyType = member.parameters[0]?.type\n ? resolveTypeNode(member.parameters[0].type, checker, visited)\n : { kind: 'any' as const };\n const valueType = member.type\n ? resolveTypeNode(member.type, checker, visited)\n : { kind: 'any' as const };\n return { kind: 'record', key: keyType, value: valueType };\n }\n }\n\n for (const prop of propMap.values()) {\n properties.push(prop);\n }\n\n return { kind: 'object', properties };\n}\n\n/**\n * 数值约束标签集合(仅 number 字段)\n */\nconst NUMBER_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'max',\n 'min',\n 'int',\n 'positive',\n 'negative',\n 'nonnegative',\n 'nonpositive',\n]);\n\n/**\n * 长度约束标签集合(string 或 array 字段)\n */\nconst LENGTH_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'maxLength',\n 'minLength',\n 'length',\n]);\n\n/**\n * 字符串格式约束标签集合(仅 string 字段)\n */\nconst STRING_FORMAT_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'regex',\n 'email',\n 'url',\n 'uuid',\n]);\n\n/**\n * 从字段的 JSDoc 注释中提取约束标签\n *\n * 解析 `/** ... *\\/` 块注释中的 `@max`/`@min`/`@maxLength` 等标签。\n * 行内注释 `// @max 100` 不识别。\n *\n * @param node 字段节点(PropertySignature)\n * @param fieldName 字段名,用于错误信息\n * @returns 约束数组(无约束时返回空数组)\n */\nfunction extractConstraintsFromJsDoc(\n node: ts.PropertySignature,\n fieldName: string,\n): TypeConstraint[] {\n // ts.getJSDocCommentsAndTags 返回 JSDoc/JsDocTag 数组\n const jsDocs = ts\n .getJSDocCommentsAndTags(node)\n .filter((entry): entry is ts.JSDoc => ts.isJSDoc(entry));\n\n if (jsDocs.length === 0) return [];\n\n const constraints: TypeConstraint[] = [];\n\n for (const jsDoc of jsDocs) {\n if (!jsDoc.tags) continue;\n for (const tag of jsDoc.tags) {\n const constraint = parseJsDocTag(tag, fieldName);\n if (constraint) constraints.push(constraint);\n }\n }\n\n return constraints;\n}\n\n/**\n * 提取 JSDoc 标签后的文本注释\n *\n * `tag.comment` 类型为 `string | NodeArray<JSDocComment> | undefined`,\n * 此处仅取纯文本形式(约束标签后跟的均为字面量值,不会包含 JSDocLink 等节点)。\n *\n * @returns 标签后的文本;无文本或非纯字符串时返回 undefined\n */\nfunction getTagCommentText(tag: ts.JSDocTag): string | undefined {\n const comment = tag.comment;\n if (typeof comment === 'string') return comment;\n return undefined;\n}\n\n/**\n * 解析单个 JSDoc 标签为约束\n *\n * @param tag JSDocTag 节点\n * @param fieldName 字段名,用于错误信息\n * @returns 约束对象;非约束标签返回 null\n */\nfunction parseJsDocTag(tag: ts.JSDocTag, fieldName: string): TypeConstraint | null {\n const tagName = tag.tagName.text;\n\n switch (tagName) {\n // 数值约束(带值)\n case 'max':\n case 'min': {\n const value = parseNumberValue(tag, fieldName, tagName);\n return { kind: tagName, value };\n }\n\n // 长度约束(带值)\n case 'maxLength':\n case 'minLength':\n case 'length': {\n const value = parseNumberValue(tag, fieldName, tagName);\n return { kind: tagName, value };\n }\n\n // 正则约束(带 /pattern/flags 值)\n case 'regex':\n case 'pattern': {\n const text = getTagCommentText(tag);\n if (!text) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签需要 /pattern/flags 形式的值`);\n }\n const regex = parseRegexLiteral(text.trim(), fieldName);\n return { kind: 'regex', pattern: regex.pattern, flags: regex.flags };\n }\n\n // 数值约束(无值)\n case 'int':\n case 'positive':\n case 'negative':\n case 'nonnegative':\n case 'nonpositive':\n return { kind: tagName };\n\n // 字符串格式约束(无值)\n case 'email':\n case 'url':\n case 'uuid':\n return { kind: tagName };\n\n default:\n return null;\n }\n}\n\n/**\n * 解析标签后的数字值\n *\n * @param tag JSDocTag 节点\n * @param fieldName 字段名,用于错误信息\n * @param tagName 标签名,用于错误信息\n */\nfunction parseNumberValue(tag: ts.JSDocTag, fieldName: string, tagName: string): number {\n const text = getTagCommentText(tag);\n if (!text) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签需要一个数字值`);\n }\n const trimmed = text.trim();\n const num = Number(trimmed);\n if (!Number.isFinite(num)) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签的值 \"${trimmed}\" 不是有效数字`);\n }\n return num;\n}\n\n/**\n * 解析 /pattern/flags 形式的正则字面量\n *\n * @param text 标签后的文本,如 \"/^[a-z]+$/i\"\n * @param fieldName 字段名,用于错误信息\n */\nfunction parseRegexLiteral(text: string, fieldName: string): { pattern: string; flags?: string } {\n // 必须以 / 开头,匹配结尾的 / 和可选 flags\n const match = /^\\/(.+)\\/([gimsuy]*)$/.exec(text);\n if (!match) {\n throw new SchemaExtractionError(fieldName, `正则值 \"${text}\" 不是 /pattern/flags 形式`);\n }\n const [, pattern, flags] = match;\n if (!pattern) {\n throw new SchemaExtractionError(fieldName, `正则值 \"${text}\" 的 pattern 部分为空`);\n }\n return flags ? { pattern, flags } : { pattern };\n}\n\n/**\n * 校验约束与字段类型是否匹配\n *\n * 不匹配时抛 SchemaExtractionError(复用现有错误类型)。\n *\n * @param constraints 约束数组\n * @param type 字段类型\n * @param fieldName 字段名,用于错误信息\n */\nfunction validateConstraints(\n constraints: TypeConstraint[],\n type: RuntimeType,\n fieldName: string,\n): void {\n if (constraints.length === 0) return;\n\n for (const constraint of constraints) {\n const kind = constraint.kind;\n\n if (NUMBER_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'number') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 number 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n\n if (LENGTH_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'string' && type.kind !== 'array') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 string 或 array 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n\n if (STRING_FORMAT_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'string') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 string 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n }\n}\n","/**\n * 根据 HTTP 方法判断主输入类型\n *\n * - GET / DELETE / HEAD:query(URL 查询参数)\n * - POST / PUT / PATCH:body(请求体)\n *\n * 注意:所有方法都可能同时有 query 和 body,\n * 这里返回的是\"主输入\"(用于校验和注入)。\n * DELETE 请求也支持 body(见 resolveInput.ts)。\n */\nexport function getInputTypeForMethod(method: string): 'query' | 'body' {\n const upper = method.toUpperCase();\n if (upper === 'GET' || upper === 'DELETE' || upper === 'HEAD') {\n return 'query';\n }\n return 'body';\n}\n\n/**\n * 判断方法是否有请求体\n *\n * POST / PUT / PATCH / DELETE 可能有 body\n * GET / HEAD 不应该有 body\n */\nexport function hasBody(method: string): boolean {\n const upper = method.toUpperCase();\n return upper === 'POST' || upper === 'PUT' || upper === 'PATCH' || upper === 'DELETE';\n}\n","/**\n * 根据 HTTP 方法和输入类型生成 schema 类型名\n * GET + query -> GETQuery\n * POST + body -> POSTBody\n * GET + params -> GETParams\n */\nexport function getSchemaName(method: string, inputType: 'query' | 'body' | 'params'): string {\n return `${method.toUpperCase()}${inputType.charAt(0).toUpperCase() + inputType.slice(1)}`;\n}\n","import ts from 'typescript';\nimport type { InjectionType } from './resolveInjection';\nimport { PARAM_TYPE_MAP } from './resolveInjection';\n\n/**\n * 参数元数据\n */\nexport interface ParamMeta {\n name: string; // 参数名\n type: InjectionType; // 注入类型\n typeName?: string; // 引用类型名(如 'Query')\n schema?: PropertyInfo[]; // 类型结构(内联类型时)\n}\n\n/**\n * 属性信息\n */\nexport interface PropertyInfo {\n name: string;\n type: string;\n optional: boolean;\n}\n\n/**\n * 注入元数据\n */\nexport interface InjectionMeta {\n params: ParamMeta[];\n}\n\n/**\n * AST 分析 handler 函数,提取参数注入元数据\n */\nexport function analyzeInjection(code: string, functionName: string): InjectionMeta {\n const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);\n\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 ts from 'typescript';\n\n/**\n * 参数注入类型\n */\nexport type InjectionType =\n | 'query'\n | 'body'\n | 'form'\n | 'headers'\n | 'params'\n | 'context'\n | 'cookies'\n | 'ip'\n | 'files'\n | 'fields'\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 */\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 files: 'files',\n fields: 'fields',\n};\n\n/**\n * 分析函数参数,决定需要注入什么内容\n *\n * 使用 TypeScript AST 解析 fn.toString() 的结果,\n * 正确处理解构参数、默认值、rest 参数等情况。\n *\n * 注意:运行时类型信息已被擦除,hasType 始终为 false。\n */\nexport function resolveInjection(fn: (...args: unknown[]) => unknown): InjectionItem[] {\n const fnStr = fn.toString();\n const params = extractParamsWithAst(fnStr);\n\n return 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\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 { createProgram } from '../ast/createProgram';\nimport { extractTypeInfo, extractAllTypes, type HandlerTypeInfo } from '../ast/extractHandlerTypes';\nimport { getInputTypeForMethod } from '../runtime/inputType';\nimport { getSchemaName } from '../validator/schemaName';\nimport { analyzeInjection } 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. 对每个文件 createProgram + extractAllTypes 收集所有类型\n * 3. 用 analyzeInjection + extractTypeInfo 提取每个路由的 schema 类型\n * 4. 同时返回按文件分组的 allTypesMap 和合并后的全局 allTypes\n *\n * 调用方基于返回的 sources 和 allTypes 各自做最终转换:\n * - dev:生成 JS 模块文件 → import 加载(用 allTypesByFile)\n * - prd:生成 JS 模块代码 → SchemaModuleEntry[](用 allTypesByFile)\n */\nexport function collectRouteSchemaSources(\n routes: RouteManifest,\n rootDir?: string,\n): {\n sources: RouteSchemaSource[];\n /** 按文件分组的类型映射(prd writeSchemaModule 用) */\n allTypesByFile: Map<string, Map<string, HandlerTypeInfo>>;\n /** 合并后的全局类型映射(兼容旧调用方保留,新路径使用 allTypesByFile) */\n mergedAllTypes: Map<string, HandlerTypeInfo>;\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 // 先收集所有文件的类型\n const programByFile = new Map<string, ReturnType<typeof createProgram>>();\n const allTypesByFile = new Map<string, Map<string, HandlerTypeInfo>>();\n const mergedAllTypes = new Map<string, HandlerTypeInfo>();\n for (const filePath of methodsByFile.keys()) {\n const program = createProgram(filePath);\n programByFile.set(filePath, program);\n const allTypes = extractAllTypes(program, filePath);\n allTypesByFile.set(filePath, allTypes);\n for (const [name, info] of allTypes) {\n mergedAllTypes.set(name, info);\n }\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 const code = sourceFile?.text ?? '';\n for (const method of entry.methods) {\n const inputType = getInputTypeForMethod(method);\n const schemaName = getSchemaName(method, inputType);\n const meta = analyzeInjection(code, 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 const typeInfo = param?.typeName ? extractTypeInfo(program, filePath, 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, allTypesByFile, mergedAllTypes };\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 if (!allowOrigin) {\n await next();\n return;\n }\n\n // 设置 CORS 响应头\n ctx.setHeader('Access-Control-Allow-Origin', allowOrigin);\n\n // 当 origin 为动态值(true 或数组)时,必须设置 Vary: Origin,\n // 防止 CDN/浏览器错误缓存针对某个 Origin 的响应给其他 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 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 { 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 * @returns 模块导出对象\n */\nexport async function importWithCacheBust(filePath: string): Promise<Record<string, unknown>> {\n // vitest 环境优先走 Vite pipeline(识别 tsconfig paths + vi.mock)\n const importActual = getVitestImportActual();\n if (importActual) {\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 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 const [, key, rawValue] = match;\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 // 单引号:字面量\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 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 * 校验问题类型\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","/**\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 { FaapiContext, ResponseMeta, CookieOptions } from './contextTypes';\nimport { createSseWriter, type SseWriter } from './sse';\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 */\nexport function createContext(\n request: Request,\n params: Record<string, string>,\n config: Record<string, unknown> = {},\n ip: string = '',\n): FaapiContext {\n const url = new URL(request.url);\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 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 const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n return new Response(JSON.stringify(data), {\n status: status ?? 200,\n headers,\n });\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 } as FaapiContext & { meta: ResponseMeta; __sseResponse?: Response; __sseWriter?: SseWriter };\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","export function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') {\n return false;\n }\n\n // 数组不是普通对象\n if (Array.isArray(value)) {\n return false;\n }\n\n // 检查原型链:普通对象的 prototype 是 Object.prototype 或 null(Object.create(null))\n const proto = Object.getPrototypeOf(value);\n return proto === null || proto === Object.prototype;\n}\n","import { isPlainObject } from '../utils/isPlainObject';\nimport type { ResponseMeta } from '../runtime/contextTypes';\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 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","export function queryToObject(params: URLSearchParams): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of params) {\n result[key] = value;\n }\n return result;\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';\n\n/**\n * 根据注入类型获取对应的值(内置)\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 '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 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 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 { injectParamsAsync } from '../injection/injectParams';\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 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 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(result, 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(result, meta);\n } catch (err) {\n // handler 抛错时关闭未完成的 SSE 流,避免泄漏\n autoCloseSseOnError();\n throw err;\n }\n };\n\n return await compose(middlewares, ctx, finalHandler);\n}\n","import type { Server } from 'node:http';\nimport path from 'node:path';\nimport os from 'node:os';\nimport fs from 'node:fs/promises';\nimport { scanRoutes } from './router/scanRoutes';\nimport { sortRoutes } from './router/sortRoutes';\nimport { generateSchemaFiles } from './cli/generateSchemaFiles';\nimport { invalidateSchemaCache } from './validator/validateInput';\nimport { createServer } from './server/createServer';\nimport type { RouteManifest, WsRouteManifest } from './router/routeTypes';\nimport type { FaapiMiddleware } from './middleware/middlewareTypes';\nimport type { InjectorMap } from './middleware/injectorTypes';\nimport type { CorsOptions } from './middleware/cors';\nimport type { HelmetOptions } from './middleware/helmet';\nimport type { LoggerOptions } from './middleware/logger';\nimport type { FaapiContext } from './runtime/contextTypes';\n\n/**\n * createTestServer 入参\n *\n * 业务方一行代码启动带 schema 校验的 E2E 测试服务器。\n * 详见 src/testServer.md。\n */\nexport interface TestServerOptions {\n /** 项目根目录(路由源码所在,必填) */\n rootDir: string;\n // 路由扫描 glob,相对 rootDir;默认 ['src/api/**/*.ts']\n patterns?: string[];\n /**\n * schema 产物输出目录(绝对路径或相对 rootDir)。\n * 不传时自动 mkdtemp 生成临时目录,close() 时清理。\n * 传值时 close() 仍会清理该目录。\n */\n dist?: string;\n /** CORS 中间件配置,默认 false(禁用,避免污染断言) */\n cors?: CorsOptions | boolean;\n /** 安全头配置,默认 false */\n helmet?: HelmetOptions | boolean;\n /** 请求日志配置,默认 false(避免污染测试输出) */\n logger?: LoggerOptions | boolean;\n /** 全局中间件(外层洋葱) */\n middlewares?: FaapiMiddleware[];\n /** 全局注入器 */\n injectors?: InjectorMap;\n /** 请求错误钩子(在错误响应生成后调用,用于副作用) */\n onError?: (error: unknown, ctx: FaapiContext) => Promise<void> | void;\n /** 业务配置(注入到 ctx.config) */\n config?: Record<string, unknown>;\n /** 请求体大小限制(字节),默认 10MB */\n bodyLimit?: number;\n}\n\n/**\n * createTestServer 返回值\n *\n * 业务方通过 baseUrl 发 fetch 请求,close() 一行完成 teardown。\n */\nexport interface TestServer {\n /** Node.js HTTP Server 实例(已 listen) */\n server: Server;\n /** 形如 http://localhost:<随机端口> */\n baseUrl: string;\n /** 排序后的路由清单 */\n routes: RouteManifest;\n /** WebSocket 路由清单 */\n wsRoutes: WsRouteManifest;\n /** schema 临时目录绝对路径(业务方调试时可查看生成的 zod.js) */\n schemaDist: string;\n /**\n * 关闭 server + 清理 schema 目录 + 清空 schema 模块缓存\n *\n * 内部顺序:\n * 1. server.closeAllConnections?.()(Node 18+,强制断开 WS / 长连接)\n * 2. server.close()\n * 3. fs.rm(schemaDist, { recursive, force })\n * 4. invalidateSchemaCache()\n *\n * 幂等:重复调用不会重复清理。\n */\n close(): Promise<void>;\n}\n\n/** 默认路由扫描 glob */\nconst DEFAULT_PATTERNS = ['src/api/**/*.ts'];\n\n/** 默认请求体大小限制:10MB */\nconst DEFAULT_BODY_LIMIT = 10 * 1024 * 1024;\n\n/**\n * 一键启动带 schema 校验的 E2E 测试服务器\n *\n * 内部流程:\n * 1. scanRoutes 扫描路由\n * 2. sortRoutes 排序\n * 3. mkdtemp 创建临时 schema 目录(或用传入的 dist)\n * 4. generateSchemaFiles 生成 zod.js\n * 5. createServer 创建 server(默认禁用 CORS/Helmet/Logger,避免污染断言)\n * 6. server.listen(0) 随机端口\n * 7. 返回 TestServer\n *\n * 详见 src/testServer.md。\n *\n * @param options rootDir 必填,其余可选\n * @returns TestServer 实例\n */\nexport async function createTestServer(options: TestServerOptions): Promise<TestServer> {\n const {\n rootDir,\n patterns = DEFAULT_PATTERNS,\n dist,\n cors = false,\n helmet = false,\n logger = false,\n middlewares,\n injectors,\n onError,\n config,\n bodyLimit = DEFAULT_BODY_LIMIT,\n } = options;\n\n // 1. 扫描 + 排序路由\n const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);\n const sorted = sortRoutes(routes);\n\n // 2. schema 临时目录(未传 dist 时自动 mkdtemp)\n const schemaDist = dist\n ? path.isAbsolute(dist)\n ? dist\n : path.resolve(rootDir, dist)\n : await fs.mkdtemp(path.join(os.tmpdir(), 'faapi-test-schema-'));\n\n // 3. 生成 zod.js\n await generateSchemaFiles(sorted, rootDir, schemaDist);\n\n // 4. 创建 server(默认禁用 CORS/Helmet/Logger,避免污染断言)\n const { server } = createServer({\n routes: sorted,\n rootDir,\n dist: schemaDist,\n cors,\n helmet,\n logger,\n middlewares,\n injectors,\n onError,\n config,\n wsRoutes,\n bodyLimit,\n });\n\n // 5. listen(0) 随机端口\n const baseUrl = await listenOnRandomPort(server);\n\n // 6. 关闭状态标记(幂等保护)\n let closed = false;\n\n const testServer: TestServer = {\n server,\n baseUrl,\n routes: sorted,\n wsRoutes,\n schemaDist,\n\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n\n // 强制断开 WS / 长连接(Node 18+)\n const s = server as Server & {\n closeAllConnections?: () => void;\n closeIdleConnections?: () => void;\n };\n s.closeAllConnections?.();\n s.closeIdleConnections?.();\n\n // 关闭 HTTP server\n await new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n\n // 清理 schema 临时目录(失败不阻塞 close 流程;force: true 已尽可能容错)\n await fs.rm(schemaDist, { recursive: true, force: true }).catch(() => {\n /* 清理失败不阻塞:closed 标记已设,目录可能在 OS 临时目录被外部清理 */\n });\n\n // 清空 schema 模块缓存,避免串扰后续测试\n invalidateSchemaCache();\n },\n };\n\n return testServer;\n}\n\n/**\n * server.listen(0) 取随机端口,返回 http://localhost:<port>\n *\n * 抽出来便于失败时抛清晰错误。\n */\nfunction listenOnRandomPort(server: Server): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n server.listen(0, () => {\n const addr = server.address();\n if (typeof addr === 'object' && addr !== null) {\n resolve(`http://localhost:${addr.port}`);\n } else {\n reject(new Error('Failed to get server address'));\n }\n });\n server.on('error', (err) => {\n reject(err);\n });\n });\n}\n","import fg from 'fast-glob';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport type { RouteManifest, WsRouteManifest } from './routeTypes';\nimport { isHttpMethod, type HttpMethod } from './constants';\nimport { filePathToUrlPath, extractParamNames, isCatchAllSegment } from './parseRouteFile';\nimport {\n loadMiddlewaresFile,\n getCachedMiddlewares,\n setCachedMiddlewares,\n type LoadedMiddlewareBundle,\n} from '../middleware/loadMiddlewares';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\n\n/**\n * 路由源码目录(写死为 src)\n */\nconst APP_DIR = 'src';\n\n/**\n * 把源码绝对路径转为产物绝对路径(用于 import 产物 .js)\n *\n * 产物结构打平 src/ 前缀:`src/api/hello/handler.ts` → `<dist>/api/hello/handler.js`\n *\n * @param sourceAbsPath 源码绝对路径(如 /root/src/api/hello/handler.ts)\n * @param rootDir 项目根目录\n * @param dist 产物目录(dist 或 .faapi)\n */\nfunction toProdAbsPath(sourceAbsPath: string, rootDir: string, dist: string): string {\n let rel = path.relative(rootDir, sourceAbsPath).replace(/\\\\/g, '/');\n // 去掉 src/ 前缀(打平产物结构)\n if (rel.startsWith(`${APP_DIR}/`)) {\n rel = rel.slice(APP_DIR.length + 1);\n }\n const prodRel = `${dist}/${rel.replace(/\\.ts$/, '.js')}`;\n return path.resolve(rootDir, prodRel);\n}\n\n/**\n * 从路由文件所在目录向上逐级查找 middlewares.ts(源码),\n * 按从根到路由目录的顺序合并中间件和注入器(父级在前,子级在后)。\n *\n * 若传入 dist,加载产物 middlewares.js(已编译);否则加载源码 middlewares.ts。\n *\n * 子级中间件包裹父级(洋葱模型下后注册的先执行 after);\n * 子级注入器覆盖父级同名注入器。\n *\n * @param routeFilePath 源码相对路径(如 src/api/hello/handler.ts)\n * @param rootDir 项目根目录\n * @param dist 产物目录(dist 或 .faapi),不传则加载源码\n */\nasync function findMergedMiddlewares(\n routeFilePath: string,\n rootDir: string,\n dist?: string,\n): Promise<{ middlewares: FaapiMiddleware[]; injectors: InjectorMap } | undefined> {\n const routeDir = path.dirname(routeFilePath);\n const resolvedRoot = path.resolve(rootDir);\n\n // 收集从根到路由目录的所有 middlewares 路径\n // dist 传入时查找产物 middlewares.js;否则查找源码 middlewares.ts(兼容旧路径)\n const mwPaths: string[] = [];\n let currentDir = path.resolve(rootDir, routeDir);\n while (true) {\n if (dist) {\n // 新模式:查找产物 middlewares.js\n const mwPath = path.join(currentDir, 'middlewares.js');\n const absMwPath = path.resolve(rootDir, mwPath);\n // 产物路径:把源码路径转为产物路径(打平 src/ 前缀)\n const prodAbsMwPath = toProdAbsPath(absMwPath, rootDir, dist);\n if (fs.existsSync(prodAbsMwPath)) {\n mwPaths.push(prodAbsMwPath);\n }\n } else {\n // 旧模式:查找源码 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 mwPaths.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 if (mwPaths.length === 0) return undefined;\n\n // 从根到路由目录逐级加载(mwPaths 是从路由目录向上收集的,需反转)\n mwPaths.reverse();\n\n const mergedMiddlewares: FaapiMiddleware[] = [];\n const mergedInjectors: InjectorMap = {};\n\n for (const absMwPath of mwPaths) {\n let bundle: LoadedMiddlewareBundle | undefined = getCachedMiddlewares(absMwPath);\n if (bundle === undefined) {\n bundle = await loadMiddlewaresFile(absMwPath);\n setCachedMiddlewares(absMwPath, bundle);\n }\n // 子级中间件追加在父级之后(洋葱模型:后注册的中间件在内层)\n mergedMiddlewares.push(...bundle.middlewares);\n // 子级注入器覆盖父级同名注入器\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/**\n * 从 handler 模块中提取导出的 HTTP 方法名\n *\n * @param absPath handler 文件路径(产物 .js 或源码 .ts,取决于调用方)\n */\nexport async function extractMethodsFromHandler(absPath: string): Promise<HttpMethod[]> {\n try {\n const module = await importWithCacheBust(absPath);\n const methods: HttpMethod[] = [];\n for (const key of Object.keys(module)) {\n if (isHttpMethod(key) && typeof module[key] === 'function') {\n methods.push(key);\n }\n }\n return methods;\n } catch (err) {\n // 模块加载失败时输出警告,避免静默吞没错误\n const reason = err instanceof Error ? err.message : String(err);\n console.warn(`[faapi] 加载路由文件失败 ${absPath}: ${reason}`);\n return [];\n }\n}\n\n/**\n * 检测 handler 模块是否导出 WS 函数\n *\n * WS 导出与 HTTP 方法导出(GET/POST 等)同级,导出名必须为 `WS`。\n *\n * @param absPath handler 文件路径(产物 .js 或源码 .ts,取决于调用方)\n */\nexport async function hasWsExport(absPath: string): Promise<boolean> {\n try {\n const module = await importWithCacheBust(absPath);\n return typeof module['WS'] === 'function';\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.warn(`[faapi] 加载路由文件失败(WS 检测)${absPath}: ${reason}`);\n return false;\n }\n}\n\n/**\n * 扫描 api 目录,同时生成 HTTP 路由清单和 WebSocket 路由清单\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 * @param rootDir 项目根目录\n * @param patterns glob patterns(源码 .ts 路径,如 src/api 下所有 .ts)\n * @param dist 产物目录(dist 或 .faapi)。传入时 import 产物 .js(不依赖 tsx);\n * 不传时 import 源码 .ts(旧模式,需要 tsx)。\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 // dist 传入时 import 产物 .js(打平 src/ 前缀);否则 import 源码 .ts\n const importPath = dist ? toProdAbsPath(absPath, rootDir, dist) : absPath;\n const urlPath = filePathToUrlPath(normalizedFile);\n const paramNames = extractParamNames(urlPath);\n const isDynamic = paramNames.length > 0;\n const isCatchAll = normalizedFile.split('/').some(isCatchAllSegment);\n const middlewareBundle = await findMergedMiddlewares(normalizedFile, rootDir, dist);\n\n // HTTP 方法导出\n const methods = await extractMethodsFromHandler(importPath);\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 middlewares: middlewareBundle?.middlewares,\n injectors: middlewareBundle?.injectors,\n });\n }\n\n // WS 导出\n const hasWs = await hasWsExport(importPath);\n if (hasWs) {\n wsRoutes.push({\n urlPath,\n filePath: normalizedFile,\n paramNames,\n isDynamic,\n isCatchAll: isCatchAll || undefined,\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 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 * 失效所有中间件缓存(watch 模式下文件变化时调用)\n */\nexport function invalidateMiddlewareCache(): void {\n middlewareCache.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 */\nexport async function loadMiddlewaresFile(filePath: string): Promise<LoadedMiddlewareBundle> {\n try {\n const module = await importWithCacheBust(filePath);\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 } catch {\n return { middlewares: [], injectors: {} };\n }\n}\n","import type { RouteManifest } from './routeTypes';\n\n/**\n * 路由排序规则:\n * 1. 静态路由优先于动态路由\n * 2. 动态路由优先于 catch-all 路由\n * 3. 路径段数少的优先\n * 4. 字母序排列\n */\nexport function sortRoutes(routes: RouteManifest): RouteManifest {\n return [...routes].sort((a, b) => {\n // 1. 静态路由优先于动态路由\n if (a.isDynamic !== b.isDynamic) {\n return a.isDynamic ? 1 : -1;\n }\n\n // 2. catch-all 路由优先级最低(排在所有动态路由之后)\n if (a.isCatchAll !== b.isCatchAll) {\n return a.isCatchAll ? 1 : -1;\n }\n\n // 3. 路径段数少的优先\n const aSegments = a.urlPath.split('/').filter(Boolean).length;\n const bSegments = b.urlPath.split('/').filter(Boolean).length;\n if (aSegments !== bSegments) {\n return aSegments - bSegments;\n }\n\n // 4. 字母序排列\n return a.urlPath.localeCompare(b.urlPath);\n });\n}\n","import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport type { RouteManifest } from '../router/routeTypes';\nimport type { RouteSchemaSource } from './collectRouteSchemaSources';\nimport type { HandlerTypeInfo } from '../ast/extractHandlerTypes';\nimport { collectRouteSchemaSources } from './collectRouteSchemaSources';\nimport {\n generateZodSchemaSource,\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 * 公用函数复用:schema 引用 coerceNumber / coerceBoolean / coerceMap / coerceSet 变量时,\n * 这些变量从 dist 根部的 faapi-helpers.js import(跨文件复用,仅一份声明)。\n *\n * @param sources 同一文件的 schema 提取结果(含多个方法,如 GETQuery + POSTBody)\n * @param allTypes 该文件的所有命名类型(用于解析循环引用中的 ref)\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 allTypes: Map<string, HandlerTypeInfo>,\n helpersImportPath: string,\n): string {\n const resolveType = (name: string) => allTypes.get(name)?.runtimeType;\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 // 推断 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 const block = [`// ${schemaName}`];\n // generateZodSchemaSource 自带 import 语句,剥离后由本函数统一管理 import\n // 传入 schemaName 作为 exportName,确保导出名与 validateInput 查找的一致\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 公用函数,若有则注入 import 语句\n // 公用函数包含 coerceNumber / coerceBoolean(query/params 的 string 转换)和\n // coerceMap / coerceSet(Map/Set 的 JSON 还原,body 场景也会引用)\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 * 为路由清单中每个 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, allTypesByFile } = 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 const allTypes = allTypesByFile.get(filePath) ?? new Map();\n\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 source = generateSchemaFileSource(fileSources, allTypes, helpersImportPath);\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 await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.writeFile(outputPath, source, 'utf-8');\n}\n","import type { RuntimeType, TupleElement, PropertyType, TypeConstraint } 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 }\n return;\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 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.properties, 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) => v === \"true\" || v === \"1\" ? true : v === \"false\" || v === \"0\" ? false : v;';\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 if (restExpression) {\n return `z.tuple([${fixedExprs.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 */\nfunction generateObjectExpression(properties: PropertyType[], ctx: CodeGenContext): string {\n const fields = 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 return `z.object({ ${fields.join(', ')} })`;\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 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 lines: string[] = [];\n lines.push(\"import { z } from 'zod';\");\n lines.push('');\n\n // 生成命名类型声明(z.lazy 处理循环引用)\n for (const [n, type] of ctx.namedTypes) {\n lines.push(generateNamedTypeDeclaration(n, type, ctx));\n }\n if (ctx.namedTypes.size > 0) lines.push('');\n\n // 生成入口 schema 导出(含循环引用时用 z.lazy)\n const entryExpr = runtimeTypeToZodExpression(typeInfo.runtimeType, ctx);\n const hasSelfRef = containsRef(typeInfo.runtimeType, new Set([typeInfo.name]));\n if (hasSelfRef) {\n lines.push(`export const ${name}Schema = z.lazy(() => ${entryExpr});`);\n } else {\n lines.push(`export const ${name}Schema = ${entryExpr};`);\n }\n\n return lines.join('\\n');\n}\n","import { getSchemaName } from './schemaName';\nimport { InternalError } from '../errors/httpErrors';\nimport type { ValidationIssue, ValidationErrorCode } from '../errors/httpErrors';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\n\n/**\n * 校验结果类型\n */\nexport interface ValidationResult {\n valid: boolean;\n issues: ValidationIssue[];\n data: Record<string, 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 mod = (await importWithCacheBust(schemaPath)) 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 // 无类型声明:跳过校验\n if (schema === undefined || schema === null) {\n const data =\n typeof input === 'object' && input !== null && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : {};\n return { valid: true, issues: [], data };\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 const inputObj =\n typeof input === 'object' && input !== null && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : {};\n\n // zod safeParse 校验(query/params 的 preprocess 已在 schema 生成阶段内联)\n const zodSchema = schema as { safeParse: (v: unknown) => ZodSafeParseResult };\n const result = zodSchema.safeParse(inputObj);\n\n if (result.success) {\n const data =\n typeof result.data === 'object' && result.data !== null && !Array.isArray(result.data)\n ? (result.data as Record<string, unknown>)\n : {};\n return { valid: true, issues: [], data };\n }\n\n // 将 zod issues 转为 ValidationIssue\n const issues = mapZodIssues(result.error);\n return { valid: false, issues, data: inputObj };\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 / invalid_union → TYPE_MISMATCH(422)\n * - too_small / too_big / invalid_string → INVALID_VALUE(422)\n * - invalid_value(v4 新增,合并了 v3 的 invalid_enum_value / invalid_literal)→ INVALID_VALUE(422)\n * - unrecognized_keys → INVALID_FORMAT(400)\n * - custom → 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.code, issue.message);\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\n/**\n * 映射 zod issue code 到框架 ValidationErrorCode\n */\nfunction mapZodCode(zodCode: string, message: string): ValidationErrorCode {\n switch (zodCode) {\n case 'invalid_type':\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_string':\n case 'too_small':\n case 'too_big':\n case 'invalid_intersection_types':\n case 'not_multiple_of':\n return 'INVALID_VALUE';\n case 'custom':\n return 'INVALID_VALUE';\n default:\n if (message.includes('Required') || message.includes('required')) {\n return 'MISSING_FIELD';\n }\n return 'INVALID_VALUE';\n }\n}\n\n/**\n * 从 zod message 中提取期望类型(兜底)\n */\nfunction mapExpectedFromMessage(message: string): string {\n // zod v3 message 示例:\"Expected string, received number\"\n const match = message.match(/Expected\\s+(\\w+)/i);\n return match ? match[1]!.toLowerCase() : 'unknown';\n}\n\n/**\n * 从 zod message 中提取实际类型(兜底)\n */\nfunction mapReceivedFromMessage(message: string): string {\n const match = message.match(/received\\s+(\\w+)/i);\n return match ? match[1]!.toLowerCase() : 'unknown';\n}\n","import {\n createServer as createHttpServer,\n type Server,\n type IncomingMessage,\n type ServerResponse,\n} from 'node:http';\nimport { createSecureServer as createHttp2SecureServer } from 'node:http2';\nimport { readFileSync } from 'node:fs';\nimport { Readable } from 'node:stream';\nimport path from 'node:path';\nimport type { RouteManifest, WsRouteManifest, RoutesRef } from '../router/routeTypes';\nimport { matchRoute, matchDynamicPath } from '../router/matchRoute';\nimport { loadRouteModule } from '../loader/loadRouteModule';\nimport { createContext } from '../runtime/createContext';\nimport { resolveInput } from '../runtime/resolveInput';\nimport { invokeHandler, compose, mergeMeta } from '../runtime/invokeHandler';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { sendNodeResponse } from '../response/sendNodeResponse';\nimport { RouteNotFoundError, MethodNotAllowedError, ValidationError } 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 { 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';\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(req: IncomingMessage, bodyLimit: number = DEFAULT_BODY_LIMIT): Request {\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 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 new Request(url.toString(), { method, headers });\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 new Request(url.toString(), {\n method,\n headers,\n body: limitedStream,\n duplex: 'half',\n } as RequestInit);\n}\n\n/**\n * 限制 ReadableStream 的总字节数,超过限制时抛错\n */\nfunction limitStreamSize(\n stream: ReadableStream<Uint8Array>,\n maxSize: number,\n): ReadableStream<Uint8Array> {\n let totalSize = 0;\n const reader = stream.getReader();\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n reader.releaseLock();\n return;\n }\n totalSize += value.byteLength;\n if (totalSize > maxSize) {\n controller.error(new Error(`请求体超过大小限制 ${maxSize} 字节`));\n reader.releaseLock();\n return;\n }\n controller.enqueue(value);\n },\n cancel(reason) {\n reader.cancel(reason);\n },\n });\n}\n\n/**\n * 检查指定路径是否有其他方法的路由(用于 405 响应)\n */\nfunction findAllowedMethods(routes: RouteManifest, path: string): string[] {\n const methods = new Set<string>();\n for (const route of routes) {\n if (route.urlPath === path) {\n methods.add(route.method);\n continue;\n }\n // 也检查动态路由\n if (route.isDynamic) {\n const params = matchDynamicPath(route.urlPath, path, route.paramNames, route.isCatchAll);\n if (params !== null) {\n methods.add(route.method);\n }\n }\n }\n return Array.from(methods);\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 /** 请求日志配置,默认启用(与 cors 一致) */\n logger?: LoggerOptions | boolean;\n /** 请求体大小限制(字节) */\n bodyLimit?: number;\n /** HTTP/2 配置,启用时需提供 SSL 证书路径 */\n http2?: Http2Options | 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 logger: loggerOption,\n bodyLimit = DEFAULT_BODY_LIMIT,\n http2: http2Option,\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 // 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 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 configMiddlewares,\n onError,\n config,\n globalMiddlewares,\n globalInjectors,\n bodyLimit,\n ).catch(() => {\n res.statusCode = 500;\n res.end();\n });\n });\n\n // 挂载 WebSocket 升级处理(仅当提供了 WS 路由)\n if (routesRef.wsCurrent.length > 0) {\n attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares });\n }\n\n return { server, routesRef };\n}\n\nasync function handleRequest(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n req: IncomingMessage,\n res: ServerResponse,\n configMiddlewares: FaapiMiddleware[],\n onError: ((error: unknown, ctx: FaapiContext) => Promise<void> | void) | undefined,\n config: Record<string, unknown> | undefined,\n globalMiddlewares: FaapiMiddleware[] | undefined,\n globalInjectors: InjectorMap | undefined,\n bodyLimit: number,\n): Promise<void> {\n const request = toWebRequest(req, bodyLimit);\n const method = request.method.toUpperCase();\n const urlPath = new URL(request.url).pathname;\n const ctx = createContext(request, {}, config, getClientIp(req));\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n\n // 路由处理管线:作为 CORS 中间件的 next\n // 包含路由匹配、静态文件、参数校验、handler 调用、响应格式化\n const routePipeline = async (): Promise<Response> => {\n // 尝试匹配路由\n const match = matchRoute(routes, method, urlPath);\n\n if (!match) {\n // 检查是否有其他方法匹配该路径\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 ctx.params = match.params;\n const { route } = match;\n\n // 处理 API 路由(handler.ts)\n const absoluteFilePath = path.resolve(rootDir, route.filePath);\n const routeModule = await loadRouteModule(absoluteFilePath, route.method);\n const input = await resolveInput(route.method, request);\n\n // 参数校验(运行时按 route.filePath 计算 zod.js 路径,import 并 safeParse)\n const inputType = getInputTypeForMethod(route.method);\n const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);\n const result = await validateInput(schemaPath, route.method, inputType, input);\n if (!result.valid) {\n throw new ValidationError('参数校验失败', result.issues);\n }\n\n const body = hasBody(route.method) ? result.data : undefined;\n // 合并注入器:全局注入器为基线,目录注入器覆盖同名\n const mergedInjectors = globalInjectors\n ? { ...globalInjectors, ...route.injectors }\n : route.injectors;\n const response = await invokeHandler(\n routeModule.handler,\n ctx,\n body,\n route.middlewares,\n mergedInjectors,\n );\n\n return response;\n };\n\n try {\n let response: Response;\n // 外层中间件链:配置中间件(CORS/helmet/logger)+ 全局中间件\n // 顺序:CORS → helmet → logger → 全局 → routePipeline(含目录中间件 + handler)\n const outerMiddlewares: FaapiMiddleware[] = [];\n if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);\n if (globalMiddlewares && globalMiddlewares.length > 0) {\n outerMiddlewares.push(...globalMiddlewares);\n }\n\n if (outerMiddlewares.length > 0) {\n response = await compose(outerMiddlewares, ctx, routePipeline);\n } else {\n response = await routePipeline();\n }\n await sendNodeResponse(response, res);\n } catch (err: unknown) {\n // 错误处理兜底链(参考 Fastify 语义):\n // 1. 框架内置 formatErrorResponse 兜底(handler 抛错时)\n // 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n // 3. 响应发出后 → onError 触发副作用(不修改已发出的响应)\n // 注:业务方如需自定义错误响应,在全局中间件中 try/catch next() 即可\n const errorResponse = buildErrorResponse(err);\n await sendNodeResponse(mergeMeta(errorResponse, meta), res);\n\n // 响应已发出,触发 onError 副作用(日志/告警),自身抛错被忽略\n if (onError) {\n try {\n await onError(err, ctx);\n } catch {\n // onError 自身抛错不影响已发出的响应\n }\n }\n }\n}\n","import type { RouteManifest, RouteMatch, WsRouteManifest, WsRouteMatch } from './routeTypes';\n\n/**\n * 根据请求路径和方法匹配路由\n * @param routes 已排序的路由清单\n * @param method HTTP 方法\n * @param path 请求路径\n * @returns 匹配结果,包含路由记录和参数\n */\nexport function matchRoute(routes: RouteManifest, method: string, path: string): RouteMatch | null {\n for (const route of routes) {\n // 方法不匹配,跳过\n if (route.method !== method) {\n continue;\n }\n\n // 静态路由直接比较\n if (!route.isDynamic) {\n if (route.urlPath === path) {\n return { route, params: {} };\n }\n continue;\n }\n\n // 动态路由匹配\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 * 匹配 WebSocket 路由(无 HTTP 方法维度)\n *\n * WS 路由只按路径匹配,协议升级时调用。\n * @param wsRoutes WebSocket 路由清单\n * @param path 请求路径\n */\nexport function matchWsRoute(wsRoutes: WsRouteManifest, path: string): WsRouteMatch | null {\n for (const route of wsRoutes) {\n // 静态路由直接比较\n if (!route.isDynamic) {\n if (route.urlPath === path) {\n return { route, params: {} };\n }\n continue;\n }\n\n // 动态路由匹配\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 * 动态路径匹配\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 const patternSegments = pattern.split('/').filter(Boolean);\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 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 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 const patternSeg = patternSegments[i];\n const pathSeg = pathSegments[i];\n\n if (patternSeg.startsWith(':')) {\n // 动态段,提取参数值\n const paramName = patternSeg.slice(1);\n params[paramName] = pathSeg;\n } else if (patternSeg !== pathSeg) {\n // 静态段不匹配\n return null;\n }\n }\n\n // 确保所有参数名都被提取到\n if (Object.keys(params).length !== paramNames.length) {\n return null;\n }\n\n return params;\n}\n","/**\n * 从模块中解析出指定名称的导出\n *\n * 解析顺序:\n * 1. 具名导出:export function GET() {}\n * 2. 默认导出的对象属性:export default { GET() {} }\n *\n * @param module 动态 import 得到的模块对象\n * @param exportName 导出名(如 'GET')\n */\nexport function resolveExport(module: Record<string, unknown>, exportName: string): unknown {\n // 1. 具名导出\n if (exportName in module && typeof module[exportName] !== 'undefined') {\n return module[exportName];\n }\n\n // 2. 默认导出的对象属性\n const defaultExport = module.default;\n if (defaultExport !== null && typeof defaultExport === 'object') {\n const value = (defaultExport as Record<string, unknown>)[exportName];\n if (value !== undefined) {\n return value;\n }\n }\n\n return undefined;\n}\n","/**\n * 校验导出值是否为合法的 handler 函数\n */\nexport function validateRouteModule(\n value: unknown,\n method: string,\n filePath: string,\n): asserts value is (...args: unknown[]) => unknown {\n if (typeof value !== 'function') {\n throw new Error(\n `Route module \"${filePath}\" does not export a valid handler for method \"${method}\". ` +\n `Expected a function, got ${typeof value}.`,\n );\n }\n}\n","import type { HttpMethod } from '../router/constants';\nimport { resolveExport } from './resolveExports';\nimport { validateRouteModule } from './validateRouteModule';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\n\nexport interface RouteModule {\n handler: (...args: unknown[]) => unknown;\n method: HttpMethod;\n}\n\n/**\n * 动态 import 路由文件并提取 handler\n * @param filePath 路由文件的绝对路径\n * @param method HTTP 方法名(也是导出名)\n */\nexport async function loadRouteModule(filePath: string, method: HttpMethod): Promise<RouteModule> {\n let module: Record<string, unknown>;\n try {\n module = await importWithCacheBust(filePath);\n } catch (err: unknown) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to load route module \"${filePath}\": ${reason}`, { cause: err });\n }\n\n const handler = resolveExport(module, method);\n validateRouteModule(handler, method, filePath);\n\n return { handler, method };\n}\n","export type JsonParseResult = { success: true; data: unknown } | { success: false; error: string };\n\nexport function parseJsonBody(text: string): JsonParseResult {\n try {\n const data = JSON.parse(text);\n return { success: true, data };\n } catch {\n return { success: false, error: 'Invalid JSON body' };\n }\n}\n","/**\n * 解析 multipart/form-data 请求\n * 使用 Web 标准 Request.formData() API\n */\n\nexport interface UploadedFile {\n name: string; // 字段名\n filename: string; // 原始文件名\n type: string; // MIME 类型\n size: number; // 文件大小(字节)\n arrayBuffer: () => Promise<ArrayBuffer>; // 获取文件内容\n}\n\nexport interface MultipartResult {\n /** 表单字段(同名字段用数组收集,单值保持 string) */\n fields: Record<string, string | string[]>;\n files: UploadedFile[];\n}\n\n/**\n * 解析 multipart/form-data\n *\n * 同名字段的处理:\n * - 第一次出现:string\n * - 第二次出现:string[](包含所有同名字段的值)\n * - 文件始终收集到 files 数组(通过 name 字段标识归属)\n */\nexport async function parseMultipart(request: Request): Promise<MultipartResult> {\n const formData = await request.formData();\n const fields: Record<string, string | string[]> = {};\n const files: UploadedFile[] = [];\n\n for (const [key, value] of formData.entries()) {\n if (value instanceof File) {\n files.push({\n name: key,\n filename: value.name,\n type: value.type,\n size: value.size,\n arrayBuffer: () => value.arrayBuffer(),\n });\n } else {\n // 同名字段收集为数组\n if (key in fields) {\n const existing = fields[key];\n if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n 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 通过 injectParams 单独注入。\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 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 (text.trim() === '') 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 (text.trim() === '') {\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 const url = new URL(request.url);\n return queryToObject(url.searchParams);\n}\n","import type { ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\n\n/**\n * 将 Web Response 写入 Node.js ServerResponse\n *\n * 使用 stream pipe 处理背压,避免高并发下内存膨胀。\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\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 nodeStream.on('error', reject);\n res.on('error', reject);\n res.on('finish', resolve);\n nodeStream.pipe(res);\n });\n return;\n }\n\n res.end();\n}\n","import type { IncomingMessage } from 'node:http';\n\n/**\n * 从 IncomingMessage 提取客户端 IP\n *\n * 优先级:\n * 1. `x-forwarded-for` 第一个 IP(反向代理场景,如 nginx/CDN)\n * 2. `req.socket.remoteAddress`(直连场景)\n *\n * `x-forwarded-for` 格式:`client, proxy1, proxy2`,取第一个即客户端真实 IP。\n * 注意:仅在受信任的反向代理后才有效;若客户端直连且未经过代理,该 header 可被伪造。\n *\n * @returns 客户端 IP,无法获取时返回空字符串\n */\nexport function getClientIp(req: IncomingMessage): string {\n // 1. x-forwarded-for 优先(反向代理场景)\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 // 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","/**\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 { WebSocketServer, WebSocket } 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 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 */\nasync function loadWsHandler(filePath: string, ctx: WsContext): Promise<WsEventHandlers | void> {\n const module = await importWithCacheBust(filePath);\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: Buffer) => {\n // ws 库 message 事件传 Buffer[],合并为单个 Buffer 后转 string\n const buf = Buffer.isBuffer(data) ? data : Buffer.from(data as unknown as Uint8Array);\n handlers.onMessage!(ws, 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/**\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 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\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 { server, routesRef, rootDir, config, globalMiddlewares } = options;\n\n const wss = new WebSocketServer({ noServer: true });\n\n server.on('upgrade', async (req: IncomingMessage, socket: Socket, head: Buffer) => {\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));\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);\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 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 { FaapiError } from './FaapiError';\nimport { ValidationError, MethodNotAllowedError } from './httpErrors';\n\n/**\n * 将错误转换为统一的 JSON 响应结构\n *\n * 格式:\n * {\n * \"error\": {\n * \"code\": \"VALIDATION_ERROR\",\n * \"message\": \"Invalid query parameters\",\n * \"issues\": [...] // 仅 ValidationError 包含\n * }\n * }\n */\nexport function formatErrorResponse(error: unknown): Response {\n if (error instanceof ValidationError) {\n const body: Record<string, unknown> = {\n code: error.code,\n message: error.message,\n issues: error.issues,\n };\n return new Response(JSON.stringify({ error: body }), {\n status: error.statusCode,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n if (error instanceof MethodNotAllowedError) {\n const body = {\n code: error.code,\n message: error.message,\n };\n return new Response(JSON.stringify({ error: body }), {\n status: error.statusCode,\n headers: {\n 'Content-Type': 'application/json',\n Allow: error.allowedMethods.join(', '),\n },\n });\n }\n\n if (error instanceof FaapiError) {\n const body = {\n code: error.code,\n message: error.message,\n };\n return new Response(JSON.stringify({ error: body }), {\n status: error.statusCode,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // 未知错误:500 INTERNAL_ERROR\n const body = {\n code: 'INTERNAL_ERROR',\n message: error instanceof Error ? error.message : 'An unknown error occurred',\n };\n return new Response(JSON.stringify({ error: body }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n });\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 抛错时)\n * 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n *\n * 业务方如需自定义错误响应,在全局中间件中 try/catch next() 即可。\n */\nexport function buildErrorResponse(err: unknown): Response {\n try {\n return formatErrorResponse(err);\n } catch {\n // 极端情况:内置兜底也失败,返回最简 500\n return new Response(\n JSON.stringify({ error: { code: 'INTERNAL_ERROR', message: 'Internal Server Error' } }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n}\n","/**\n * WebSocket Handler 类型定义与 socket 封装\n *\n * 用户在 handler.ts 中导出 WS 函数,返回事件回调对象。\n * 框架在协议升级成功后调用对应回调,传递封装后的 WsSocket。\n *\n * @see wsHandler.md 功能说明\n */\n\n/**\n * faapi 封装的 WebSocket socket 抽象\n *\n * 不直接暴露 ws 库的原生 socket,提供更安全、易用的 API。\n * send 对象时自动 JSON.stringify。\n */\nexport interface WsSocket {\n /** 发送数据(string/Buffer 直发,对象自动 JSON.stringify) */\n send(data: string | Buffer | object): void;\n /** 关闭连接 */\n close(code?: number, reason?: string | Buffer): void;\n /** 连接状态:0=connecting, 1=open, 2=closing, 3=closed */\n readonly readyState: number;\n}\n\n/**\n * WebSocket 事件回调集合\n *\n * 用户在 WS handler 中返回此对象,框架在对应事件触发时调用。\n * 所有回调可选,未提供则忽略事件。\n */\nexport interface WsEventHandlers {\n /** 连接建立时触发 */\n onOpen?: (ws: WsSocket) => void;\n /** 收到客户端消息时触发 */\n onMessage?: (ws: WsSocket, message: string | Buffer) => void;\n /** 连接关闭时触发 */\n onClose?: (ws: WsSocket, code: number, reason: string) => void;\n /** 发生错误时触发 */\n onError?: (ws: WsSocket, error: Error) => void;\n}\n\n/**\n * WebSocket 上下文(握手阶段构造)\n *\n * 与 HTTP FaapiContext 类似但精简,提供路由参数、查询参数、请求头、配置。\n * 可通过 declare module '@faapi/faapi' 增强自定义字段。\n */\nexport interface WsContext {\n /** 动态路由参数(如 [id] → params.id) */\n params: Record<string, string>;\n /** URL 查询参数 */\n query: URLSearchParams;\n /** 请求头 */\n headers: Headers;\n /** 业务配置(来自 faapi.config.ts) */\n config: Record<string, unknown>;\n /** 中间件塞入的用户信息(鉴权等) */\n user?: unknown;\n /** 允许通过 declare module 扩展 */\n [key: string]: unknown;\n}\n\n/**\n * WS handler 签名:接收 WsContext,返回事件回调对象(或无返回)\n */\nexport type WsHandler = (ctx: WsContext) => WsEventHandlers | void;\n\n/**\n * 将 ws 库的原生 WebSocket 封装为 faapi 的 WsSocket\n *\n * - send 对象时自动 JSON.stringify\n * - close/re.readyState 透传原生 socket\n */\nexport function wrapWsSocket(rawSocket: {\n send: (data: string | Buffer, cb?: (err?: Error) => void) => void;\n close: (code?: number, reason?: string | Buffer) => void;\n readonly readyState: number;\n}): WsSocket {\n return {\n send(data: string | Buffer | object): void {\n const payload =\n typeof data === 'string' || Buffer.isBuffer(data) ? data : JSON.stringify(data);\n rawSocket.send(payload);\n },\n close(code?: number, reason?: string | Buffer): void {\n rawSocket.close(code, reason);\n },\n get readyState(): number {\n return rawSocket.readyState;\n },\n };\n}\n","import { WebSocket, type RawData } from 'ws';\n\n/**\n * WebSocket 测试客户端\n *\n * 公开导出 connectWs + MessageQueue + waitForWsOpen,业务方测试 WS 路由时\n * 免去手写\"消息竞态防护 + 三事件监听 + 端口拼接\"样板代码。\n *\n * 详见 src/wsTestClient.md。\n */\n\n/**\n * connectWs 入参\n */\nexport interface WsTestClientOptions {\n /** 等待 open 的超时(ms),默认 2000 */\n timeout?: number;\n /** 握手请求头(如 authorization) */\n headers?: Record<string, string>;\n /** WS 子协议 */\n protocols?: string | string[];\n}\n\n/**\n * connectWs 返回值\n *\n * 业务方通过 ws.send() 发消息,queue.next() 取消息,close() 关闭。\n */\nexport interface WsTestClient {\n /** ws 库原生实例,业务方可直接 ws.send() / ws.close() */\n ws: WebSocket;\n /** 已开始缓冲的消息队列,调 next(timeout?) 取下一条 */\n queue: MessageQueue;\n /**\n * 关闭 ws 并等待 'close' 事件\n *\n * 内部:\n * 1. 若 ws 仍 OPEN/CLOSING,调 ws.close()\n * 2. 等待 'close' 事件(超时 1000ms 强制 resolve)\n *\n * 幂等:重复调用不抛错。\n */\n close(): Promise<void>;\n}\n\n/**\n * 消息队列:避免 once('message') 与服务端 onOpen 推送的竞态\n *\n * 服务端在 handleUpgrade 回调里同步触发 onOpen 并 send('connected'),\n * 客户端 'open' 事件触发后到注册 once('message') 之间存在窗口,\n * 若 'connected' 在此窗口内到达,once 会错过。\n *\n * 队列在创建 ws 时立即监听 'message',按 FIFO 顺序消费。\n */\nexport class MessageQueue {\n private queue: string[] = [];\n private waiters: Array<(msg: string) => void> = [];\n private listener: (data: RawData) => void;\n\n constructor(ws: WebSocket) {\n this.listener = (data: RawData) => {\n const msg = normalizeRawData(data);\n const waiter = this.waiters.shift();\n if (waiter) {\n waiter(msg);\n } else {\n this.queue.push(msg);\n }\n };\n ws.on('message', this.listener);\n }\n\n /**\n * 取下一条消息\n *\n * 队列有则立即 resolve,无则注册 waiter 等待下一条 'message' 事件。\n * 超时未到 → reject('WebSocket message timeout'),waiter 被清理。\n *\n * @param timeout 超时毫秒,默认 2000\n */\n next(timeout = 2000): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n // 先定义 wrapped,再定义引用 wrapped 的 timer,避免 TDZ 风格可读性问题\n const wrapped = (msg: string) => {\n clearTimeout(timer);\n resolve(msg);\n };\n const timer = setTimeout(() => {\n const idx = this.waiters.indexOf(wrapped);\n if (idx >= 0) this.waiters.splice(idx, 1);\n reject(new Error('WebSocket message timeout'));\n }, timeout);\n\n const msg = this.queue.shift();\n if (msg !== undefined) {\n wrapped(msg);\n } else {\n this.waiters.push(wrapped);\n }\n });\n }\n}\n\n/**\n * 将 ws 库的 RawData 统一转为 utf8 字符串\n *\n * RawData 可能形态:\n * - Buffer(最常见)\n * - Buffer[](消息被分片,需先 concat)\n * - ArrayBuffer / ArrayBufferView(部分运行时)\n */\nfunction normalizeRawData(data: RawData): string {\n if (Buffer.isBuffer(data)) {\n return data.toString('utf8');\n }\n if (Array.isArray(data)) {\n return Buffer.concat(data).toString('utf8');\n }\n // ArrayBuffer / ArrayBufferView\n return Buffer.from(data as unknown as Uint8Array).toString('utf8');\n}\n\n/**\n * Promise 化等待 ws 'open' 事件\n *\n * 同时监听 'open' / 'error' / 'close' 三事件,任一触发都清理 timer,\n * 避免 timer 泄漏。\n *\n * @param ws WebSocket 实例\n * @param timeout 超时毫秒,默认 2000\n * @returns 'open' → resolve;'error' → reject(err);'close' → reject;超时 → reject\n */\nexport function waitForWsOpen(ws: WebSocket, timeout = 2000): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error('WebSocket open timeout'));\n }, timeout);\n\n const cleanup = () => {\n clearTimeout(timer);\n ws.removeListener('open', onOpen);\n ws.removeListener('error', onError);\n ws.removeListener('close', onClose);\n };\n\n const onOpen = () => {\n cleanup();\n resolve();\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const onClose = () => {\n cleanup();\n reject(new Error('WebSocket closed before open'));\n };\n\n ws.once('open', onOpen);\n ws.once('error', onError);\n ws.once('close', onClose);\n });\n}\n\n/**\n * 一键连接 WS server\n *\n * 内部流程:\n * 1. baseUrl 协议转换(http → ws,https → wss)\n * 2. new WebSocket(url, protocols, { headers })\n * 3. 立即创建 MessageQueue(开始缓冲消息,避免竞态)\n * 4. waitForWsOpen 等待连接建立(三事件监听 + 超时清理)\n * 5. 返回 WsTestClient\n *\n * 连接失败(中间件拦截 / 路径未匹配 / 超时)→ reject。\n *\n * @param baseUrl createTestServer().baseUrl(http://...)\n * @param pathname WS 路径,如 '/api/chat',可含 query\n * @param options timeout / headers / protocols\n * @returns WsTestClient 实例\n */\nexport async function connectWs(\n baseUrl: string,\n pathname: string,\n options: WsTestClientOptions = {},\n): Promise<WsTestClient> {\n const { timeout = 2000, headers, protocols } = options;\n\n // 协议转换:http → ws,https → wss(业务方传入 createTestServer().baseUrl)\n const wsBaseUrl = baseUrl.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:');\n const url = `${wsBaseUrl}${pathname}`;\n\n const ws = new WebSocket(url, protocols, headers ? { headers } : undefined);\n const queue = new MessageQueue(ws);\n\n // 等待 open;失败时主动 close ws,避免 socket 处于 CONNECTING 状态泄漏\n try {\n await waitForWsOpen(ws, timeout);\n } catch (err) {\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n throw err;\n }\n\n // 关闭状态标记(幂等保护)\n let closed = false;\n\n return {\n ws,\n queue,\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n\n // 若 ws 仍 OPEN/CLOSING,主动 close\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n\n // 等待 'close' 事件(超时 1000ms 强制 resolve,避免 server 端不回 close)\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, 1000);\n ws.once('close', () => {\n clearTimeout(timer);\n resolve();\n });\n });\n },\n };\n}\n","import type { Server } from 'node:http';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { PassThrough } 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 { loadPlugins } from './loadPlugins';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport type { FaapiConfig } from '../config/configTypes';\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/** 路由源码目录(写死为 src,路由 .ts 文件位于 src/api/ 下) */\nconst PATTERNS = ['src/api/**/*.ts'];\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 'bodyLimit',\n 'logger',\n 'http2',\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 /** 排序后的路由清单 */\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 * 构建一个模拟请求直接走完整请求链路,不绑定端口。\n * 需要在 listen() 之前调用(server 未启动时)。\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 /** 产物目录 */\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 // 自定义业务配置(排除内置 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 logger: config?.logger,\n bodyLimit: config?.bodyLimit,\n http2: config?.http2,\n });\n\n // 加载插件 + 应用 handler/upgrade 包装器\n const { handlerWrappers, upgradeWrappers } = await loadPlugins(config?.plugins, {\n rootDir,\n routes: sorted,\n getRoutes: () => sorted,\n server,\n config: pluginConfig,\n });\n applyPluginWrappers(server, handlerWrappers, upgradeWrappers);\n\n // 关闭状态标记(避免重复关闭)\n let closed = false;\n\n const app: AppBase = {\n server: null,\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 return new Promise<Server>((resolve) => {\n server.listen(actualPort, async () => {\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\n // 注册优雅关闭信号(仅当配置了 onClose)\n if (config?.lifecycle?.onClose) {\n const graceful = async (signal: string): Promise<void> => {\n console.log(`\\n- Received ${signal}, shutting down...`);\n await app.close();\n process.exit(0);\n };\n process.on('SIGTERM', () => void graceful('SIGTERM'));\n process.on('SIGINT', () => void graceful('SIGINT'));\n }\n\n // onReady 生命周期钩子\n if (config?.lifecycle?.onReady) {\n await config.lifecycle.onReady({ rootDir, routes: sorted, server });\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 const mockRes = {\n statusCode: 200,\n _headers: {} as Record<string, string>,\n _body: Buffer.alloc(0) as Buffer,\n setHeader(name: string, value: string) {\n this._headers[name.toLowerCase()] = value;\n },\n appendHeader(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 writeHead(status: number, headers?: Record<string, string>) {\n this.statusCode = status;\n if (headers) {\n Object.assign(this._headers, headers);\n }\n },\n end(data?: string | Buffer) {\n const buf: Buffer = Buffer.isBuffer(data) ? data : Buffer.from(data ?? '');\n this._body = buf;\n resolve({\n status: this.statusCode,\n headers: new Headers(this._headers as Record<string, string>),\n body: this.parseBody(),\n });\n },\n parseBody(): unknown {\n try {\n return JSON.parse(this._body.toString());\n } catch {\n return this._body.toString();\n }\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: PassThrough & {\n method?: string;\n url?: string;\n headers?: Record<string, string | undefined>;\n socket?: { remoteAddress?: string };\n } = new PassThrough({\n read() {\n this.push(null);\n },\n });\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 if (body !== undefined) {\n mockReq.push(JSON.stringify(body));\n }\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 // 停止接受新连接(HTTP/2 server 支持,HTTP/1.1 无此方法)\n const s = server as unknown as Record<string, unknown>;\n if (typeof s.closeIdleConnections === 'function') {\n (s.closeIdleConnections as () => void)();\n }\n if (typeof s.closeAllConnections === 'function') {\n (s.closeAllConnections as () => void)();\n }\n\n if (config?.lifecycle?.onClose) {\n await config.lifecycle.onClose({ rootDir, routes: sorted, server });\n }\n\n return new Promise<void>((resolve) => {\n server.close((err) => {\n if (err) console.error('Error closing server:', err);\n app.server = null;\n resolve();\n });\n });\n },\n };\n\n /** 更新路由引用(app + routesRef + 闭包变量) */\n const ctx: AppContext = {\n rootDir,\n dist,\n patterns: 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\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 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 type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport {\n loadMiddlewaresFile,\n getCachedMiddlewares,\n setCachedMiddlewares,\n} from '../middleware/loadMiddlewares';\nimport type { LoadedMiddlewareBundle } from '../middleware/loadMiddlewares';\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 * 把源码 filePath(src/api/hello/handler.ts)转为产物路径(dist/api/hello/handler.js)\n *\n * 产物结构打平 src/ 前缀:去掉 `src/`,加 dist 前缀,.ts → .js。\n *\n * @param filePath 源码相对路径\n * @param dist 产物目录(dist 或 .faapi)\n */\nfunction 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 * 序列化路由清单为可写入 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 const dir = path.dirname(outputPath);\n await fs.promises.mkdir(dir, { recursive: true });\n\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 fs.promises.writeFile(outputPath, content, 'utf-8');\n}\n\n/**\n * 从序列化清单水合路由(加载中间件,还原 RouteRecord)\n *\n * start 时调用,对每条路由按 middlewarePaths 加载中间件文件。\n */\nexport async function hydrateRoutes(\n manifest: SerializedRouteManifest,\n): Promise<{ routes: RouteManifest; wsRoutes: WsRouteManifest }> {\n const hydrateRoute = async (serialized: SerializedRouteRecord): Promise<RouteRecord> => {\n const bundle = await loadMiddlewarePaths(serialized.middlewarePaths);\n return {\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 middlewares: bundle?.middlewares,\n injectors: bundle?.injectors,\n };\n };\n\n const hydrateWsRoute = async (serialized: SerializedWsRouteRecord): Promise<WsRouteRecord> => {\n const bundle = await loadMiddlewarePaths(serialized.middlewarePaths);\n return {\n urlPath: serialized.urlPath,\n filePath: serialized.filePath,\n paramNames: serialized.paramNames,\n isDynamic: serialized.isDynamic,\n isCatchAll: serialized.isCatchAll,\n middlewares: bundle?.middlewares,\n injectors: bundle?.injectors,\n };\n };\n\n const routes = await Promise.all(manifest.routes.map(hydrateRoute));\n const wsRoutes = await Promise.all(manifest.wsRoutes.map(hydrateWsRoute));\n return { routes, wsRoutes };\n}\n\n/**\n * 按路径列表加载并合并中间件(根在前,路由目录在后)\n *\n * 与 scanRoutes.findMergedMiddlewares 的合并逻辑一致:\n * - 中间件:父级在前,子级追加在后(洋葱模型内层)\n * - 注入器:子级覆盖父级同名\n */\nasync function loadMiddlewarePaths(\n middlewarePaths: string[],\n): Promise<{ middlewares: FaapiMiddleware[]; injectors: InjectorMap } | 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 bundle = await loadMiddlewaresFile(absMwPath);\n setCachedMiddlewares(absMwPath, bundle);\n }\n mergedMiddlewares.push(...bundle.middlewares);\n for (const [name, injector] of Object.entries(bundle.injectors)) {\n mergedInjectors[name] = injector;\n }\n }\n\n return { middlewares: mergedMiddlewares, injectors: mergedInjectors };\n}\n","import type { FaapiPlugin, PluginDeclaration } from '../config/pluginTypes';\nimport type { RequestHandler, UpgradeHandler } from '../config/pluginTypes';\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}\n\n/**\n * 加载并执行插件列表\n *\n * 遍历 config.plugins,动态 import 插件包,调用 setup(ctx)。\n * 插件在 server 创建后、listen 之前按声明顺序执行。\n *\n * 插件可通过 ctx.wrapHandler / ctx.wrapUpgradeHandler 注册包装函数,\n * 本函数收集后返回,由调用方在 listen 之前应用。\n *\n * @param declarations 插件声明列表\n * @param ctx 插件上下文(不含 wrap 能力,由本函数注入)\n * @returns 包装器收集结果\n */\nexport async function loadPlugins(\n declarations: PluginDeclaration[] | undefined,\n ctx: LoadPluginsContext,\n): Promise<PluginLoadResult> {\n const handlerWrappers: Array<(original: RequestHandler) => RequestHandler> = [];\n const upgradeWrappers: Array<(original: UpgradeHandler | undefined) => UpgradeHandler> = [];\n\n if (!declarations || declarations.length === 0) {\n return { handlerWrappers, upgradeWrappers };\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 const { specifier, options, enable } = resolveDeclaration(decl);\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 import(specifier);\n const plugin: FaapiPlugin = mod.default ?? mod;\n\n if (typeof plugin.setup !== 'function') {\n console.warn(`! Plugin ${specifier} has no setup function, skipping`);\n continue;\n }\n\n await plugin.setup({ ...fullCtx, options });\n console.log(`- Plugin loaded: ${plugin.name ?? specifier}`);\n } catch (err) {\n console.warn(\n `! Failed to load plugin ${specifier}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n return { handlerWrappers, upgradeWrappers };\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 // 不应该到这里\n throw new Error(`Invalid plugin declaration: ${JSON.stringify(decl)}`);\n}\n","import type { AppBase, CreateAppOptions } from './createAppCore';\nimport { createAppBase } from './createAppCore';\nimport { scanRoutes } from '../router/scanRoutes';\nimport { sortRoutes } from '../router/sortRoutes';\nimport { generateSchemaFiles } from './generateSchemaFiles';\nimport { invalidateMiddlewareCache } from '../middleware/loadMiddlewares';\nimport { invalidateProgramCache } from '../ast/createProgram';\nimport { invalidateSchemaCache } from '../validator/validateInput';\nimport { setLoadTimestamp } from '../utils/importWithCacheBust';\n\n/** dev 应用接口(AppBase + reloadRoutes 热替换) */\nexport interface DevApp extends AppBase {\n /** 重新水合路由清单 + 清 schema 缓存 + 更新 server 路由引用(dev 热替换用) */\n reloadRoutes(): 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 // 重新扫描路由 + 重新生成 schema\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 await generateSchemaFiles(sorted, ctx.rootDir, ctx.dist);\n // 更新 app 和 server 路由引用\n ctx.updateRoutes(sorted, reScanned.wsRoutes);\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","ts","ts","path","ts","path","fs","path","path","url","headers","body","path","fs","path","fs","path","path","fs","path","fs","unionInner","path","fs","path","Readable","path","path","params","text","path","body","path","Readable","path","DEFAULT_BODY_LIMIT","cors","helmet","logger","path","fs","WebSocket","msg","fs","path","fs","path","path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/ast/createProgram.ts","../src/ast/extractHandlerTypes.ts","../src/ast/resolveTypeNode.ts","../src/runtime/inputType.ts","../src/validator/schemaName.ts","../src/injection/analyzeInjection.ts","../src/injection/resolveInjection.ts","../src/cli/collectRouteSchemaSources.ts","../src/middleware/cors.ts","../src/middleware/logger.ts","../src/middleware/helmet.ts","../src/config/loadConfig.ts","../src/utils/importWithCacheBust.ts","../src/cli/loadEnv.ts","../src/errors/FaapiError.ts","../src/errors/httpErrors.ts","../src/runtime/sse.ts","../src/runtime/createContext.ts","../src/utils/isPlainObject.ts","../src/response/toResponse.ts","../src/utils/queryToObject.ts","../src/injection/injectParams.ts","../src/runtime/invokeHandler.ts","../src/testServer.ts","../src/router/scanRoutes.ts","../src/router/constants.ts","../src/utils/normalizePath.ts","../src/router/parseRouteFile.ts","../src/middleware/loadMiddlewares.ts","../src/router/sortRoutes.ts","../src/cli/generateSchemaFiles.ts","../src/ast/generateZodSchema.ts","../src/validator/validateInput.ts","../src/server/createServer.ts","../src/router/matchRoute.ts","../src/loader/resolveExports.ts","../src/loader/validateRouteModule.ts","../src/loader/loadRouteModule.ts","../src/utils/parseJsonBody.ts","../src/utils/parseMultipart.ts","../src/runtime/resolveInput.ts","../src/response/sendNodeResponse.ts","../src/utils/getClientIp.ts","../src/server/handleWsUpgrade.ts","../src/errors/formatErrorResponse.ts","../src/server/serverUtils.ts","../src/runtime/wsHandler.ts","../src/wsTestClient.ts","../src/cli/createAppCore.ts","../src/router/detectRouteConflicts.ts","../src/server/startServer.ts","../src/cli/generateRoutes.ts","../src/cli/loadPlugins.ts","../src/cli/createDevApp.ts","../src/cli/createProdApp.ts"],"sourcesContent":["import ts from 'typescript';\n\n/**\n * Program 缓存,key 为文件路径\n *\n * watch 模式下通过 invalidateProgramCache() 全量清理。\n * 同一文件多次创建 Program 时复用缓存,避免重复解析。\n */\nconst programCache = new Map<string, ts.Program>();\n\n/**\n * 清理所有 Program 缓存(watch 模式下文件变化时调用)\n *\n * 全量清理而非增量清理,理由:\n * - 简单可靠,无状态一致性问题\n * - 跨文件类型引用需要所有文件的 Program 同步更新\n * - dev 模式文件量有限,全量重建在百毫秒级\n */\nexport function invalidateProgramCache(): void {\n programCache.clear();\n}\n\n/**\n * 为指定文件创建 TypeScript 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 = ts.createProgram([filePath], {\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 programCache.set(filePath, program);\n return program;\n}\n","import ts from 'typescript';\nimport {\n resolveTypeNode,\n resolveInterfaceDeclaration,\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 *\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 // 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 return result;\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 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}\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 );\n throw enriched;\n }\n throw err;\n }\n}\n","import ts from 'typescript';\n\n/**\n * Schema 提取错误\n *\n * 遇到无法解析或不支持运行时校验的类型时抛出,\n * 避免静默降级为 any 导致用户不知情。\n */\nexport class SchemaExtractionError extends Error {\n constructor(\n public readonly typeText: string,\n public readonly reason: string,\n options?: ErrorOptions,\n ) {\n super(`无法解析类型 \"${typeText}\": ${reason}`, options);\n this.name = 'SchemaExtractionError';\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[] }\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 */\nexport function resolveTypeNode(\n typeNode: ts.TypeNode,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\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),\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);\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),\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),\n optional: true,\n rest: false,\n };\n }\n // 普通元素\n return {\n type: resolveTypeNode(e, checker, visited),\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));\n return { kind: 'union', members };\n }\n\n // 交叉类型:A & B → 合并对象属性\n if (ts.isIntersectionTypeNode(typeNode)) {\n const properties: PropertyType[] = [];\n for (const t of typeNode.types) {\n const resolved = resolveTypeNode(t, checker, visited);\n if (resolved.kind === 'object') {\n properties.push(...resolved.properties);\n }\n }\n return { kind: 'object', properties };\n }\n\n // 内联对象类型:{ name: string; age?: number }\n if (ts.isTypeLiteralNode(typeNode)) {\n return resolveTypeLiteral(typeNode, checker, visited);\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);\n }\n\n // 引用类型:Date / 自定义 interface / Array<T> / Record<K,V> / Partial<T> 等\n if (ts.isTypeReferenceNode(typeNode)) {\n return resolveTypeReference(typeNode, checker, visited);\n }\n\n // 其他无法识别的语法节点\n throw new SchemaExtractionError(typeNode.getText(), '不支持的类型语法');\n}\n\n/**\n * 解析内联对象类型字面量\n */\nfunction resolveTypeLiteral(\n typeNode: ts.TypeLiteralNode,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n): RuntimeType {\n const properties: PropertyType[] = [];\n\n for (const member of typeNode.members) {\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)\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 → 转为 record\n if (ts.isIndexSignatureDeclaration(member)) {\n const keyType = member.parameters[0]?.type\n ? resolveTypeNode(member.parameters[0].type, checker, visited)\n : { kind: 'any' as const };\n const valueType = member.type\n ? resolveTypeNode(member.type, checker, visited)\n : { kind: 'any' as const };\n return { kind: 'record', key: keyType, value: valueType };\n }\n }\n\n return { 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 * 解析类型引用(Date / 自定义 interface / Array<T> / Record<K,V> 等)\n */\nfunction resolveTypeReference(\n typeNode: ts.TypeReferenceNode,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n): RuntimeType {\n const typeName = typeNode.typeName.getText();\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 return {\n kind: 'array',\n element: resolveTypeNode(typeNode.typeArguments[0], checker, visited),\n };\n }\n\n // Record<K, V>\n if (typeName === 'Record' && typeNode.typeArguments?.length === 2) {\n return {\n kind: 'record',\n key: resolveTypeNode(typeNode.typeArguments[0], checker, visited),\n value: resolveTypeNode(typeNode.typeArguments[1], checker, visited),\n };\n }\n\n // Partial<T> / Required<T> / Readonly<T> — best effort,解析内部类型\n if (\n (typeName === 'Partial' || typeName === 'Required' || typeName === 'Readonly') &&\n typeNode.typeArguments?.length === 1\n ) {\n const inner = resolveTypeNode(typeNode.typeArguments[0], checker, visited);\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 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);\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));\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 return {\n kind: 'map',\n key: resolveTypeNode(typeNode.typeArguments[0], checker, visited),\n value: resolveTypeNode(typeNode.typeArguments[1], checker, visited),\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 return {\n kind: 'set',\n element: resolveTypeNode(typeNode.typeArguments[0], checker, visited),\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 typeNode.typeName.kind === ts.SyntaxKind.Identifier\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(declaration, checker, visited);\n }\n // type 别名声明\n if (ts.isTypeAliasDeclaration(declaration)) {\n return resolveTypeNode(declaration.type, checker, visited);\n }\n // enum 声明 → 字面量联合\n if (ts.isEnumDeclaration(declaration)) {\n return resolveEnumDeclaration(declaration);\n }\n }\n }\n }\n\n throw new SchemaExtractionError(typeNode.getText(), `无法解析的引用类型 \"${typeName}\"`);\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 */\nfunction 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 */\nexport function resolveInterfaceDeclaration(\n node: ts.InterfaceDeclaration,\n checker?: ts.TypeChecker,\n visited: Set<string> = new Set(),\n): RuntimeType {\n const properties: PropertyType[] = [];\n const propMap = new Map<string, PropertyType>();\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);\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 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)\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 // 索引签名 → record\n if (ts.isIndexSignatureDeclaration(member)) {\n const keyType = member.parameters[0]?.type\n ? resolveTypeNode(member.parameters[0].type, checker, visited)\n : { kind: 'any' as const };\n const valueType = member.type\n ? resolveTypeNode(member.type, checker, visited)\n : { kind: 'any' as const };\n return { kind: 'record', key: keyType, value: valueType };\n }\n }\n\n for (const prop of propMap.values()) {\n properties.push(prop);\n }\n\n return { kind: 'object', properties };\n}\n\n/**\n * 数值约束标签集合(仅 number 字段)\n */\nconst NUMBER_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'max',\n 'min',\n 'int',\n 'positive',\n 'negative',\n 'nonnegative',\n 'nonpositive',\n]);\n\n/**\n * 长度约束标签集合(string 或 array 字段)\n */\nconst LENGTH_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'maxLength',\n 'minLength',\n 'length',\n]);\n\n/**\n * 字符串格式约束标签集合(仅 string 字段)\n */\nconst STRING_FORMAT_CONSTRAINT_KINDS = new Set<TypeConstraint['kind']>([\n 'regex',\n 'email',\n 'url',\n 'uuid',\n]);\n\n/**\n * 从字段的 JSDoc 注释中提取约束标签\n *\n * 解析 `/** ... *\\/` 块注释中的 `@max`/`@min`/`@maxLength` 等标签。\n * 行内注释 `// @max 100` 不识别。\n *\n * @param node 字段节点(PropertySignature)\n * @param fieldName 字段名,用于错误信息\n * @returns 约束数组(无约束时返回空数组)\n */\nfunction extractConstraintsFromJsDoc(\n node: ts.PropertySignature,\n fieldName: string,\n): TypeConstraint[] {\n // ts.getJSDocCommentsAndTags 返回 JSDoc/JsDocTag 数组\n const jsDocs = ts\n .getJSDocCommentsAndTags(node)\n .filter((entry): entry is ts.JSDoc => ts.isJSDoc(entry));\n\n if (jsDocs.length === 0) return [];\n\n const constraints: TypeConstraint[] = [];\n\n for (const jsDoc of jsDocs) {\n if (!jsDoc.tags) continue;\n for (const tag of jsDoc.tags) {\n const constraint = parseJsDocTag(tag, fieldName);\n if (constraint) constraints.push(constraint);\n }\n }\n\n return constraints;\n}\n\n/**\n * 提取 JSDoc 标签后的文本注释\n *\n * `tag.comment` 类型为 `string | NodeArray<JSDocComment> | undefined`,\n * 此处仅取纯文本形式(约束标签后跟的均为字面量值,不会包含 JSDocLink 等节点)。\n *\n * @returns 标签后的文本;无文本或非纯字符串时返回 undefined\n */\nfunction getTagCommentText(tag: ts.JSDocTag): string | undefined {\n const comment = tag.comment;\n if (typeof comment === 'string') return comment;\n return undefined;\n}\n\n/**\n * 解析单个 JSDoc 标签为约束\n *\n * @param tag JSDocTag 节点\n * @param fieldName 字段名,用于错误信息\n * @returns 约束对象;非约束标签返回 null\n */\nfunction parseJsDocTag(tag: ts.JSDocTag, fieldName: string): TypeConstraint | null {\n const tagName = tag.tagName.text;\n\n switch (tagName) {\n // 数值约束(带值)\n case 'max':\n case 'min': {\n const value = parseNumberValue(tag, fieldName, tagName);\n return { kind: tagName, value };\n }\n\n // 长度约束(带值)\n case 'maxLength':\n case 'minLength':\n case 'length': {\n const value = parseNumberValue(tag, fieldName, tagName);\n return { kind: tagName, value };\n }\n\n // 正则约束(带 /pattern/flags 值)\n case 'regex':\n case 'pattern': {\n const text = getTagCommentText(tag);\n if (!text) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签需要 /pattern/flags 形式的值`);\n }\n const regex = parseRegexLiteral(text.trim(), fieldName);\n return { kind: 'regex', pattern: regex.pattern, flags: regex.flags };\n }\n\n // 数值约束(无值)\n case 'int':\n case 'positive':\n case 'negative':\n case 'nonnegative':\n case 'nonpositive':\n return { kind: tagName };\n\n // 字符串格式约束(无值)\n case 'email':\n case 'url':\n case 'uuid':\n return { kind: tagName };\n\n default:\n return null;\n }\n}\n\n/**\n * 解析标签后的数字值\n *\n * @param tag JSDocTag 节点\n * @param fieldName 字段名,用于错误信息\n * @param tagName 标签名,用于错误信息\n */\nfunction parseNumberValue(tag: ts.JSDocTag, fieldName: string, tagName: string): number {\n const text = getTagCommentText(tag);\n if (!text) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签需要一个数字值`);\n }\n const trimmed = text.trim();\n const num = Number(trimmed);\n if (!Number.isFinite(num)) {\n throw new SchemaExtractionError(fieldName, `@${tagName} 标签的值 \"${trimmed}\" 不是有效数字`);\n }\n return num;\n}\n\n/**\n * 解析 /pattern/flags 形式的正则字面量\n *\n * @param text 标签后的文本,如 \"/^[a-z]+$/i\"\n * @param fieldName 字段名,用于错误信息\n */\nfunction parseRegexLiteral(text: string, fieldName: string): { pattern: string; flags?: string } {\n // 必须以 / 开头,匹配结尾的 / 和可选 flags\n const match = /^\\/(.+)\\/([gimsuy]*)$/.exec(text);\n if (!match) {\n throw new SchemaExtractionError(fieldName, `正则值 \"${text}\" 不是 /pattern/flags 形式`);\n }\n const [, pattern, flags] = match;\n if (!pattern) {\n throw new SchemaExtractionError(fieldName, `正则值 \"${text}\" 的 pattern 部分为空`);\n }\n return flags ? { pattern, flags } : { pattern };\n}\n\n/**\n * 校验约束与字段类型是否匹配\n *\n * 不匹配时抛 SchemaExtractionError(复用现有错误类型)。\n *\n * @param constraints 约束数组\n * @param type 字段类型\n * @param fieldName 字段名,用于错误信息\n */\nfunction validateConstraints(\n constraints: TypeConstraint[],\n type: RuntimeType,\n fieldName: string,\n): void {\n if (constraints.length === 0) return;\n\n for (const constraint of constraints) {\n const kind = constraint.kind;\n\n if (NUMBER_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'number') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 number 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n\n if (LENGTH_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'string' && type.kind !== 'array') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 string 或 array 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n\n if (STRING_FORMAT_CONSTRAINT_KINDS.has(kind)) {\n if (type.kind !== 'string') {\n throw new SchemaExtractionError(\n fieldName,\n `@${kind} 约束仅适用于 string 字段,实际为 ${type.kind}`,\n );\n }\n continue;\n }\n }\n}\n","/**\n * 根据 HTTP 方法判断主输入类型\n *\n * - GET / DELETE / HEAD:query(URL 查询参数)\n * - POST / PUT / PATCH:body(请求体)\n *\n * 注意:所有方法都可能同时有 query 和 body,\n * 这里返回的是\"主输入\"(用于校验和注入)。\n * DELETE 请求也支持 body(见 resolveInput.ts)。\n */\nexport function getInputTypeForMethod(method: string): 'query' | 'body' {\n const upper = method.toUpperCase();\n if (upper === 'GET' || upper === 'DELETE' || upper === 'HEAD') {\n return 'query';\n }\n return 'body';\n}\n\n/**\n * 判断方法是否有请求体\n *\n * POST / PUT / PATCH / DELETE 可能有 body\n * GET / HEAD 不应该有 body\n */\nexport function hasBody(method: string): boolean {\n const upper = method.toUpperCase();\n return upper === 'POST' || upper === 'PUT' || upper === 'PATCH' || upper === 'DELETE';\n}\n","/**\n * 根据 HTTP 方法和输入类型生成 schema 类型名\n * GET + query -> GETQuery\n * POST + body -> POSTBody\n * GET + params -> GETParams\n */\nexport function getSchemaName(method: string, inputType: 'query' | 'body' | 'params'): string {\n return `${method.toUpperCase()}${inputType.charAt(0).toUpperCase() + inputType.slice(1)}`;\n}\n","import ts from 'typescript';\nimport type { InjectionType } from './resolveInjection';\nimport { PARAM_TYPE_MAP } from './resolveInjection';\n\n/**\n * 参数元数据\n */\nexport interface ParamMeta {\n name: string; // 参数名\n type: InjectionType; // 注入类型\n typeName?: string; // 引用类型名(如 'Query')\n schema?: PropertyInfo[]; // 类型结构(内联类型时)\n}\n\n/**\n * 属性信息\n */\nexport interface PropertyInfo {\n name: string;\n type: string;\n optional: boolean;\n}\n\n/**\n * 注入元数据\n */\nexport interface InjectionMeta {\n params: ParamMeta[];\n}\n\n/**\n * AST 分析 handler 函数,提取参数注入元数据\n */\nexport function analyzeInjection(code: string, functionName: string): InjectionMeta {\n const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);\n\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 ts from 'typescript';\n\n/**\n * 参数注入类型\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 | '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 */\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};\n\n/**\n * 分析函数参数,决定需要注入什么内容\n *\n * 使用 TypeScript AST 解析 fn.toString() 的结果,\n * 正确处理解构参数、默认值、rest 参数等情况。\n *\n * 注意:运行时类型信息已被擦除,hasType 始终为 false。\n */\nexport function resolveInjection(fn: (...args: unknown[]) => unknown): InjectionItem[] {\n const fnStr = fn.toString();\n const params = extractParamsWithAst(fnStr);\n\n return 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\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 { createProgram } from '../ast/createProgram';\nimport { extractTypeInfo, extractAllTypes, type HandlerTypeInfo } from '../ast/extractHandlerTypes';\nimport { getInputTypeForMethod } from '../runtime/inputType';\nimport { getSchemaName } from '../validator/schemaName';\nimport { analyzeInjection } 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. 对每个文件 createProgram + extractAllTypes 收集所有类型\n * 3. 用 analyzeInjection + extractTypeInfo 提取每个路由的 schema 类型\n * 4. 同时返回按文件分组的 allTypesMap 和合并后的全局 allTypes\n *\n * 调用方基于返回的 sources 和 allTypes 各自做最终转换:\n * - dev:生成 JS 模块文件 → import 加载(用 allTypesByFile)\n * - prd:生成 JS 模块代码 → SchemaModuleEntry[](用 allTypesByFile)\n */\nexport function collectRouteSchemaSources(\n routes: RouteManifest,\n rootDir?: string,\n): {\n sources: RouteSchemaSource[];\n /** 按文件分组的类型映射(prd writeSchemaModule 用) */\n allTypesByFile: Map<string, Map<string, HandlerTypeInfo>>;\n /** 合并后的全局类型映射(兼容旧调用方保留,新路径使用 allTypesByFile) */\n mergedAllTypes: Map<string, HandlerTypeInfo>;\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 // 先收集所有文件的类型\n const programByFile = new Map<string, ReturnType<typeof createProgram>>();\n const allTypesByFile = new Map<string, Map<string, HandlerTypeInfo>>();\n const mergedAllTypes = new Map<string, HandlerTypeInfo>();\n for (const filePath of methodsByFile.keys()) {\n const program = createProgram(filePath);\n programByFile.set(filePath, program);\n const allTypes = extractAllTypes(program, filePath);\n allTypesByFile.set(filePath, allTypes);\n for (const [name, info] of allTypes) {\n mergedAllTypes.set(name, info);\n }\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 const code = sourceFile?.text ?? '';\n for (const method of entry.methods) {\n const inputType = getInputTypeForMethod(method);\n const schemaName = getSchemaName(method, inputType);\n const meta = analyzeInjection(code, 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 const typeInfo = param?.typeName ? extractTypeInfo(program, filePath, 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, allTypesByFile, mergedAllTypes };\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 if (!allowOrigin) {\n await next();\n return;\n }\n\n // 设置 CORS 响应头\n ctx.setHeader('Access-Control-Allow-Origin', allowOrigin);\n\n // 当 origin 为动态值(true 或数组)时,必须设置 Vary: Origin,\n // 防止 CDN/浏览器错误缓存针对某个 Origin 的响应给其他 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 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 { 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 * @returns 模块导出对象\n */\nexport async function importWithCacheBust(filePath: string): Promise<Record<string, unknown>> {\n // vitest 环境优先走 Vite pipeline(识别 tsconfig paths + vi.mock)\n const importActual = getVitestImportActual();\n if (importActual) {\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 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 const [, key, rawValue] = match;\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 // 单引号:字面量\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 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 * 校验问题类型\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","/**\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 { FaapiContext, ResponseMeta, CookieOptions } from './contextTypes';\nimport { createSseWriter, type SseWriter } from './sse';\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): FaapiContext {\n const url = new URL(request.url);\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 const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n return new Response(JSON.stringify(data), {\n status: status ?? 200,\n headers,\n });\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 } as FaapiContext & { meta: ResponseMeta; __sseResponse?: Response; __sseWriter?: SseWriter };\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","export function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') {\n return false;\n }\n\n // 数组不是普通对象\n if (Array.isArray(value)) {\n return false;\n }\n\n // 检查原型链:普通对象的 prototype 是 Object.prototype 或 null(Object.create(null))\n const proto = Object.getPrototypeOf(value);\n return proto === null || proto === Object.prototype;\n}\n","import { isPlainObject } from '../utils/isPlainObject';\nimport type { ResponseMeta } from '../runtime/contextTypes';\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 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","export function queryToObject(params: URLSearchParams): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of params) {\n result[key] = value;\n }\n return result;\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';\n\n/**\n * 根据注入类型获取对应的值(内置)\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 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 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 { injectParamsAsync } from '../injection/injectParams';\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 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 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(result, 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(result, meta);\n } catch (err) {\n // handler 抛错时关闭未完成的 SSE 流,避免泄漏\n autoCloseSseOnError();\n throw err;\n }\n };\n\n return await compose(middlewares, ctx, finalHandler);\n}\n","import type { Server } from 'node:http';\nimport path from 'node:path';\nimport os from 'node:os';\nimport fs from 'node:fs/promises';\nimport { scanRoutes } from './router/scanRoutes';\nimport { sortRoutes } from './router/sortRoutes';\nimport { generateSchemaFiles } from './cli/generateSchemaFiles';\nimport { invalidateSchemaCache } from './validator/validateInput';\nimport { createServer } from './server/createServer';\nimport type { RouteManifest, WsRouteManifest } from './router/routeTypes';\nimport type { FaapiMiddleware } from './middleware/middlewareTypes';\nimport type { InjectorMap } from './middleware/injectorTypes';\nimport type { CorsOptions } from './middleware/cors';\nimport type { HelmetOptions } from './middleware/helmet';\nimport type { LoggerOptions } from './middleware/logger';\nimport type { FaapiContext } from './runtime/contextTypes';\n\n/**\n * createTestServer 入参\n *\n * 业务方一行代码启动带 schema 校验的 E2E 测试服务器。\n * 详见 src/testServer.md。\n */\nexport interface TestServerOptions {\n /** 项目根目录(路由源码所在,必填) */\n rootDir: string;\n // 路由扫描 glob,相对 rootDir;默认 ['src/api/**/*.ts']\n patterns?: string[];\n /**\n * schema 产物输出目录(绝对路径或相对 rootDir)。\n * 不传时自动 mkdtemp 生成临时目录,close() 时清理。\n * 传值时 close() 仍会清理该目录。\n */\n dist?: string;\n /** CORS 中间件配置,默认 false(禁用,避免污染断言) */\n cors?: CorsOptions | boolean;\n /** 安全头配置,默认 false */\n helmet?: HelmetOptions | boolean;\n /** 请求日志配置,默认 false(避免污染测试输出) */\n logger?: LoggerOptions | boolean;\n /** 全局中间件(外层洋葱) */\n middlewares?: FaapiMiddleware[];\n /** 全局注入器 */\n injectors?: InjectorMap;\n /** 请求错误钩子(在错误响应生成后调用,用于副作用) */\n onError?: (error: unknown, ctx: FaapiContext) => Promise<void> | void;\n /** 业务配置(注入到 ctx.config) */\n config?: Record<string, unknown>;\n /** 请求体大小限制(字节),默认 10MB */\n bodyLimit?: number;\n}\n\n/**\n * createTestServer 返回值\n *\n * 业务方通过 baseUrl 发 fetch 请求,close() 一行完成 teardown。\n */\nexport interface TestServer {\n /** Node.js HTTP Server 实例(已 listen) */\n server: Server;\n /** 形如 http://localhost:<随机端口> */\n baseUrl: string;\n /** 排序后的路由清单 */\n routes: RouteManifest;\n /** WebSocket 路由清单 */\n wsRoutes: WsRouteManifest;\n /** schema 临时目录绝对路径(业务方调试时可查看生成的 zod.js) */\n schemaDist: string;\n /**\n * 关闭 server + 清理 schema 目录 + 清空 schema 模块缓存\n *\n * 内部顺序:\n * 1. server.closeAllConnections?.()(Node 18+,强制断开 WS / 长连接)\n * 2. server.close()\n * 3. fs.rm(schemaDist, { recursive, force })\n * 4. invalidateSchemaCache()\n *\n * 幂等:重复调用不会重复清理。\n */\n close(): Promise<void>;\n}\n\n/** 默认路由扫描 glob */\nconst DEFAULT_PATTERNS = ['src/api/**/*.ts'];\n\n/** 默认请求体大小限制:10MB */\nconst DEFAULT_BODY_LIMIT = 10 * 1024 * 1024;\n\n/**\n * 一键启动带 schema 校验的 E2E 测试服务器\n *\n * 内部流程:\n * 1. scanRoutes 扫描路由\n * 2. sortRoutes 排序\n * 3. mkdtemp 创建临时 schema 目录(或用传入的 dist)\n * 4. generateSchemaFiles 生成 zod.js\n * 5. createServer 创建 server(默认禁用 CORS/Helmet/Logger,避免污染断言)\n * 6. server.listen(0) 随机端口\n * 7. 返回 TestServer\n *\n * 详见 src/testServer.md。\n *\n * @param options rootDir 必填,其余可选\n * @returns TestServer 实例\n */\nexport async function createTestServer(options: TestServerOptions): Promise<TestServer> {\n const {\n rootDir,\n patterns = DEFAULT_PATTERNS,\n dist,\n cors = false,\n helmet = false,\n logger = false,\n middlewares,\n injectors,\n onError,\n config,\n bodyLimit = DEFAULT_BODY_LIMIT,\n } = options;\n\n // 1. 扫描 + 排序路由\n const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);\n const sorted = sortRoutes(routes);\n\n // 2. schema 临时目录(未传 dist 时自动 mkdtemp)\n const schemaDist = dist\n ? path.isAbsolute(dist)\n ? dist\n : path.resolve(rootDir, dist)\n : await fs.mkdtemp(path.join(os.tmpdir(), 'faapi-test-schema-'));\n\n // 3. 生成 zod.js\n await generateSchemaFiles(sorted, rootDir, schemaDist);\n\n // 4. 创建 server(默认禁用 CORS/Helmet/Logger,避免污染断言)\n const { server } = createServer({\n routes: sorted,\n rootDir,\n dist: schemaDist,\n cors,\n helmet,\n logger,\n middlewares,\n injectors,\n onError,\n config,\n wsRoutes,\n bodyLimit,\n });\n\n // 5. listen(0) 随机端口\n const baseUrl = await listenOnRandomPort(server);\n\n // 6. 关闭状态标记(幂等保护)\n let closed = false;\n\n const testServer: TestServer = {\n server,\n baseUrl,\n routes: sorted,\n wsRoutes,\n schemaDist,\n\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n\n // 强制断开 WS / 长连接(Node 18+)\n const s = server as Server & {\n closeAllConnections?: () => void;\n closeIdleConnections?: () => void;\n };\n s.closeAllConnections?.();\n s.closeIdleConnections?.();\n\n // 关闭 HTTP server\n await new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n\n // 清理 schema 临时目录(失败不阻塞 close 流程;force: true 已尽可能容错)\n await fs.rm(schemaDist, { recursive: true, force: true }).catch(() => {\n /* 清理失败不阻塞:closed 标记已设,目录可能在 OS 临时目录被外部清理 */\n });\n\n // 清空 schema 模块缓存,避免串扰后续测试\n invalidateSchemaCache();\n },\n };\n\n return testServer;\n}\n\n/**\n * server.listen(0) 取随机端口,返回 http://localhost:<port>\n *\n * 抽出来便于失败时抛清晰错误。\n */\nfunction listenOnRandomPort(server: Server): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n server.listen(0, () => {\n const addr = server.address();\n if (typeof addr === 'object' && addr !== null) {\n resolve(`http://localhost:${addr.port}`);\n } else {\n reject(new Error('Failed to get server address'));\n }\n });\n server.on('error', (err) => {\n reject(err);\n });\n });\n}\n","import fg from 'fast-glob';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport type { RouteManifest, WsRouteManifest } from './routeTypes';\nimport { isHttpMethod, type HttpMethod } from './constants';\nimport { filePathToUrlPath, extractParamNames, isCatchAllSegment } from './parseRouteFile';\nimport {\n loadMiddlewaresFile,\n getCachedMiddlewares,\n setCachedMiddlewares,\n type LoadedMiddlewareBundle,\n} from '../middleware/loadMiddlewares';\nimport type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\n\n/**\n * 路由源码目录(写死为 src)\n */\nconst APP_DIR = 'src';\n\n/**\n * 把源码绝对路径转为产物绝对路径(用于 import 产物 .js)\n *\n * 产物结构打平 src/ 前缀:`src/api/hello/handler.ts` → `<dist>/api/hello/handler.js`\n *\n * @param sourceAbsPath 源码绝对路径(如 /root/src/api/hello/handler.ts)\n * @param rootDir 项目根目录\n * @param dist 产物目录(dist 或 .faapi)\n */\nfunction toProdAbsPath(sourceAbsPath: string, rootDir: string, dist: string): string {\n let rel = path.relative(rootDir, sourceAbsPath).replace(/\\\\/g, '/');\n // 去掉 src/ 前缀(打平产物结构)\n if (rel.startsWith(`${APP_DIR}/`)) {\n rel = rel.slice(APP_DIR.length + 1);\n }\n const prodRel = `${dist}/${rel.replace(/\\.ts$/, '.js')}`;\n return path.resolve(rootDir, prodRel);\n}\n\n/**\n * 从路由文件所在目录向上逐级查找 middlewares.ts(源码),\n * 按从根到路由目录的顺序合并中间件和注入器(父级在前,子级在后)。\n *\n * 若传入 dist,加载产物 middlewares.js(已编译);否则加载源码 middlewares.ts。\n *\n * 子级中间件包裹父级(洋葱模型下后注册的先执行 after);\n * 子级注入器覆盖父级同名注入器。\n *\n * @param routeFilePath 源码相对路径(如 src/api/hello/handler.ts)\n * @param rootDir 项目根目录\n * @param dist 产物目录(dist 或 .faapi),不传则加载源码\n */\nasync function findMergedMiddlewares(\n routeFilePath: string,\n rootDir: string,\n dist?: string,\n): Promise<{ middlewares: FaapiMiddleware[]; injectors: InjectorMap } | undefined> {\n const routeDir = path.dirname(routeFilePath);\n const resolvedRoot = path.resolve(rootDir);\n\n // 收集从根到路由目录的所有 middlewares 路径\n // dist 传入时查找产物 middlewares.js;否则查找源码 middlewares.ts(兼容旧路径)\n const mwPaths: string[] = [];\n let currentDir = path.resolve(rootDir, routeDir);\n while (true) {\n if (dist) {\n // 新模式:查找产物 middlewares.js\n const mwPath = path.join(currentDir, 'middlewares.js');\n const absMwPath = path.resolve(rootDir, mwPath);\n // 产物路径:把源码路径转为产物路径(打平 src/ 前缀)\n const prodAbsMwPath = toProdAbsPath(absMwPath, rootDir, dist);\n if (fs.existsSync(prodAbsMwPath)) {\n mwPaths.push(prodAbsMwPath);\n }\n } else {\n // 旧模式:查找源码 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 mwPaths.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 if (mwPaths.length === 0) return undefined;\n\n // 从根到路由目录逐级加载(mwPaths 是从路由目录向上收集的,需反转)\n mwPaths.reverse();\n\n const mergedMiddlewares: FaapiMiddleware[] = [];\n const mergedInjectors: InjectorMap = {};\n\n for (const absMwPath of mwPaths) {\n let bundle: LoadedMiddlewareBundle | undefined = getCachedMiddlewares(absMwPath);\n if (bundle === undefined) {\n bundle = await loadMiddlewaresFile(absMwPath);\n setCachedMiddlewares(absMwPath, bundle);\n }\n // 子级中间件追加在父级之后(洋葱模型:后注册的中间件在内层)\n mergedMiddlewares.push(...bundle.middlewares);\n // 子级注入器覆盖父级同名注入器\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/**\n * 从 handler 模块中提取导出的 HTTP 方法名\n *\n * @param absPath handler 文件路径(产物 .js 或源码 .ts,取决于调用方)\n */\nexport async function extractMethodsFromHandler(absPath: string): Promise<HttpMethod[]> {\n try {\n const module = await importWithCacheBust(absPath);\n const methods: HttpMethod[] = [];\n for (const key of Object.keys(module)) {\n if (isHttpMethod(key) && typeof module[key] === 'function') {\n methods.push(key);\n }\n }\n return methods;\n } catch (err) {\n // 模块加载失败时输出警告,避免静默吞没错误\n const reason = err instanceof Error ? err.message : String(err);\n console.warn(`[faapi] 加载路由文件失败 ${absPath}: ${reason}`);\n return [];\n }\n}\n\n/**\n * 检测 handler 模块是否导出 WS 函数\n *\n * WS 导出与 HTTP 方法导出(GET/POST 等)同级,导出名必须为 `WS`。\n *\n * @param absPath handler 文件路径(产物 .js 或源码 .ts,取决于调用方)\n */\nexport async function hasWsExport(absPath: string): Promise<boolean> {\n try {\n const module = await importWithCacheBust(absPath);\n return typeof module['WS'] === 'function';\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.warn(`[faapi] 加载路由文件失败(WS 检测)${absPath}: ${reason}`);\n return false;\n }\n}\n\n/**\n * 扫描 api 目录,同时生成 HTTP 路由清单和 WebSocket 路由清单\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 * @param rootDir 项目根目录\n * @param patterns glob patterns(源码 .ts 路径,如 src/api 下所有 .ts)\n * @param dist 产物目录(dist 或 .faapi)。传入时 import 产物 .js(不依赖 tsx);\n * 不传时 import 源码 .ts(旧模式,需要 tsx)。\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 // dist 传入时 import 产物 .js(打平 src/ 前缀);否则 import 源码 .ts\n const importPath = dist ? toProdAbsPath(absPath, rootDir, dist) : absPath;\n const urlPath = filePathToUrlPath(normalizedFile);\n const paramNames = extractParamNames(urlPath);\n const isDynamic = paramNames.length > 0;\n const isCatchAll = normalizedFile.split('/').some(isCatchAllSegment);\n const middlewareBundle = await findMergedMiddlewares(normalizedFile, rootDir, dist);\n\n // HTTP 方法导出\n const methods = await extractMethodsFromHandler(importPath);\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 middlewares: middlewareBundle?.middlewares,\n injectors: middlewareBundle?.injectors,\n });\n }\n\n // WS 导出\n const hasWs = await hasWsExport(importPath);\n if (hasWs) {\n wsRoutes.push({\n urlPath,\n filePath: normalizedFile,\n paramNames,\n isDynamic,\n isCatchAll: isCatchAll || undefined,\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 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 * 失效所有中间件缓存(watch 模式下文件变化时调用)\n */\nexport function invalidateMiddlewareCache(): void {\n middlewareCache.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 */\nexport async function loadMiddlewaresFile(filePath: string): Promise<LoadedMiddlewareBundle> {\n try {\n const module = await importWithCacheBust(filePath);\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 } catch {\n return { middlewares: [], injectors: {} };\n }\n}\n","import type { RouteManifest } from './routeTypes';\n\n/**\n * 路由排序规则:\n * 1. 静态路由优先于动态路由\n * 2. 动态路由优先于 catch-all 路由\n * 3. 路径段数少的优先\n * 4. 字母序排列\n */\nexport function sortRoutes(routes: RouteManifest): RouteManifest {\n return [...routes].sort((a, b) => {\n // 1. 静态路由优先于动态路由\n if (a.isDynamic !== b.isDynamic) {\n return a.isDynamic ? 1 : -1;\n }\n\n // 2. catch-all 路由优先级最低(排在所有动态路由之后)\n if (a.isCatchAll !== b.isCatchAll) {\n return a.isCatchAll ? 1 : -1;\n }\n\n // 3. 路径段数少的优先\n const aSegments = a.urlPath.split('/').filter(Boolean).length;\n const bSegments = b.urlPath.split('/').filter(Boolean).length;\n if (aSegments !== bSegments) {\n return aSegments - bSegments;\n }\n\n // 4. 字母序排列\n return a.urlPath.localeCompare(b.urlPath);\n });\n}\n","import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport type { RouteManifest } from '../router/routeTypes';\nimport type { RouteSchemaSource } from './collectRouteSchemaSources';\nimport type { HandlerTypeInfo } from '../ast/extractHandlerTypes';\nimport { collectRouteSchemaSources } from './collectRouteSchemaSources';\nimport {\n generateZodSchemaSource,\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 * 公用函数复用:schema 引用 coerceNumber / coerceBoolean / coerceMap / coerceSet 变量时,\n * 这些变量从 dist 根部的 faapi-helpers.js import(跨文件复用,仅一份声明)。\n *\n * @param sources 同一文件的 schema 提取结果(含多个方法,如 GETQuery + POSTBody)\n * @param allTypes 该文件的所有命名类型(用于解析循环引用中的 ref)\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 allTypes: Map<string, HandlerTypeInfo>,\n helpersImportPath: string,\n): string {\n const resolveType = (name: string) => allTypes.get(name)?.runtimeType;\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 // 推断 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 const block = [`// ${schemaName}`];\n // generateZodSchemaSource 自带 import 语句,剥离后由本函数统一管理 import\n // 传入 schemaName 作为 exportName,确保导出名与 validateInput 查找的一致\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 公用函数,若有则注入 import 语句\n // 公用函数包含 coerceNumber / coerceBoolean(query/params 的 string 转换)和\n // coerceMap / coerceSet(Map/Set 的 JSON 还原,body 场景也会引用)\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 * 为路由清单中每个 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, allTypesByFile } = 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 const allTypes = allTypesByFile.get(filePath) ?? new Map();\n\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 source = generateSchemaFileSource(fileSources, allTypes, helpersImportPath);\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 await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.writeFile(outputPath, source, 'utf-8');\n}\n","import type { RuntimeType, TupleElement, PropertyType, TypeConstraint } 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 }\n return;\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 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.properties, 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) => v === \"true\" || v === \"1\" ? true : v === \"false\" || v === \"0\" ? false : v;';\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 if (restExpression) {\n return `z.tuple([${fixedExprs.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 */\nfunction generateObjectExpression(properties: PropertyType[], ctx: CodeGenContext): string {\n const fields = 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 return `z.object({ ${fields.join(', ')} })`;\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 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 lines: string[] = [];\n lines.push(\"import { z } from 'zod';\");\n lines.push('');\n\n // 生成命名类型声明(z.lazy 处理循环引用)\n for (const [n, type] of ctx.namedTypes) {\n lines.push(generateNamedTypeDeclaration(n, type, ctx));\n }\n if (ctx.namedTypes.size > 0) lines.push('');\n\n // 生成入口 schema 导出(含循环引用时用 z.lazy)\n const entryExpr = runtimeTypeToZodExpression(typeInfo.runtimeType, ctx);\n const hasSelfRef = containsRef(typeInfo.runtimeType, new Set([typeInfo.name]));\n if (hasSelfRef) {\n lines.push(`export const ${name}Schema = z.lazy(() => ${entryExpr});`);\n } else {\n lines.push(`export const ${name}Schema = ${entryExpr};`);\n }\n\n return lines.join('\\n');\n}\n","import { getSchemaName } from './schemaName';\nimport { InternalError } from '../errors/httpErrors';\nimport type { ValidationIssue, ValidationErrorCode } from '../errors/httpErrors';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\n\n/**\n * 校验结果类型\n */\nexport interface ValidationResult {\n valid: boolean;\n issues: ValidationIssue[];\n data: Record<string, 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 mod = (await importWithCacheBust(schemaPath)) 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 // 无类型声明:跳过校验\n if (schema === undefined || schema === null) {\n const data =\n typeof input === 'object' && input !== null && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : {};\n return { valid: true, issues: [], data };\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 const inputObj =\n typeof input === 'object' && input !== null && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : {};\n\n // zod safeParse 校验(query/params 的 preprocess 已在 schema 生成阶段内联)\n const zodSchema = schema as { safeParse: (v: unknown) => ZodSafeParseResult };\n const result = zodSchema.safeParse(inputObj);\n\n if (result.success) {\n const data =\n typeof result.data === 'object' && result.data !== null && !Array.isArray(result.data)\n ? (result.data as Record<string, unknown>)\n : {};\n return { valid: true, issues: [], data };\n }\n\n // 将 zod issues 转为 ValidationIssue\n const issues = mapZodIssues(result.error);\n return { valid: false, issues, data: inputObj };\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 / invalid_union → TYPE_MISMATCH(422)\n * - too_small / too_big / invalid_string → INVALID_VALUE(422)\n * - invalid_value(v4 新增,合并了 v3 的 invalid_enum_value / invalid_literal)→ INVALID_VALUE(422)\n * - unrecognized_keys → INVALID_FORMAT(400)\n * - custom → 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.code, issue.message);\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\n/**\n * 映射 zod issue code 到框架 ValidationErrorCode\n */\nfunction mapZodCode(zodCode: string, message: string): ValidationErrorCode {\n switch (zodCode) {\n case 'invalid_type':\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_string':\n case 'too_small':\n case 'too_big':\n case 'invalid_intersection_types':\n case 'not_multiple_of':\n return 'INVALID_VALUE';\n case 'custom':\n return 'INVALID_VALUE';\n default:\n if (message.includes('Required') || message.includes('required')) {\n return 'MISSING_FIELD';\n }\n return 'INVALID_VALUE';\n }\n}\n\n/**\n * 从 zod message 中提取期望类型(兜底)\n */\nfunction mapExpectedFromMessage(message: string): string {\n // zod v3 message 示例:\"Expected string, received number\"\n const match = message.match(/Expected\\s+(\\w+)/i);\n return match ? match[1]!.toLowerCase() : 'unknown';\n}\n\n/**\n * 从 zod message 中提取实际类型(兜底)\n */\nfunction mapReceivedFromMessage(message: string): string {\n const match = message.match(/received\\s+(\\w+)/i);\n return match ? match[1]!.toLowerCase() : 'unknown';\n}\n","import {\n createServer as createHttpServer,\n type Server,\n type IncomingMessage,\n type ServerResponse,\n} from 'node:http';\nimport { createSecureServer as createHttp2SecureServer } from 'node:http2';\nimport { readFileSync } from 'node:fs';\nimport { Readable } from 'node:stream';\nimport path from 'node:path';\nimport type { RouteManifest, WsRouteManifest, RoutesRef } from '../router/routeTypes';\nimport { matchRoute, matchDynamicPath } from '../router/matchRoute';\nimport { loadRouteModule } from '../loader/loadRouteModule';\nimport { createContext } from '../runtime/createContext';\nimport { resolveInput } from '../runtime/resolveInput';\nimport { invokeHandler, compose, mergeMeta } from '../runtime/invokeHandler';\nimport type { FaapiContext, ResponseMeta } from '../runtime/contextTypes';\nimport { sendNodeResponse } from '../response/sendNodeResponse';\nimport { RouteNotFoundError, MethodNotAllowedError, ValidationError } 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 { 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';\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(req: IncomingMessage, bodyLimit: number = DEFAULT_BODY_LIMIT): Request {\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 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 new Request(url.toString(), { method, headers });\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 new Request(url.toString(), {\n method,\n headers,\n body: limitedStream,\n duplex: 'half',\n } as RequestInit);\n}\n\n/**\n * 限制 ReadableStream 的总字节数,超过限制时抛错\n */\nfunction limitStreamSize(\n stream: ReadableStream<Uint8Array>,\n maxSize: number,\n): ReadableStream<Uint8Array> {\n let totalSize = 0;\n const reader = stream.getReader();\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n reader.releaseLock();\n return;\n }\n totalSize += value.byteLength;\n if (totalSize > maxSize) {\n controller.error(new Error(`请求体超过大小限制 ${maxSize} 字节`));\n reader.releaseLock();\n return;\n }\n controller.enqueue(value);\n },\n cancel(reason) {\n reader.cancel(reason);\n },\n });\n}\n\n/**\n * 检查指定路径是否有其他方法的路由(用于 405 响应)\n */\nfunction findAllowedMethods(routes: RouteManifest, path: string): string[] {\n const methods = new Set<string>();\n for (const route of routes) {\n if (route.urlPath === path) {\n methods.add(route.method);\n continue;\n }\n // 也检查动态路由\n if (route.isDynamic) {\n const params = matchDynamicPath(route.urlPath, path, route.paramNames, route.isCatchAll);\n if (params !== null) {\n methods.add(route.method);\n }\n }\n }\n return Array.from(methods);\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 /** 请求日志配置,默认启用(与 cors 一致) */\n logger?: LoggerOptions | boolean;\n /** 请求体大小限制(字节) */\n bodyLimit?: number;\n /** HTTP/2 配置,启用时需提供 SSL 证书路径 */\n http2?: Http2Options | 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 logger: loggerOption,\n bodyLimit = DEFAULT_BODY_LIMIT,\n http2: http2Option,\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 // 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 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 configMiddlewares,\n onError,\n config,\n globalMiddlewares,\n globalInjectors,\n bodyLimit,\n ).catch(() => {\n res.statusCode = 500;\n res.end();\n });\n });\n\n // 挂载 WebSocket 升级处理(仅当提供了 WS 路由)\n if (routesRef.wsCurrent.length > 0) {\n attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares });\n }\n\n return { server, routesRef };\n}\n\nasync function handleRequest(\n routes: RouteManifest,\n rootDir: string,\n dist: string,\n req: IncomingMessage,\n res: ServerResponse,\n configMiddlewares: FaapiMiddleware[],\n onError: ((error: unknown, ctx: FaapiContext) => Promise<void> | void) | undefined,\n config: Record<string, unknown> | undefined,\n globalMiddlewares: FaapiMiddleware[] | undefined,\n globalInjectors: InjectorMap | undefined,\n bodyLimit: number,\n): Promise<void> {\n const request = toWebRequest(req, bodyLimit);\n const method = request.method.toUpperCase();\n const urlPath = new URL(request.url).pathname;\n const ctx = createContext(request, {}, config, getClientIp(req));\n const meta = (ctx as FaapiContext & { meta: ResponseMeta }).meta;\n\n // 路由处理管线:作为 CORS 中间件的 next\n // 包含路由匹配、静态文件、参数校验、handler 调用、响应格式化\n const routePipeline = async (): Promise<Response> => {\n // 尝试匹配路由\n const match = matchRoute(routes, method, urlPath);\n\n if (!match) {\n // 检查是否有其他方法匹配该路径\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 ctx.params = match.params;\n const { route } = match;\n\n // 处理 API 路由(handler.ts)\n const absoluteFilePath = path.resolve(rootDir, route.filePath);\n const routeModule = await loadRouteModule(absoluteFilePath, route.method);\n const input = await resolveInput(route.method, request);\n\n // 参数校验(运行时按 route.filePath 计算 zod.js 路径,import 并 safeParse)\n const inputType = getInputTypeForMethod(route.method);\n const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);\n const result = await validateInput(schemaPath, route.method, inputType, input);\n if (!result.valid) {\n throw new ValidationError('参数校验失败', result.issues);\n }\n\n const body = hasBody(route.method) ? result.data : undefined;\n // 合并注入器:全局注入器为基线,目录注入器覆盖同名\n const mergedInjectors = globalInjectors\n ? { ...globalInjectors, ...route.injectors }\n : route.injectors;\n const response = await invokeHandler(\n routeModule.handler,\n ctx,\n body,\n route.middlewares,\n mergedInjectors,\n );\n\n return response;\n };\n\n try {\n let response: Response;\n // 外层中间件链:配置中间件(CORS/helmet/logger)+ 全局中间件\n // 顺序:CORS → helmet → logger → 全局 → routePipeline(含目录中间件 + handler)\n const outerMiddlewares: FaapiMiddleware[] = [];\n if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);\n if (globalMiddlewares && globalMiddlewares.length > 0) {\n outerMiddlewares.push(...globalMiddlewares);\n }\n\n if (outerMiddlewares.length > 0) {\n response = await compose(outerMiddlewares, ctx, routePipeline);\n } else {\n response = await routePipeline();\n }\n await sendNodeResponse(response, res);\n } catch (err: unknown) {\n // 错误处理兜底链(参考 Fastify 语义):\n // 1. 框架内置 formatErrorResponse 兜底(handler 抛错时)\n // 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n // 3. 响应发出后 → onError 触发副作用(不修改已发出的响应)\n // 注:业务方如需自定义错误响应,在全局中间件中 try/catch next() 即可\n const errorResponse = buildErrorResponse(err);\n await sendNodeResponse(mergeMeta(errorResponse, meta), res);\n\n // 响应已发出,触发 onError 副作用(日志/告警),自身抛错被忽略\n if (onError) {\n try {\n await onError(err, ctx);\n } catch {\n // onError 自身抛错不影响已发出的响应\n }\n }\n }\n}\n","import type { RouteManifest, RouteMatch, WsRouteManifest, WsRouteMatch } from './routeTypes';\n\n/**\n * 根据请求路径和方法匹配路由\n * @param routes 已排序的路由清单\n * @param method HTTP 方法\n * @param path 请求路径\n * @returns 匹配结果,包含路由记录和参数\n */\nexport function matchRoute(routes: RouteManifest, method: string, path: string): RouteMatch | null {\n for (const route of routes) {\n // 方法不匹配,跳过\n if (route.method !== method) {\n continue;\n }\n\n // 静态路由直接比较\n if (!route.isDynamic) {\n if (route.urlPath === path) {\n return { route, params: {} };\n }\n continue;\n }\n\n // 动态路由匹配\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 * 匹配 WebSocket 路由(无 HTTP 方法维度)\n *\n * WS 路由只按路径匹配,协议升级时调用。\n * @param wsRoutes WebSocket 路由清单\n * @param path 请求路径\n */\nexport function matchWsRoute(wsRoutes: WsRouteManifest, path: string): WsRouteMatch | null {\n for (const route of wsRoutes) {\n // 静态路由直接比较\n if (!route.isDynamic) {\n if (route.urlPath === path) {\n return { route, params: {} };\n }\n continue;\n }\n\n // 动态路由匹配\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 * 动态路径匹配\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 const patternSegments = pattern.split('/').filter(Boolean);\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 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 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 const patternSeg = patternSegments[i];\n const pathSeg = pathSegments[i];\n\n if (patternSeg.startsWith(':')) {\n // 动态段,提取参数值\n const paramName = patternSeg.slice(1);\n params[paramName] = pathSeg;\n } else if (patternSeg !== pathSeg) {\n // 静态段不匹配\n return null;\n }\n }\n\n // 确保所有参数名都被提取到\n if (Object.keys(params).length !== paramNames.length) {\n return null;\n }\n\n return params;\n}\n","/**\n * 从模块中解析出指定名称的导出\n *\n * 解析顺序:\n * 1. 具名导出:export function GET() {}\n * 2. 默认导出的对象属性:export default { GET() {} }\n *\n * @param module 动态 import 得到的模块对象\n * @param exportName 导出名(如 'GET')\n */\nexport function resolveExport(module: Record<string, unknown>, exportName: string): unknown {\n // 1. 具名导出\n if (exportName in module && typeof module[exportName] !== 'undefined') {\n return module[exportName];\n }\n\n // 2. 默认导出的对象属性\n const defaultExport = module.default;\n if (defaultExport !== null && typeof defaultExport === 'object') {\n const value = (defaultExport as Record<string, unknown>)[exportName];\n if (value !== undefined) {\n return value;\n }\n }\n\n return undefined;\n}\n","/**\n * 校验导出值是否为合法的 handler 函数\n */\nexport function validateRouteModule(\n value: unknown,\n method: string,\n filePath: string,\n): asserts value is (...args: unknown[]) => unknown {\n if (typeof value !== 'function') {\n throw new Error(\n `Route module \"${filePath}\" does not export a valid handler for method \"${method}\". ` +\n `Expected a function, got ${typeof value}.`,\n );\n }\n}\n","import type { HttpMethod } from '../router/constants';\nimport { resolveExport } from './resolveExports';\nimport { validateRouteModule } from './validateRouteModule';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\n\nexport interface RouteModule {\n handler: (...args: unknown[]) => unknown;\n method: HttpMethod;\n}\n\n/**\n * 动态 import 路由文件并提取 handler\n * @param filePath 路由文件的绝对路径\n * @param method HTTP 方法名(也是导出名)\n */\nexport async function loadRouteModule(filePath: string, method: HttpMethod): Promise<RouteModule> {\n let module: Record<string, unknown>;\n try {\n module = await importWithCacheBust(filePath);\n } catch (err: unknown) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to load route module \"${filePath}\": ${reason}`, { cause: err });\n }\n\n const handler = resolveExport(module, method);\n validateRouteModule(handler, method, filePath);\n\n return { handler, method };\n}\n","export type JsonParseResult = { success: true; data: unknown } | { success: false; error: string };\n\nexport function parseJsonBody(text: string): JsonParseResult {\n try {\n const data = JSON.parse(text);\n return { success: true, data };\n } catch {\n return { success: false, error: 'Invalid JSON body' };\n }\n}\n","/**\n * 解析 multipart/form-data 请求\n * 使用 Web 标准 Request.formData() API\n */\n\nexport interface UploadedFile {\n name: string; // 字段名\n filename: string; // 原始文件名\n type: string; // MIME 类型\n size: number; // 文件大小(字节)\n arrayBuffer: () => Promise<ArrayBuffer>; // 获取文件内容\n}\n\nexport interface MultipartResult {\n /** 表单字段(同名字段用数组收集,单值保持 string) */\n fields: Record<string, string | string[]>;\n files: UploadedFile[];\n}\n\n/**\n * 解析 multipart/form-data\n *\n * 同名字段的处理:\n * - 第一次出现:string\n * - 第二次出现:string[](包含所有同名字段的值)\n * - 文件始终收集到 files 数组(通过 name 字段标识归属)\n */\nexport async function parseMultipart(request: Request): Promise<MultipartResult> {\n const formData = await request.formData();\n const fields: Record<string, string | string[]> = {};\n const files: UploadedFile[] = [];\n\n for (const [key, value] of formData.entries()) {\n if (value instanceof File) {\n files.push({\n name: key,\n filename: value.name,\n type: value.type,\n size: value.size,\n arrayBuffer: () => value.arrayBuffer(),\n });\n } else {\n // 同名字段收集为数组\n if (key in fields) {\n const existing = fields[key];\n if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n 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 通过 injectParams 单独注入。\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 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 (text.trim() === '') 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 (text.trim() === '') {\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 const url = new URL(request.url);\n return queryToObject(url.searchParams);\n}\n","import type { ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\n\n/**\n * 将 Web Response 写入 Node.js ServerResponse\n *\n * 使用 stream pipe 处理背压,避免高并发下内存膨胀。\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\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 nodeStream.on('error', reject);\n res.on('error', reject);\n res.on('finish', resolve);\n nodeStream.pipe(res);\n });\n return;\n }\n\n res.end();\n}\n","import type { IncomingMessage } from 'node:http';\n\n/**\n * 从 IncomingMessage 提取客户端 IP\n *\n * 优先级:\n * 1. `x-forwarded-for` 第一个 IP(反向代理场景,如 nginx/CDN)\n * 2. `req.socket.remoteAddress`(直连场景)\n *\n * `x-forwarded-for` 格式:`client, proxy1, proxy2`,取第一个即客户端真实 IP。\n * 注意:仅在受信任的反向代理后才有效;若客户端直连且未经过代理,该 header 可被伪造。\n *\n * @returns 客户端 IP,无法获取时返回空字符串\n */\nexport function getClientIp(req: IncomingMessage): string {\n // 1. x-forwarded-for 优先(反向代理场景)\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 // 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","/**\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 { WebSocketServer, WebSocket } 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 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 */\nasync function loadWsHandler(filePath: string, ctx: WsContext): Promise<WsEventHandlers | void> {\n const module = await importWithCacheBust(filePath);\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: Buffer) => {\n // ws 库 message 事件传 Buffer[],合并为单个 Buffer 后转 string\n const buf = Buffer.isBuffer(data) ? data : Buffer.from(data as unknown as Uint8Array);\n handlers.onMessage!(ws, 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/**\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 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\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 { server, routesRef, rootDir, config, globalMiddlewares } = options;\n\n const wss = new WebSocketServer({ noServer: true });\n\n server.on('upgrade', async (req: IncomingMessage, socket: Socket, head: Buffer) => {\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));\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);\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 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 { FaapiError } from './FaapiError';\nimport { ValidationError, MethodNotAllowedError } from './httpErrors';\n\n/**\n * 将错误转换为统一的 JSON 响应结构\n *\n * 格式:\n * {\n * \"error\": {\n * \"code\": \"VALIDATION_ERROR\",\n * \"message\": \"Invalid query parameters\",\n * \"issues\": [...] // 仅 ValidationError 包含\n * }\n * }\n */\nexport function formatErrorResponse(error: unknown): Response {\n if (error instanceof ValidationError) {\n const body: Record<string, unknown> = {\n code: error.code,\n message: error.message,\n issues: error.issues,\n };\n return new Response(JSON.stringify({ error: body }), {\n status: error.statusCode,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n if (error instanceof MethodNotAllowedError) {\n const body = {\n code: error.code,\n message: error.message,\n };\n return new Response(JSON.stringify({ error: body }), {\n status: error.statusCode,\n headers: {\n 'Content-Type': 'application/json',\n Allow: error.allowedMethods.join(', '),\n },\n });\n }\n\n if (error instanceof FaapiError) {\n const body = {\n code: error.code,\n message: error.message,\n };\n return new Response(JSON.stringify({ error: body }), {\n status: error.statusCode,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n // 未知错误:500 INTERNAL_ERROR\n const body = {\n code: 'INTERNAL_ERROR',\n message: error instanceof Error ? error.message : 'An unknown error occurred',\n };\n return new Response(JSON.stringify({ error: body }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n });\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 抛错时)\n * 2. 内置兜底仍抛错 → 最简 500 JSON 响应\n *\n * 业务方如需自定义错误响应,在全局中间件中 try/catch next() 即可。\n */\nexport function buildErrorResponse(err: unknown): Response {\n try {\n return formatErrorResponse(err);\n } catch {\n // 极端情况:内置兜底也失败,返回最简 500\n return new Response(\n JSON.stringify({ error: { code: 'INTERNAL_ERROR', message: 'Internal Server Error' } }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n}\n","/**\n * WebSocket Handler 类型定义与 socket 封装\n *\n * 用户在 handler.ts 中导出 WS 函数,返回事件回调对象。\n * 框架在协议升级成功后调用对应回调,传递封装后的 WsSocket。\n *\n * @see wsHandler.md 功能说明\n */\n\n/**\n * faapi 封装的 WebSocket socket 抽象\n *\n * 不直接暴露 ws 库的原生 socket,提供更安全、易用的 API。\n * send 对象时自动 JSON.stringify。\n */\nexport interface WsSocket {\n /** 发送数据(string/Buffer 直发,对象自动 JSON.stringify) */\n send(data: string | Buffer | object): void;\n /** 关闭连接 */\n close(code?: number, reason?: string | Buffer): void;\n /** 连接状态:0=connecting, 1=open, 2=closing, 3=closed */\n readonly readyState: number;\n}\n\n/**\n * WebSocket 事件回调集合\n *\n * 用户在 WS handler 中返回此对象,框架在对应事件触发时调用。\n * 所有回调可选,未提供则忽略事件。\n */\nexport interface WsEventHandlers {\n /** 连接建立时触发 */\n onOpen?: (ws: WsSocket) => void;\n /** 收到客户端消息时触发 */\n onMessage?: (ws: WsSocket, message: string | Buffer) => void;\n /** 连接关闭时触发 */\n onClose?: (ws: WsSocket, code: number, reason: string) => void;\n /** 发生错误时触发 */\n onError?: (ws: WsSocket, error: Error) => void;\n}\n\n/**\n * WebSocket 上下文(握手阶段构造)\n *\n * 与 HTTP FaapiContext 类似但精简,提供路由参数、查询参数、请求头、配置。\n * 可通过 declare module '@faapi/faapi' 增强自定义字段。\n */\nexport interface WsContext {\n /** 动态路由参数(如 [id] → params.id) */\n params: Record<string, string>;\n /** URL 查询参数 */\n query: URLSearchParams;\n /** 请求头 */\n headers: Headers;\n /** 业务配置(来自 faapi.config.ts) */\n config: Record<string, unknown>;\n /** 中间件塞入的用户信息(鉴权等) */\n user?: unknown;\n /** 允许通过 declare module 扩展 */\n [key: string]: unknown;\n}\n\n/**\n * WS handler 签名:接收 WsContext,返回事件回调对象(或无返回)\n */\nexport type WsHandler = (ctx: WsContext) => WsEventHandlers | void;\n\n/**\n * 将 ws 库的原生 WebSocket 封装为 faapi 的 WsSocket\n *\n * - send 对象时自动 JSON.stringify\n * - close/re.readyState 透传原生 socket\n */\nexport function wrapWsSocket(rawSocket: {\n send: (data: string | Buffer, cb?: (err?: Error) => void) => void;\n close: (code?: number, reason?: string | Buffer) => void;\n readonly readyState: number;\n}): WsSocket {\n return {\n send(data: string | Buffer | object): void {\n const payload =\n typeof data === 'string' || Buffer.isBuffer(data) ? data : JSON.stringify(data);\n rawSocket.send(payload);\n },\n close(code?: number, reason?: string | Buffer): void {\n rawSocket.close(code, reason);\n },\n get readyState(): number {\n return rawSocket.readyState;\n },\n };\n}\n","import { WebSocket, type RawData } from 'ws';\n\n/**\n * WebSocket 测试客户端\n *\n * 公开导出 connectWs + MessageQueue + waitForWsOpen,业务方测试 WS 路由时\n * 免去手写\"消息竞态防护 + 三事件监听 + 端口拼接\"样板代码。\n *\n * 详见 src/wsTestClient.md。\n */\n\n/**\n * connectWs 入参\n */\nexport interface WsTestClientOptions {\n /** 等待 open 的超时(ms),默认 2000 */\n timeout?: number;\n /** 握手请求头(如 authorization) */\n headers?: Record<string, string>;\n /** WS 子协议 */\n protocols?: string | string[];\n}\n\n/**\n * connectWs 返回值\n *\n * 业务方通过 ws.send() 发消息,queue.next() 取消息,close() 关闭。\n */\nexport interface WsTestClient {\n /** ws 库原生实例,业务方可直接 ws.send() / ws.close() */\n ws: WebSocket;\n /** 已开始缓冲的消息队列,调 next(timeout?) 取下一条 */\n queue: MessageQueue;\n /**\n * 关闭 ws 并等待 'close' 事件\n *\n * 内部:\n * 1. 若 ws 仍 OPEN/CLOSING,调 ws.close()\n * 2. 等待 'close' 事件(超时 1000ms 强制 resolve)\n *\n * 幂等:重复调用不抛错。\n */\n close(): Promise<void>;\n}\n\n/**\n * 消息队列:避免 once('message') 与服务端 onOpen 推送的竞态\n *\n * 服务端在 handleUpgrade 回调里同步触发 onOpen 并 send('connected'),\n * 客户端 'open' 事件触发后到注册 once('message') 之间存在窗口,\n * 若 'connected' 在此窗口内到达,once 会错过。\n *\n * 队列在创建 ws 时立即监听 'message',按 FIFO 顺序消费。\n */\nexport class MessageQueue {\n private queue: string[] = [];\n private waiters: Array<(msg: string) => void> = [];\n private listener: (data: RawData) => void;\n\n constructor(ws: WebSocket) {\n this.listener = (data: RawData) => {\n const msg = normalizeRawData(data);\n const waiter = this.waiters.shift();\n if (waiter) {\n waiter(msg);\n } else {\n this.queue.push(msg);\n }\n };\n ws.on('message', this.listener);\n }\n\n /**\n * 取下一条消息\n *\n * 队列有则立即 resolve,无则注册 waiter 等待下一条 'message' 事件。\n * 超时未到 → reject('WebSocket message timeout'),waiter 被清理。\n *\n * @param timeout 超时毫秒,默认 2000\n */\n next(timeout = 2000): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n // 先定义 wrapped,再定义引用 wrapped 的 timer,避免 TDZ 风格可读性问题\n const wrapped = (msg: string) => {\n clearTimeout(timer);\n resolve(msg);\n };\n const timer = setTimeout(() => {\n const idx = this.waiters.indexOf(wrapped);\n if (idx >= 0) this.waiters.splice(idx, 1);\n reject(new Error('WebSocket message timeout'));\n }, timeout);\n\n const msg = this.queue.shift();\n if (msg !== undefined) {\n wrapped(msg);\n } else {\n this.waiters.push(wrapped);\n }\n });\n }\n}\n\n/**\n * 将 ws 库的 RawData 统一转为 utf8 字符串\n *\n * RawData 可能形态:\n * - Buffer(最常见)\n * - Buffer[](消息被分片,需先 concat)\n * - ArrayBuffer / ArrayBufferView(部分运行时)\n */\nfunction normalizeRawData(data: RawData): string {\n if (Buffer.isBuffer(data)) {\n return data.toString('utf8');\n }\n if (Array.isArray(data)) {\n return Buffer.concat(data).toString('utf8');\n }\n // ArrayBuffer / ArrayBufferView\n return Buffer.from(data as unknown as Uint8Array).toString('utf8');\n}\n\n/**\n * Promise 化等待 ws 'open' 事件\n *\n * 同时监听 'open' / 'error' / 'close' 三事件,任一触发都清理 timer,\n * 避免 timer 泄漏。\n *\n * @param ws WebSocket 实例\n * @param timeout 超时毫秒,默认 2000\n * @returns 'open' → resolve;'error' → reject(err);'close' → reject;超时 → reject\n */\nexport function waitForWsOpen(ws: WebSocket, timeout = 2000): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error('WebSocket open timeout'));\n }, timeout);\n\n const cleanup = () => {\n clearTimeout(timer);\n ws.removeListener('open', onOpen);\n ws.removeListener('error', onError);\n ws.removeListener('close', onClose);\n };\n\n const onOpen = () => {\n cleanup();\n resolve();\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const onClose = () => {\n cleanup();\n reject(new Error('WebSocket closed before open'));\n };\n\n ws.once('open', onOpen);\n ws.once('error', onError);\n ws.once('close', onClose);\n });\n}\n\n/**\n * 一键连接 WS server\n *\n * 内部流程:\n * 1. baseUrl 协议转换(http → ws,https → wss)\n * 2. new WebSocket(url, protocols, { headers })\n * 3. 立即创建 MessageQueue(开始缓冲消息,避免竞态)\n * 4. waitForWsOpen 等待连接建立(三事件监听 + 超时清理)\n * 5. 返回 WsTestClient\n *\n * 连接失败(中间件拦截 / 路径未匹配 / 超时)→ reject。\n *\n * @param baseUrl createTestServer().baseUrl(http://...)\n * @param pathname WS 路径,如 '/api/chat',可含 query\n * @param options timeout / headers / protocols\n * @returns WsTestClient 实例\n */\nexport async function connectWs(\n baseUrl: string,\n pathname: string,\n options: WsTestClientOptions = {},\n): Promise<WsTestClient> {\n const { timeout = 2000, headers, protocols } = options;\n\n // 协议转换:http → ws,https → wss(业务方传入 createTestServer().baseUrl)\n const wsBaseUrl = baseUrl.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:');\n const url = `${wsBaseUrl}${pathname}`;\n\n const ws = new WebSocket(url, protocols, headers ? { headers } : undefined);\n const queue = new MessageQueue(ws);\n\n // 等待 open;失败时主动 close ws,避免 socket 处于 CONNECTING 状态泄漏\n try {\n await waitForWsOpen(ws, timeout);\n } catch (err) {\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n throw err;\n }\n\n // 关闭状态标记(幂等保护)\n let closed = false;\n\n return {\n ws,\n queue,\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n\n // 若 ws 仍 OPEN/CLOSING,主动 close\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n\n // 等待 'close' 事件(超时 1000ms 强制 resolve,避免 server 端不回 close)\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, 1000);\n ws.once('close', () => {\n clearTimeout(timer);\n resolve();\n });\n });\n },\n };\n}\n","import type { Server } from 'node:http';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { PassThrough } 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 { loadPlugins } from './loadPlugins';\nimport { importWithCacheBust } from '../utils/importWithCacheBust';\nimport type { FaapiConfig } from '../config/configTypes';\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/** 路由源码目录(写死为 src,路由 .ts 文件位于 src/api/ 下) */\nconst PATTERNS = ['src/api/**/*.ts'];\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 'bodyLimit',\n 'logger',\n 'http2',\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 /** 排序后的路由清单 */\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 * 构建一个模拟请求直接走完整请求链路,不绑定端口。\n * 需要在 listen() 之前调用(server 未启动时)。\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 /** 产物目录 */\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 // 自定义业务配置(排除内置 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 logger: config?.logger,\n bodyLimit: config?.bodyLimit,\n http2: config?.http2,\n });\n\n // 加载插件 + 应用 handler/upgrade 包装器\n const { handlerWrappers, upgradeWrappers } = await loadPlugins(config?.plugins, {\n rootDir,\n routes: sorted,\n getRoutes: () => sorted,\n server,\n config: pluginConfig,\n });\n applyPluginWrappers(server, handlerWrappers, upgradeWrappers);\n\n // 关闭状态标记(避免重复关闭)\n let closed = false;\n\n const app: AppBase = {\n server: null,\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 return new Promise<Server>((resolve) => {\n server.listen(actualPort, async () => {\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\n // 注册优雅关闭信号(仅当配置了 onClose)\n if (config?.lifecycle?.onClose) {\n const graceful = async (signal: string): Promise<void> => {\n console.log(`\\n- Received ${signal}, shutting down...`);\n await app.close();\n process.exit(0);\n };\n process.on('SIGTERM', () => void graceful('SIGTERM'));\n process.on('SIGINT', () => void graceful('SIGINT'));\n }\n\n // onReady 生命周期钩子\n if (config?.lifecycle?.onReady) {\n await config.lifecycle.onReady({ rootDir, routes: sorted, server });\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 const mockRes = {\n statusCode: 200,\n _headers: {} as Record<string, string>,\n _body: Buffer.alloc(0) as Buffer,\n setHeader(name: string, value: string) {\n this._headers[name.toLowerCase()] = value;\n },\n appendHeader(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 writeHead(status: number, headers?: Record<string, string>) {\n this.statusCode = status;\n if (headers) {\n Object.assign(this._headers, headers);\n }\n },\n end(data?: string | Buffer) {\n const buf: Buffer = Buffer.isBuffer(data) ? data : Buffer.from(data ?? '');\n this._body = buf;\n resolve({\n status: this.statusCode,\n headers: new Headers(this._headers as Record<string, string>),\n body: this.parseBody(),\n });\n },\n parseBody(): unknown {\n try {\n return JSON.parse(this._body.toString());\n } catch {\n return this._body.toString();\n }\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: PassThrough & {\n method?: string;\n url?: string;\n headers?: Record<string, string | undefined>;\n socket?: { remoteAddress?: string };\n } = new PassThrough({\n read() {\n this.push(null);\n },\n });\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 if (body !== undefined) {\n mockReq.push(JSON.stringify(body));\n }\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 // 停止接受新连接(HTTP/2 server 支持,HTTP/1.1 无此方法)\n const s = server as unknown as Record<string, unknown>;\n if (typeof s.closeIdleConnections === 'function') {\n (s.closeIdleConnections as () => void)();\n }\n if (typeof s.closeAllConnections === 'function') {\n (s.closeAllConnections as () => void)();\n }\n\n if (config?.lifecycle?.onClose) {\n await config.lifecycle.onClose({ rootDir, routes: sorted, server });\n }\n\n return new Promise<void>((resolve) => {\n server.close((err) => {\n if (err) console.error('Error closing server:', err);\n app.server = null;\n resolve();\n });\n });\n },\n };\n\n /** 更新路由引用(app + routesRef + 闭包变量) */\n const ctx: AppContext = {\n rootDir,\n dist,\n patterns: 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\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 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 type { FaapiMiddleware } from '../middleware/middlewareTypes';\nimport type { InjectorMap } from '../middleware/injectorTypes';\nimport {\n loadMiddlewaresFile,\n getCachedMiddlewares,\n setCachedMiddlewares,\n} from '../middleware/loadMiddlewares';\nimport type { LoadedMiddlewareBundle } from '../middleware/loadMiddlewares';\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 * 把源码 filePath(src/api/hello/handler.ts)转为产物路径(dist/api/hello/handler.js)\n *\n * 产物结构打平 src/ 前缀:去掉 `src/`,加 dist 前缀,.ts → .js。\n *\n * @param filePath 源码相对路径\n * @param dist 产物目录(dist 或 .faapi)\n */\nfunction 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 * 序列化路由清单为可写入 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 const dir = path.dirname(outputPath);\n await fs.promises.mkdir(dir, { recursive: true });\n\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 fs.promises.writeFile(outputPath, content, 'utf-8');\n}\n\n/**\n * 从序列化清单水合路由(加载中间件,还原 RouteRecord)\n *\n * start 时调用,对每条路由按 middlewarePaths 加载中间件文件。\n */\nexport async function hydrateRoutes(\n manifest: SerializedRouteManifest,\n): Promise<{ routes: RouteManifest; wsRoutes: WsRouteManifest }> {\n const hydrateRoute = async (serialized: SerializedRouteRecord): Promise<RouteRecord> => {\n const bundle = await loadMiddlewarePaths(serialized.middlewarePaths);\n return {\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 middlewares: bundle?.middlewares,\n injectors: bundle?.injectors,\n };\n };\n\n const hydrateWsRoute = async (serialized: SerializedWsRouteRecord): Promise<WsRouteRecord> => {\n const bundle = await loadMiddlewarePaths(serialized.middlewarePaths);\n return {\n urlPath: serialized.urlPath,\n filePath: serialized.filePath,\n paramNames: serialized.paramNames,\n isDynamic: serialized.isDynamic,\n isCatchAll: serialized.isCatchAll,\n middlewares: bundle?.middlewares,\n injectors: bundle?.injectors,\n };\n };\n\n const routes = await Promise.all(manifest.routes.map(hydrateRoute));\n const wsRoutes = await Promise.all(manifest.wsRoutes.map(hydrateWsRoute));\n return { routes, wsRoutes };\n}\n\n/**\n * 按路径列表加载并合并中间件(根在前,路由目录在后)\n *\n * 与 scanRoutes.findMergedMiddlewares 的合并逻辑一致:\n * - 中间件:父级在前,子级追加在后(洋葱模型内层)\n * - 注入器:子级覆盖父级同名\n */\nasync function loadMiddlewarePaths(\n middlewarePaths: string[],\n): Promise<{ middlewares: FaapiMiddleware[]; injectors: InjectorMap } | 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 bundle = await loadMiddlewaresFile(absMwPath);\n setCachedMiddlewares(absMwPath, bundle);\n }\n mergedMiddlewares.push(...bundle.middlewares);\n for (const [name, injector] of Object.entries(bundle.injectors)) {\n mergedInjectors[name] = injector;\n }\n }\n\n return { middlewares: mergedMiddlewares, injectors: mergedInjectors };\n}\n","import type { FaapiPlugin, PluginDeclaration } from '../config/pluginTypes';\nimport type { RequestHandler, UpgradeHandler } from '../config/pluginTypes';\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}\n\n/**\n * 加载并执行插件列表\n *\n * 遍历 config.plugins,动态 import 插件包,调用 setup(ctx)。\n * 插件在 server 创建后、listen 之前按声明顺序执行。\n *\n * 插件可通过 ctx.wrapHandler / ctx.wrapUpgradeHandler 注册包装函数,\n * 本函数收集后返回,由调用方在 listen 之前应用。\n *\n * @param declarations 插件声明列表\n * @param ctx 插件上下文(不含 wrap 能力,由本函数注入)\n * @returns 包装器收集结果\n */\nexport async function loadPlugins(\n declarations: PluginDeclaration[] | undefined,\n ctx: LoadPluginsContext,\n): Promise<PluginLoadResult> {\n const handlerWrappers: Array<(original: RequestHandler) => RequestHandler> = [];\n const upgradeWrappers: Array<(original: UpgradeHandler | undefined) => UpgradeHandler> = [];\n\n if (!declarations || declarations.length === 0) {\n return { handlerWrappers, upgradeWrappers };\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 const { specifier, options, enable } = resolveDeclaration(decl);\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 import(specifier);\n const plugin: FaapiPlugin = mod.default ?? mod;\n\n if (typeof plugin.setup !== 'function') {\n console.warn(`! Plugin ${specifier} has no setup function, skipping`);\n continue;\n }\n\n await plugin.setup({ ...fullCtx, options });\n console.log(`- Plugin loaded: ${plugin.name ?? specifier}`);\n } catch (err) {\n console.warn(\n `! Failed to load plugin ${specifier}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n return { handlerWrappers, upgradeWrappers };\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 // 不应该到这里\n throw new Error(`Invalid plugin declaration: ${JSON.stringify(decl)}`);\n}\n","import type { AppBase, CreateAppOptions } from './createAppCore';\nimport { createAppBase } from './createAppCore';\nimport { scanRoutes } from '../router/scanRoutes';\nimport { sortRoutes } from '../router/sortRoutes';\nimport { generateSchemaFiles } from './generateSchemaFiles';\nimport { invalidateMiddlewareCache } from '../middleware/loadMiddlewares';\nimport { invalidateProgramCache } from '../ast/createProgram';\nimport { invalidateSchemaCache } from '../validator/validateInput';\nimport { setLoadTimestamp } from '../utils/importWithCacheBust';\n\n/** dev 应用接口(AppBase + reloadRoutes 热替换) */\nexport interface DevApp extends AppBase {\n /** 重新水合路由清单 + 清 schema 缓存 + 更新 server 路由引用(dev 热替换用) */\n reloadRoutes(): 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 // 重新扫描路由 + 重新生成 schema\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 await generateSchemaFiles(sorted, ctx.rootDir, ctx.dist);\n // 更新 app 和 server 路由引用\n ctx.updateRoutes(sorted, reScanned.wsRoutes);\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","ts","ts","path","ts","path","fs","path","path","url","headers","body","path","fs","path","fs","path","path","fs","path","fs","unionInner","path","fs","path","Readable","path","path","params","text","path","body","path","Readable","path","DEFAULT_BODY_LIMIT","cors","helmet","logger","path","fs","WebSocket","msg","fs","path","fs","path","path","fs"]}
|