@faapi/faapi 5.2.0 → 5.4.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/index.d.ts CHANGED
@@ -1368,6 +1368,17 @@ interface InjectOptions {
1368
1368
  path?: string;
1369
1369
  headers?: Record<string, string>;
1370
1370
  query?: Record<string, string>;
1371
+ /**
1372
+ * 请求体,语义按类型区分(与 fastify inject 约定一致):
1373
+ * - `string` / `Buffer` / `Uint8Array`:原样透传不二次编码(默认 content-type
1374
+ * 分别为 `text/plain` / `application/octet-stream`)
1375
+ * - 其他值(对象/数组等):`JSON.stringify` 后发送,默认 content-type `application/json`
1376
+ *
1377
+ * 调用方显式传入的 `content-type` 头优先于默认值(string body +
1378
+ * `application/x-www-form-urlencoded` 可直接测 form 表单路由)。
1379
+ * 注意不要把 `JSON.stringify` 的结果当对象传——那会作为原始文本再被服务端
1380
+ * JSON 解析一次,得到字符串而非对象。
1381
+ */
1371
1382
  body?: unknown;
1372
1383
  }
1373
1384
  interface InjectResponse {
package/dist/index.js CHANGED
@@ -1898,8 +1898,8 @@ function resolveExport(module, exportName) {
1898
1898
  // src/utils/importWithCacheBust.ts
1899
1899
  import { pathToFileURL } from "url";
1900
1900
  var loadTs;
1901
- function setLoadTimestamp(ts10) {
1902
- loadTs = ts10;
1901
+ function setLoadTimestamp(ts11) {
1902
+ loadTs = ts11;
1903
1903
  }
1904
1904
  function getVitestImportActual() {
1905
1905
  const vi = globalThis.vi;
@@ -1933,6 +1933,7 @@ import fs5 from "fs";
1933
1933
  import fg from "fast-glob";
1934
1934
 
1935
1935
  // src/cli/aliasPlugin.ts
1936
+ import ts7 from "typescript";
1936
1937
  import path5 from "path";
1937
1938
  import fs4 from "fs";
1938
1939
 
@@ -2063,7 +2064,14 @@ function resolveRelativeSpecifier(importer, specifier) {
2063
2064
  const importerDir = path5.dirname(importer);
2064
2065
  const base = path5.resolve(importerDir, specifier);
2065
2066
  if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
2066
- return fs4.existsSync(base) ? base : null;
2067
+ if (fs4.existsSync(base)) return base;
2068
+ if (base.endsWith(".js")) {
2069
+ for (const ext of [".ts", ".tsx", ".jsx"]) {
2070
+ const file = base.slice(0, -3) + ext;
2071
+ if (fs4.existsSync(file)) return file;
2072
+ }
2073
+ }
2074
+ return null;
2067
2075
  }
2068
2076
  if (/\.(ts|tsx|jsx)$/.test(specifier)) {
2069
2077
  return fs4.existsSync(base) ? base : null;
@@ -2079,8 +2087,18 @@ function resolveRelativeSpecifier(importer, specifier) {
2079
2087
  return null;
2080
2088
  }
2081
2089
  function createAliasPlugin(config, options) {
2082
- const SPEC_RE2 = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
2083
2090
  const appDirAbs = options?.rootDir ? toRealPath(path5.resolve(options.rootDir, APP_DIR)) : null;
2091
+ const probeCandidateFile = (candidate) => {
2092
+ for (const ext of SOURCE_EXTS) {
2093
+ const file = candidate + ext;
2094
+ if (fs4.existsSync(file)) return file;
2095
+ }
2096
+ for (const indexExt of INDEX_EXTS) {
2097
+ const file = candidate + indexExt;
2098
+ if (fs4.existsSync(file)) return file;
2099
+ }
2100
+ return null;
2101
+ };
2084
2102
  return {
2085
2103
  name: "faapi-alias",
2086
2104
  setup(build) {
@@ -2093,64 +2111,103 @@ function createAliasPlugin(config, options) {
2093
2111
  }
2094
2112
  const importer = args.path;
2095
2113
  const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;
2096
- let modified = false;
2097
- const newSource = source.replace(SPEC_RE2, (full, prefix, quote, specifier) => {
2114
+ const sourceFile = ts7.createSourceFile(importer, source, ts7.ScriptTarget.Latest, false);
2115
+ const literals = [];
2116
+ const visit = (node) => {
2117
+ if (ts7.isImportDeclaration(node)) {
2118
+ if (!node.importClause?.isTypeOnly && node.moduleSpecifier) {
2119
+ if (ts7.isStringLiteral(node.moduleSpecifier)) literals.push(node.moduleSpecifier);
2120
+ }
2121
+ } else if (ts7.isExportDeclaration(node)) {
2122
+ if (!node.isTypeOnly && node.moduleSpecifier) {
2123
+ if (ts7.isStringLiteral(node.moduleSpecifier)) literals.push(node.moduleSpecifier);
2124
+ }
2125
+ } else if (ts7.isCallExpression(node) && node.expression.kind === ts7.SyntaxKind.ImportKeyword && node.arguments[0] && ts7.isStringLiteral(node.arguments[0])) {
2126
+ literals.push(node.arguments[0]);
2127
+ }
2128
+ ts7.forEachChild(node, visit);
2129
+ };
2130
+ ts7.forEachChild(sourceFile, visit);
2131
+ const replacements = [];
2132
+ const unresolvable = [];
2133
+ for (const lit of literals) {
2134
+ const specifier = lit.text;
2098
2135
  if (specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
2099
- return full;
2136
+ continue;
2100
2137
  }
2101
2138
  if (specifier.startsWith("./") || specifier.startsWith("../")) {
2102
2139
  const resolved = resolveRelativeSpecifier(importer, specifier);
2103
- if (resolved) {
2104
- if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
2105
- return full;
2106
- }
2107
- if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
2108
- modified = true;
2109
- return `${prefix}${quote}${toProdImportFromImporter(
2110
- importer,
2111
- options.rootDir,
2112
- toStrippedProdImportPath(resolved, options.rootDir)
2113
- )}${quote}`;
2114
- }
2115
- modified = true;
2116
- return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;
2140
+ if (!resolved) {
2141
+ unresolvable.push(lit);
2142
+ continue;
2117
2143
  }
2118
- return full;
2119
- }
2120
- const candidates = resolveAlias(specifier, config);
2121
- for (const candidate of candidates) {
2122
- for (const ext of SOURCE_EXTS) {
2123
- const file = candidate + ext;
2124
- if (fs4.existsSync(file)) {
2125
- modified = true;
2126
- if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
2127
- return `${prefix}${quote}${toProdImportFromImporter(
2128
- importer,
2129
- options.rootDir,
2130
- toStrippedProdImportPath(file, options.rootDir)
2131
- )}${quote}`;
2132
- }
2133
- return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
2134
- }
2144
+ if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) continue;
2145
+ let prodPath;
2146
+ if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
2147
+ prodPath = toProdImportFromImporter(
2148
+ importer,
2149
+ options.rootDir,
2150
+ toStrippedProdImportPath(resolved, options.rootDir)
2151
+ );
2152
+ } else {
2153
+ prodPath = toProdImportPath(resolved, importer);
2135
2154
  }
2136
- for (const indexExt of INDEX_EXTS) {
2137
- const file = candidate + indexExt;
2138
- if (fs4.existsSync(file)) {
2139
- modified = true;
2140
- if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
2141
- return `${prefix}${quote}${toProdImportFromImporter(
2142
- importer,
2143
- options.rootDir,
2144
- toStrippedProdImportPath(file, options.rootDir)
2145
- )}${quote}`;
2146
- }
2147
- return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
2148
- }
2155
+ const quote = source[lit.getStart(sourceFile)];
2156
+ replacements.push({
2157
+ start: lit.getStart(sourceFile),
2158
+ end: lit.getEnd(),
2159
+ text: quote + prodPath + quote
2160
+ });
2161
+ continue;
2162
+ }
2163
+ for (const candidate of resolveAlias(specifier, config)) {
2164
+ const file = probeCandidateFile(candidate);
2165
+ if (!file) continue;
2166
+ let prodPath;
2167
+ if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
2168
+ prodPath = toProdImportFromImporter(
2169
+ importer,
2170
+ options.rootDir,
2171
+ toStrippedProdImportPath(file, options.rootDir)
2172
+ );
2173
+ } else {
2174
+ prodPath = toProdImportPath(file, importer);
2149
2175
  }
2176
+ const quote = source[lit.getStart(sourceFile)];
2177
+ replacements.push({
2178
+ start: lit.getStart(sourceFile),
2179
+ end: lit.getEnd(),
2180
+ text: quote + prodPath + quote
2181
+ });
2182
+ break;
2150
2183
  }
2151
- return full;
2152
- });
2153
- if (!modified) return void 0;
2184
+ }
2185
+ if (unresolvable.length > 0) {
2186
+ const lineStarts = sourceFile.getLineStarts();
2187
+ return {
2188
+ errors: unresolvable.map((lit) => {
2189
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(
2190
+ lit.getStart(sourceFile)
2191
+ );
2192
+ const lineEnd = line + 1 < lineStarts.length ? lineStarts[line + 1] : sourceFile.getEnd();
2193
+ return {
2194
+ text: `\u65E0\u6CD5\u89E3\u6790\u7684\u76F8\u5BF9\u5BFC\u5165 "${lit.text}"\u2014\u2014\u9879\u76EE\u5185\u4E0D\u5B58\u5728\u5BF9\u5E94\u6E90\u6587\u4EF6\uFF08faapi \u9010\u6587\u4EF6\u7F16\u8BD1\uFF08bundle: false\uFF09\u4E0D\u89E3\u6790\u4F9D\u8D56\uFF0C\u8BE5\u5BFC\u5165\u4F1A\u539F\u6837\u8FDB\u5165\u4EA7\u7269\uFF0C\u751F\u4EA7 Node ESM \u8FD0\u884C\u65F6\u62A5 ERR_MODULE_NOT_FOUND\uFF09\u3002\u8BF7\u4FEE\u6B63\u8DEF\u5F84\u6216\u8865\u5168\u540E\u7F00`,
2195
+ location: {
2196
+ file: importer,
2197
+ line: line + 1,
2198
+ column: character,
2199
+ lineText: source.slice(lineStarts[line], lineEnd).replace(/\r?\n$/, "")
2200
+ }
2201
+ };
2202
+ })
2203
+ };
2204
+ }
2205
+ if (replacements.length === 0) return void 0;
2206
+ replacements.sort((a, b) => b.start - a.start);
2207
+ let newSource = source;
2208
+ for (const r of replacements) {
2209
+ newSource = newSource.slice(0, r.start) + r.text + newSource.slice(r.end);
2210
+ }
2154
2211
  return { contents: newSource, loader: "default" };
2155
2212
  });
2156
2213
  }
@@ -2537,17 +2594,17 @@ import path11 from "path";
2537
2594
  import { existsSync } from "fs";
2538
2595
 
2539
2596
  // src/ast/extractToolMetadata.ts
2540
- import ts8 from "typescript";
2597
+ import ts9 from "typescript";
2541
2598
 
2542
2599
  // src/ast/jsDocMetadata.ts
2543
- import ts7 from "typescript";
2600
+ import ts8 from "typescript";
2544
2601
  function hasExportModifier(node) {
2545
- if (!ts7.canHaveModifiers(node)) return false;
2546
- const modifiers = ts7.getModifiers(node);
2547
- return !!modifiers?.some((m) => m.kind === ts7.SyntaxKind.ExportKeyword);
2602
+ if (!ts8.canHaveModifiers(node)) return false;
2603
+ const modifiers = ts8.getModifiers(node);
2604
+ return !!modifiers?.some((m) => m.kind === ts8.SyntaxKind.ExportKeyword);
2548
2605
  }
2549
2606
  function getJSDocFromNode(node) {
2550
- const apiDocs = ts7.getJSDocCommentsAndTags(node).filter((entry) => ts7.isJSDoc(entry));
2607
+ const apiDocs = ts8.getJSDocCommentsAndTags(node).filter((entry) => ts8.isJSDoc(entry));
2551
2608
  if (apiDocs.length > 0) return apiDocs[0];
2552
2609
  const directDocs = node.jsDoc;
2553
2610
  if (directDocs && directDocs.length > 0) return directDocs[0];
@@ -2593,18 +2650,18 @@ function extractToolMetadata(program, filePath, functionName, pathMeta) {
2593
2650
  }
2594
2651
  function findExportedFunction(sourceFile, functionName) {
2595
2652
  let result = null;
2596
- ts8.forEachChild(sourceFile, (node) => {
2653
+ ts9.forEachChild(sourceFile, (node) => {
2597
2654
  if (result) return;
2598
- if (ts8.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === functionName) {
2655
+ if (ts9.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === functionName) {
2599
2656
  result = { fn: node, jsDocOwner: node };
2600
2657
  return;
2601
2658
  }
2602
- if (ts8.isVariableStatement(node) && hasExportModifier(node)) {
2659
+ if (ts9.isVariableStatement(node) && hasExportModifier(node)) {
2603
2660
  for (const decl of node.declarationList.declarations) {
2604
2661
  if (result) break;
2605
- const nameText = ts8.isIdentifier(decl.name) ? decl.name.text : decl.name.getText(sourceFile);
2662
+ const nameText = ts9.isIdentifier(decl.name) ? decl.name.text : decl.name.getText(sourceFile);
2606
2663
  if (nameText !== functionName || !decl.initializer) continue;
2607
- if (ts8.isArrowFunction(decl.initializer) || ts8.isFunctionExpression(decl.initializer)) {
2664
+ if (ts9.isArrowFunction(decl.initializer) || ts9.isFunctionExpression(decl.initializer)) {
2608
2665
  result = { fn: decl.initializer, jsDocOwner: node };
2609
2666
  }
2610
2667
  }
@@ -2616,7 +2673,7 @@ function getFirstParamTypeName(fn, sourceFile) {
2616
2673
  const firstParam = fn.parameters[0];
2617
2674
  if (!firstParam) return void 0;
2618
2675
  if (!firstParam.type) return void 0;
2619
- if (!ts8.isTypeReferenceNode(firstParam.type)) return void 0;
2676
+ if (!ts9.isTypeReferenceNode(firstParam.type)) return void 0;
2620
2677
  return firstParam.type.typeName.getText(sourceFile);
2621
2678
  }
2622
2679
 
@@ -5088,7 +5145,7 @@ async function hydrateRoutes(manifest) {
5088
5145
  import path17 from "path";
5089
5146
 
5090
5147
  // src/ast/extractAgentMetadata.ts
5091
- import ts9 from "typescript";
5148
+ import ts10 from "typescript";
5092
5149
  init_resolveTypeNode();
5093
5150
  function extractAgentMetadata(program, filePath, pathMeta) {
5094
5151
  const sourceFile = program.getSourceFile(filePath);
@@ -5132,23 +5189,23 @@ function extractAgentMetadata(program, filePath, pathMeta) {
5132
5189
  }
5133
5190
  function findConfigExport(sourceFile) {
5134
5191
  let result = null;
5135
- ts9.forEachChild(sourceFile, (node) => {
5192
+ ts10.forEachChild(sourceFile, (node) => {
5136
5193
  if (result) return;
5137
- if (ts9.isVariableStatement(node) && hasExportModifier(node)) {
5194
+ if (ts10.isVariableStatement(node) && hasExportModifier(node)) {
5138
5195
  for (const decl of node.declarationList.declarations) {
5139
5196
  if (result) break;
5140
- const nameText = ts9.isIdentifier(decl.name) ? decl.name.text : "";
5197
+ const nameText = ts10.isIdentifier(decl.name) ? decl.name.text : "";
5141
5198
  if (nameText !== "config" || !decl.initializer) continue;
5142
- if (ts9.isObjectLiteralExpression(decl.initializer)) {
5199
+ if (ts10.isObjectLiteralExpression(decl.initializer)) {
5143
5200
  result = { jsDocOwner: node, objectLiteral: decl.initializer };
5144
- } else if (ts9.isArrowFunction(decl.initializer)) {
5201
+ } else if (ts10.isArrowFunction(decl.initializer)) {
5145
5202
  result = { jsDocOwner: node, objectLiteral: getReturnObjectLiteral(decl.initializer) };
5146
5203
  } else {
5147
5204
  result = { jsDocOwner: node, objectLiteral: null };
5148
5205
  }
5149
5206
  }
5150
5207
  }
5151
- if (ts9.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === "config") {
5208
+ if (ts10.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === "config") {
5152
5209
  const returnObj = getReturnObjectLiteral(node);
5153
5210
  result = { jsDocOwner: node, objectLiteral: returnObj };
5154
5211
  }
@@ -5158,12 +5215,12 @@ function findConfigExport(sourceFile) {
5158
5215
  function getReturnObjectLiteral(fn) {
5159
5216
  const body = fn.body;
5160
5217
  if (!body) return null;
5161
- if (ts9.isObjectLiteralExpression(body)) {
5218
+ if (ts10.isObjectLiteralExpression(body)) {
5162
5219
  return body;
5163
5220
  }
5164
- if (ts9.isBlock(body)) {
5221
+ if (ts10.isBlock(body)) {
5165
5222
  for (const stmt of body.statements) {
5166
- if (ts9.isReturnStatement(stmt) && stmt.expression && ts9.isObjectLiteralExpression(stmt.expression)) {
5223
+ if (ts10.isReturnStatement(stmt) && stmt.expression && ts10.isObjectLiteralExpression(stmt.expression)) {
5167
5224
  return stmt.expression;
5168
5225
  }
5169
5226
  }
@@ -5177,8 +5234,8 @@ function extractConfigFields(objLit, sourceFile) {
5177
5234
  let model;
5178
5235
  let maxTurns;
5179
5236
  for (const prop of objLit.properties) {
5180
- if (ts9.isSpreadAssignment(prop)) continue;
5181
- if (!ts9.isPropertyAssignment(prop)) {
5237
+ if (ts10.isSpreadAssignment(prop)) continue;
5238
+ if (!ts10.isPropertyAssignment(prop)) {
5182
5239
  throw SchemaExtractionError.at(
5183
5240
  prop,
5184
5241
  "config",
@@ -5226,13 +5283,13 @@ function extractConfigFields(objLit, sourceFile) {
5226
5283
  return { systemPrompt, tools, agents, model, maxTurns };
5227
5284
  }
5228
5285
  function getPropertyName(name) {
5229
- if (ts9.isIdentifier(name)) return name.text;
5230
- if (ts9.isStringLiteral(name)) return name.text;
5286
+ if (ts10.isIdentifier(name)) return name.text;
5287
+ if (ts10.isStringLiteral(name)) return name.text;
5231
5288
  return null;
5232
5289
  }
5233
5290
  function extractStringValue(expr) {
5234
5291
  if (isStringLikeLiteral(expr)) return expr.text;
5235
- if (ts9.isBinaryExpression(expr) && expr.operatorToken.kind === ts9.SyntaxKind.PlusToken) {
5292
+ if (ts10.isBinaryExpression(expr) && expr.operatorToken.kind === ts10.SyntaxKind.PlusToken) {
5236
5293
  const left = extractStringValue(expr.left);
5237
5294
  if (left === void 0) return void 0;
5238
5295
  const right = extractStringValue(expr.right);
@@ -5278,17 +5335,17 @@ function requireNumberValue(prop, fieldName, sourceFile) {
5278
5335
  return value;
5279
5336
  }
5280
5337
  function extractNumberValue(expr) {
5281
- if (ts9.isNumericLiteral(expr)) {
5338
+ if (ts10.isNumericLiteral(expr)) {
5282
5339
  const num = Number(expr.text);
5283
5340
  return Number.isNaN(num) ? void 0 : num;
5284
5341
  }
5285
5342
  return void 0;
5286
5343
  }
5287
5344
  function isStringLikeLiteral(node) {
5288
- return ts9.isStringLiteral(node) || ts9.isNoSubstitutionTemplateLiteral(node);
5345
+ return ts10.isStringLiteral(node) || ts10.isNoSubstitutionTemplateLiteral(node);
5289
5346
  }
5290
5347
  function extractStringArrayValue(expr) {
5291
- if (!ts9.isArrayLiteralExpression(expr)) return void 0;
5348
+ if (!ts10.isArrayLiteralExpression(expr)) return void 0;
5292
5349
  const values = [];
5293
5350
  for (const element of expr.elements) {
5294
5351
  const value = extractStringValue(element);
@@ -5833,13 +5890,28 @@ async function createAppBase(options) {
5833
5890
  reject(new Error("No request handler found"));
5834
5891
  return;
5835
5892
  }
5836
- const mockReq = Readable3.from(body !== void 0 ? [Buffer.from(JSON.stringify(body))] : []);
5893
+ let payload;
5894
+ let defaultContentType;
5895
+ if (body !== void 0) {
5896
+ if (typeof body === "string") {
5897
+ payload = Buffer.from(body, "utf-8");
5898
+ defaultContentType = "text/plain";
5899
+ } else if (body instanceof Uint8Array) {
5900
+ payload = Buffer.from(body);
5901
+ defaultContentType = "application/octet-stream";
5902
+ } else {
5903
+ payload = Buffer.from(JSON.stringify(body));
5904
+ defaultContentType = "application/json";
5905
+ }
5906
+ }
5907
+ const mockReq = Readable3.from(payload !== void 0 ? [payload] : []);
5837
5908
  mockReq.method = method;
5838
5909
  mockReq.url = `${reqPath}${queryStr}`;
5910
+ const hasCallerContentType = "content-type" in reqHeaders || "Content-Type" in reqHeaders;
5839
5911
  mockReq.headers = {
5840
5912
  ...reqHeaders,
5841
5913
  host: "localhost",
5842
- "content-type": body !== void 0 ? "application/json" : void 0
5914
+ ...payload !== void 0 && !hasCallerContentType ? { "content-type": defaultContentType } : {}
5843
5915
  };
5844
5916
  mockReq.socket = { remoteAddress: "127.0.0.1" };
5845
5917
  handler(