@faapi/faapi 3.2.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -67,6 +67,45 @@ function createProgram(filePath) {
67
67
  if (cached) {
68
68
  return cached;
69
69
  }
70
+ const program = buildProgram([filePath], findTsConfig(filePath));
71
+ programCache.set(filePath, program);
72
+ return program;
73
+ }
74
+ function createPrograms(filePaths) {
75
+ const unique = [...new Set(filePaths)];
76
+ const result = /* @__PURE__ */ new Map();
77
+ const groups = /* @__PURE__ */ new Map();
78
+ const noTsconfigFiles = [];
79
+ for (const filePath of unique) {
80
+ const tsconfigPath = findTsConfig(filePath);
81
+ if (!tsconfigPath) {
82
+ noTsconfigFiles.push(filePath);
83
+ continue;
84
+ }
85
+ const group = groups.get(tsconfigPath);
86
+ if (group) {
87
+ group.files.push(filePath);
88
+ } else {
89
+ groups.set(tsconfigPath, { tsconfigPath, files: [filePath] });
90
+ }
91
+ }
92
+ for (const { tsconfigPath, files } of groups.values()) {
93
+ const cacheKey = `shared::${tsconfigPath}::${[...files].sort().join("|")}`;
94
+ let program = programCache.get(cacheKey);
95
+ if (!program) {
96
+ program = buildProgram(files, tsconfigPath);
97
+ programCache.set(cacheKey, program);
98
+ }
99
+ for (const filePath of files) {
100
+ result.set(filePath, program);
101
+ }
102
+ }
103
+ for (const filePath of noTsconfigFiles) {
104
+ result.set(filePath, createProgram(filePath));
105
+ }
106
+ return result;
107
+ }
108
+ function buildProgram(entryFiles, tsconfigPath) {
70
109
  const options = {
71
110
  strict: true,
72
111
  target: ts.ScriptTarget.ES2022,
@@ -75,8 +114,7 @@ function createProgram(filePath) {
75
114
  skipLibCheck: true,
76
115
  noEmit: true
77
116
  };
78
- let rootNames = [filePath];
79
- const tsconfigPath = findTsConfig(filePath);
117
+ const rootNames = [...entryFiles];
80
118
  if (tsconfigPath) {
81
119
  const tsOptions = parseTsConfig(tsconfigPath);
82
120
  if (tsOptions.module !== void 0) {
@@ -86,16 +124,14 @@ function createProgram(filePath) {
86
124
  options.moduleResolution = tsOptions.moduleResolution;
87
125
  }
88
126
  if (tsOptions.fileNames.length > 0) {
89
- if (!tsOptions.fileNames.includes(filePath)) {
90
- rootNames = [filePath, ...tsOptions.fileNames];
91
- } else {
92
- rootNames = tsOptions.fileNames;
127
+ for (const fileName of tsOptions.fileNames) {
128
+ if (!rootNames.includes(fileName)) {
129
+ rootNames.push(fileName);
130
+ }
93
131
  }
94
132
  }
95
133
  }
96
- const program = ts.createProgram(rootNames, options);
97
- programCache.set(filePath, program);
98
- return program;
134
+ return ts.createProgram(rootNames, options);
99
135
  }
100
136
  var programCache, tsConfigCache;
101
137
  var init_createProgram = __esm({
@@ -1004,12 +1040,11 @@ function collectRouteSchemaSources(routes, rootDir) {
1004
1040
  }
1005
1041
  entry.methods.add(route.method);
1006
1042
  }
1007
- const programByFile = /* @__PURE__ */ new Map();
1043
+ const programByFile = createPrograms([...methodsByFile.keys()]);
1008
1044
  const allTypesByFile = /* @__PURE__ */ new Map();
1009
1045
  const mergedAllTypes = /* @__PURE__ */ new Map();
1010
1046
  for (const filePath of methodsByFile.keys()) {
1011
- const program = createProgram(filePath);
1012
- programByFile.set(filePath, program);
1047
+ const program = programByFile.get(filePath);
1013
1048
  const allTypes = extractAllTypes(program, filePath);
1014
1049
  allTypesByFile.set(filePath, allTypes);
1015
1050
  for (const [name, info] of allTypes) {
@@ -1877,6 +1912,7 @@ var state = createDevOnDemandState();
1877
1912
  function clearCompiledFiles() {
1878
1913
  state.compiledFiles.clear();
1879
1914
  state.inFlightCompilations.clear();
1915
+ sourcePathCache.clear();
1880
1916
  }
1881
1917
  async function ensureCompiled(sourceAbsPath, rootDir, dist) {
1882
1918
  const inFlight = state.inFlightCompilations.get(sourceAbsPath);
@@ -1973,7 +2009,10 @@ async function deleteSchemaFiles(routes, rootDir, dist) {
1973
2009
  }
1974
2010
  }
1975
2011
  }
2012
+ var sourcePathCache = /* @__PURE__ */ new Map();
1976
2013
  function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
2014
+ const cached = sourcePathCache.get(prodAbsPath);
2015
+ if (cached) return cached;
1977
2016
  const rel = path7.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
1978
2017
  let relWithoutDist = rel;
1979
2018
  if (relWithoutDist.startsWith(`${dist}/`)) {
@@ -1982,8 +2021,14 @@ function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
1982
2021
  const srcRel = `src/${relWithoutDist}`;
1983
2022
  const tsRel = srcRel.replace(/\.js$/, ".ts");
1984
2023
  const tsAbs = path7.resolve(rootDir, tsRel);
1985
- if (fs6.existsSync(tsAbs)) return tsAbs;
1986
- return path7.resolve(rootDir, srcRel);
2024
+ let result;
2025
+ if (fs6.existsSync(tsAbs)) {
2026
+ result = tsAbs;
2027
+ } else {
2028
+ result = path7.resolve(rootDir, srcRel);
2029
+ }
2030
+ sourcePathCache.set(prodAbsPath, result);
2031
+ return result;
1987
2032
  }
1988
2033
  function isDevOnDemandEnabled() {
1989
2034
  return state.enabled;
@@ -2224,15 +2269,16 @@ function collectToolSchemaSources(tools, rootDir) {
2224
2269
  }
2225
2270
  list.push(tool);
2226
2271
  }
2272
+ const programByFile = createPrograms([...toolsByFile.keys()]);
2227
2273
  const allTypesByFile = /* @__PURE__ */ new Map();
2228
2274
  for (const filePath of toolsByFile.keys()) {
2229
- const program = createProgram(filePath);
2275
+ const program = programByFile.get(filePath);
2230
2276
  const allTypes = extractAllTypes(program, filePath);
2231
2277
  allTypesByFile.set(filePath, allTypes);
2232
2278
  }
2233
2279
  const sources = [];
2234
2280
  for (const [filePath, fileTools] of toolsByFile) {
2235
- const program = createProgram(filePath);
2281
+ const program = programByFile.get(filePath);
2236
2282
  for (const tool of fileTools) {
2237
2283
  const inputTypeName = tool.inputTypeName;
2238
2284
  const typeInfo = extractTypeInfo(program, filePath, inputTypeName);
@@ -2285,9 +2331,10 @@ async function maybeGenerateHelpers(allSourceCode, distDir) {
2285
2331
  }
2286
2332
  async function generateToolArtifacts(tools, rootDir, dist, options) {
2287
2333
  const metadata = [];
2334
+ const programByFile = createPrograms(tools.map((m) => path8.resolve(rootDir, m.filePath)));
2288
2335
  for (const manifest of tools) {
2289
2336
  const absPath = path8.resolve(rootDir, manifest.filePath);
2290
- const program = createProgram(absPath);
2337
+ const program = programByFile.get(absPath);
2291
2338
  const result = extractToolMetadata(program, absPath, manifest.functionName, {
2292
2339
  name: manifest.name,
2293
2340
  filePath: manifest.filePath
@@ -2353,11 +2400,14 @@ function getDist() {
2353
2400
  }
2354
2401
  return process.env.FAAPI_DIST ?? "dist";
2355
2402
  }
2403
+ function getToolSchemaPath(tool, rootDir) {
2404
+ const dist = getDist();
2405
+ return getRuntimeToolSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());
2406
+ }
2356
2407
  async function loadToolSchema(tool, rootDir) {
2357
2408
  if (!tool.inputTypeName) return void 0;
2358
2409
  const schemaName = `${tool.inputTypeName}Schema`;
2359
- const dist = getDist();
2360
- const zodPath = getRuntimeToolSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());
2410
+ const zodPath = getToolSchemaPath(tool, rootDir);
2361
2411
  if (!existsSync2(zodPath)) return void 0;
2362
2412
  try {
2363
2413
  const mod = await importWithCacheBust(zodPath, isDevOnDemandEnabled());
@@ -2701,7 +2751,7 @@ var PayloadTooLargeError = class extends FaapiError {
2701
2751
  };
2702
2752
 
2703
2753
  // src/cli/createAppCore.ts
2704
- import fs16 from "fs";
2754
+ import fs15 from "fs";
2705
2755
  import path15 from "path";
2706
2756
  import { PassThrough, Readable as Readable3 } from "stream";
2707
2757
 
@@ -2758,15 +2808,50 @@ import { Readable as Readable2 } from "stream";
2758
2808
  import path12 from "path";
2759
2809
 
2760
2810
  // src/router/matchRoute.ts
2761
- function matchRoute(routes, method, path19) {
2811
+ var httpIndexCache = /* @__PURE__ */ new WeakMap();
2812
+ var wsIndexCache = /* @__PURE__ */ new WeakMap();
2813
+ function getHttpIndex(routes) {
2814
+ let index = httpIndexCache.get(routes);
2815
+ if (index) return index;
2816
+ index = { static: /* @__PURE__ */ new Map(), methodsByStaticPath: /* @__PURE__ */ new Map(), dynamics: [] };
2762
2817
  for (const route of routes) {
2763
- if (route.method !== method) {
2764
- continue;
2765
- }
2766
- if (!route.isDynamic) {
2767
- if (route.urlPath === path19) {
2768
- return { route, params: {} };
2818
+ if (route.isDynamic) {
2819
+ index.dynamics.push(route);
2820
+ } else {
2821
+ index.static.set(`${route.method}|${route.urlPath}`, route);
2822
+ let methods = index.methodsByStaticPath.get(route.urlPath);
2823
+ if (!methods) {
2824
+ methods = /* @__PURE__ */ new Set();
2825
+ index.methodsByStaticPath.set(route.urlPath, methods);
2769
2826
  }
2827
+ methods.add(route.method);
2828
+ }
2829
+ }
2830
+ httpIndexCache.set(routes, index);
2831
+ return index;
2832
+ }
2833
+ function getWsIndex(routes) {
2834
+ let index = wsIndexCache.get(routes);
2835
+ if (index) return index;
2836
+ index = { static: /* @__PURE__ */ new Map(), dynamics: [] };
2837
+ for (const route of routes) {
2838
+ if (route.isDynamic) {
2839
+ index.dynamics.push(route);
2840
+ } else {
2841
+ index.static.set(route.urlPath, route);
2842
+ }
2843
+ }
2844
+ wsIndexCache.set(routes, index);
2845
+ return index;
2846
+ }
2847
+ function matchRoute(routes, method, path19) {
2848
+ const index = getHttpIndex(routes);
2849
+ const staticHit = index.static.get(`${method}|${path19}`);
2850
+ if (staticHit) {
2851
+ return { route: staticHit, params: {} };
2852
+ }
2853
+ for (const route of index.dynamics) {
2854
+ if (route.method !== method) {
2770
2855
  continue;
2771
2856
  }
2772
2857
  const params = matchDynamicPath(route.urlPath, path19, route.paramNames, route.isCatchAll);
@@ -2777,13 +2862,12 @@ function matchRoute(routes, method, path19) {
2777
2862
  return null;
2778
2863
  }
2779
2864
  function matchWsRoute(wsRoutes, path19) {
2780
- for (const route of wsRoutes) {
2781
- if (!route.isDynamic) {
2782
- if (route.urlPath === path19) {
2783
- return { route, params: {} };
2784
- }
2785
- continue;
2786
- }
2865
+ const index = getWsIndex(wsRoutes);
2866
+ const staticHit = index.static.get(path19);
2867
+ if (staticHit) {
2868
+ return { route: staticHit, params: {} };
2869
+ }
2870
+ for (const route of index.dynamics) {
2787
2871
  const params = matchDynamicPath(route.urlPath, path19, route.paramNames, route.isCatchAll);
2788
2872
  if (params !== null) {
2789
2873
  return { route, params };
@@ -2791,6 +2875,23 @@ function matchWsRoute(wsRoutes, path19) {
2791
2875
  }
2792
2876
  return null;
2793
2877
  }
2878
+ function findAllowedMethods(routes, path19) {
2879
+ const index = getHttpIndex(routes);
2880
+ const methods = /* @__PURE__ */ new Set();
2881
+ const staticMethods = index.methodsByStaticPath.get(path19);
2882
+ if (staticMethods) {
2883
+ for (const method of staticMethods) {
2884
+ methods.add(method);
2885
+ }
2886
+ }
2887
+ for (const route of index.dynamics) {
2888
+ const params = matchDynamicPath(route.urlPath, path19, route.paramNames, route.isCatchAll);
2889
+ if (params !== null) {
2890
+ methods.add(route.method);
2891
+ }
2892
+ }
2893
+ return Array.from(methods);
2894
+ }
2794
2895
  function matchDynamicPath(pattern, path19, paramNames, isCatchAll) {
2795
2896
  const patternSegments = pattern.split("/").filter(Boolean);
2796
2897
  const pathSegments = path19.split("/").filter(Boolean);
@@ -2838,9 +2939,6 @@ function matchDynamicPath(pattern, path19, paramNames, isCatchAll) {
2838
2939
  return params;
2839
2940
  }
2840
2941
 
2841
- // src/loader/loadRouteModule.ts
2842
- import fs12 from "fs";
2843
-
2844
2942
  // src/loader/validateRouteModule.ts
2845
2943
  function validateRouteModule(value, method, filePath) {
2846
2944
  if (typeof value !== "function") {
@@ -2856,7 +2954,7 @@ async function loadRouteModule(filePath, method, rootDir) {
2856
2954
  const dist = getDevDist();
2857
2955
  if (dist) {
2858
2956
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
2859
- if (sourcePath && fs12.existsSync(sourcePath)) {
2957
+ if (sourcePath) {
2860
2958
  try {
2861
2959
  await ensureCompiled(sourcePath, rootDir, dist);
2862
2960
  } catch (compileErr) {
@@ -3096,7 +3194,9 @@ function formatSetCookie(name, value, options) {
3096
3194
  return cookie;
3097
3195
  }
3098
3196
  function createContext(request, params, config = {}, ip = "") {
3099
- const url = new URL(request.url);
3197
+ return createContextFromUrl(request, new URL(request.url), params, config, ip);
3198
+ }
3199
+ function createContextFromUrl(request, url, params, config = {}, ip = "") {
3100
3200
  const meta = { headers: {}, setCookies: [] };
3101
3201
  const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
3102
3202
  const cookiesObj = {};
@@ -3251,7 +3351,7 @@ async function parseMultipart(request) {
3251
3351
 
3252
3352
  // src/runtime/resolveInput.ts
3253
3353
  init_inputType();
3254
- async function resolveInput(method, request) {
3354
+ async function resolveInputFromUrl(method, request, url) {
3255
3355
  const inputType = getInputTypeForMethod(method);
3256
3356
  if (inputType === "body") {
3257
3357
  const contentType = request.headers.get("content-type") ?? "";
@@ -3286,7 +3386,6 @@ async function resolveInput(method, request) {
3286
3386
  }
3287
3387
  return result.data;
3288
3388
  }
3289
- const url = new URL(request.url);
3290
3389
  return queryToObject(url.searchParams);
3291
3390
  }
3292
3391
 
@@ -3658,11 +3757,13 @@ function mapReceivedFromMessage(message) {
3658
3757
  init_inputType();
3659
3758
 
3660
3759
  // src/utils/getClientIp.ts
3661
- function getClientIp(req) {
3662
- const xff = req.headers["x-forwarded-for"];
3663
- if (typeof xff === "string" && xff.length > 0) {
3664
- const first = xff.split(",")[0]?.trim();
3665
- if (first) return first;
3760
+ function getClientIp(req, trustedProxy = false) {
3761
+ if (trustedProxy) {
3762
+ const xff = req.headers["x-forwarded-for"];
3763
+ if (typeof xff === "string" && xff.length > 0) {
3764
+ const first = xff.split(",")[0]?.trim();
3765
+ if (first) return first;
3766
+ }
3666
3767
  }
3667
3768
  const remote = req.socket?.remoteAddress;
3668
3769
  if (remote) {
@@ -3675,7 +3776,7 @@ function getClientIp(req) {
3675
3776
  }
3676
3777
 
3677
3778
  // src/server/handleWsUpgrade.ts
3678
- import fs13 from "fs";
3779
+ import fs12 from "fs";
3679
3780
  import { WebSocketServer, WebSocket } from "ws";
3680
3781
  import path11 from "path";
3681
3782
 
@@ -3803,7 +3904,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
3803
3904
  const dist = getDevDist();
3804
3905
  if (dist) {
3805
3906
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
3806
- if (sourcePath && fs13.existsSync(sourcePath)) {
3907
+ if (sourcePath && fs12.existsSync(sourcePath)) {
3807
3908
  await ensureCompiled(sourcePath, rootDir, dist);
3808
3909
  }
3809
3910
  }
@@ -3861,7 +3962,7 @@ async function sendResponseToSocket(socket, response) {
3861
3962
  socket.destroy();
3862
3963
  }
3863
3964
  function attachWebSocket(options) {
3864
- const { server, routesRef, rootDir, config, globalMiddlewares } = options;
3965
+ const { server, routesRef, rootDir, config, globalMiddlewares, trustedProxy = false } = options;
3865
3966
  const wss = new WebSocketServer({ noServer: true });
3866
3967
  server.on("upgrade", async (req, socket, head) => {
3867
3968
  const currentWsRoutes = routesRef.wsCurrent;
@@ -3877,7 +3978,7 @@ function attachWebSocket(options) {
3877
3978
  const host = req.headers.host ?? "localhost";
3878
3979
  const url = `http://${host}${req.url ?? "/"}`;
3879
3980
  const request = new Request(url, { method: "GET", headers });
3880
- const ctx = createContext(request, params, config, getClientIp(req));
3981
+ const ctx = createContext(request, params, config, getClientIp(req, trustedProxy));
3881
3982
  const meta = ctx.meta;
3882
3983
  let upgraded = false;
3883
3984
  const finalHandler = async () => {
@@ -3947,16 +4048,26 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
3947
4048
  const headers = nodeHttpToWebHeaders(req);
3948
4049
  const method = req.method ?? "GET";
3949
4050
  if (method === "GET" || method === "HEAD") {
3950
- return new Request(url.toString(), { method, headers });
4051
+ return { request: new Request(url.toString(), { method, headers }), url };
4052
+ }
4053
+ const contentLength = req.headers["content-length"];
4054
+ if (contentLength !== void 0) {
4055
+ const declared = Number(Array.isArray(contentLength) ? contentLength[0] : contentLength);
4056
+ if (Number.isFinite(declared) && declared > bodyLimit) {
4057
+ throw new PayloadTooLargeError(bodyLimit);
4058
+ }
3951
4059
  }
3952
4060
  const stream = Readable2.toWeb(req);
3953
4061
  const limitedStream = limitStreamSize(stream, bodyLimit);
3954
- return new Request(url.toString(), {
3955
- method,
3956
- headers,
3957
- body: limitedStream,
3958
- duplex: "half"
3959
- });
4062
+ return {
4063
+ request: new Request(url.toString(), {
4064
+ method,
4065
+ headers,
4066
+ body: limitedStream,
4067
+ duplex: "half"
4068
+ }),
4069
+ url
4070
+ };
3960
4071
  }
3961
4072
  function limitStreamSize(stream, maxSize) {
3962
4073
  let totalSize = 0;
@@ -4008,22 +4119,6 @@ function limitStreamSize(stream, maxSize) {
4008
4119
  }
4009
4120
  });
4010
4121
  }
4011
- function findAllowedMethods(routes, path19) {
4012
- const methods = /* @__PURE__ */ new Set();
4013
- for (const route of routes) {
4014
- if (route.urlPath === path19) {
4015
- methods.add(route.method);
4016
- continue;
4017
- }
4018
- if (route.isDynamic) {
4019
- const params = matchDynamicPath(route.urlPath, path19, route.paramNames, route.isCatchAll);
4020
- if (params !== null) {
4021
- methods.add(route.method);
4022
- }
4023
- }
4024
- }
4025
- return Array.from(methods);
4026
- }
4027
4122
  function createServer(options) {
4028
4123
  const {
4029
4124
  routes,
@@ -4038,7 +4133,8 @@ function createServer(options) {
4038
4133
  helmet: helmetOption,
4039
4134
  logger: loggerOption,
4040
4135
  bodyLimit = DEFAULT_BODY_LIMIT,
4041
- http2: http2Option
4136
+ http2: http2Option,
4137
+ trustedProxy = false
4042
4138
  } = options;
4043
4139
  const routesRef = { current: routes, wsCurrent: wsRoutes ?? [] };
4044
4140
  const configMiddlewares = [];
@@ -4050,6 +4146,10 @@ function createServer(options) {
4050
4146
  }
4051
4147
  const loggerMiddlewareInst = loggerOption === false ? null : loggerOption === true || loggerOption === void 0 ? logger() : logger(loggerOption);
4052
4148
  if (loggerMiddlewareInst) configMiddlewares.push(loggerMiddlewareInst);
4149
+ const outerMiddlewares = [...configMiddlewares];
4150
+ if (globalMiddlewares && globalMiddlewares.length > 0) {
4151
+ outerMiddlewares.push(...globalMiddlewares);
4152
+ }
4053
4153
  const server = (() => {
4054
4154
  if (http2Option) {
4055
4155
  const h2Opts = typeof http2Option === "object" ? http2Option : {};
@@ -4069,29 +4169,29 @@ function createServer(options) {
4069
4169
  dist,
4070
4170
  req,
4071
4171
  res,
4072
- configMiddlewares,
4172
+ outerMiddlewares,
4073
4173
  onError,
4074
4174
  config,
4075
- globalMiddlewares,
4076
4175
  globalInjectors,
4077
- bodyLimit
4176
+ bodyLimit,
4177
+ trustedProxy
4078
4178
  ).catch(() => {
4079
4179
  res.statusCode = 500;
4080
4180
  res.end();
4081
4181
  });
4082
4182
  });
4083
4183
  if (routesRef.wsCurrent.length > 0) {
4084
- attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares });
4184
+ attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares, trustedProxy });
4085
4185
  }
4086
4186
  return { server, routesRef };
4087
4187
  }
4088
- function prepareRequest(req, config, bodyLimit) {
4089
- const request = toWebRequest(req, bodyLimit);
4188
+ function prepareRequest(req, config, bodyLimit, trustedProxy) {
4189
+ const { request, url } = toWebRequest(req, bodyLimit);
4090
4190
  const method = request.method.toUpperCase();
4091
- const urlPath = new URL(request.url).pathname;
4092
- const ctx = createContext(request, {}, config, getClientIp(req));
4191
+ const urlPath = url.pathname;
4192
+ const ctx = createContextFromUrl(request, url, {}, config, getClientIp(req, trustedProxy));
4093
4193
  const meta = ctx.meta;
4094
- return { request, ctx, meta, method, urlPath };
4194
+ return { request, url, ctx, meta, method, urlPath };
4095
4195
  }
4096
4196
  function resolveRouteOrThrow(routes, method, urlPath) {
4097
4197
  const match = matchRoute(routes, method, urlPath);
@@ -4103,14 +4203,14 @@ function resolveRouteOrThrow(routes, method, urlPath) {
4103
4203
  throw new RouteNotFoundError(urlPath);
4104
4204
  }
4105
4205
  function createRoutePipeline(opts) {
4106
- const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
4206
+ const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;
4107
4207
  return async () => {
4108
4208
  const match = resolveRouteOrThrow(routes, method, urlPath);
4109
4209
  ctx.params = match.params;
4110
4210
  const { route } = match;
4111
4211
  const absoluteFilePath = path12.resolve(rootDir, route.filePath);
4112
4212
  const routeModule = await loadRouteModule(absoluteFilePath, route.method, rootDir);
4113
- const input = await resolveInput(route.method, request);
4213
+ const input = await resolveInputFromUrl(route.method, request, url);
4114
4214
  const inputType = getInputTypeForMethod(route.method);
4115
4215
  const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
4116
4216
  if (isDevOnDemandEnabled()) {
@@ -4142,33 +4242,33 @@ async function sendSuccessResponse(response, res) {
4142
4242
  await sendNodeResponse(response, res);
4143
4243
  }
4144
4244
  async function sendErrorResponse(err, meta, res, onError, ctx) {
4145
- await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx.config), meta), res);
4146
- if (onError) {
4245
+ await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx?.config), meta), res);
4246
+ if (onError && ctx) {
4147
4247
  try {
4148
4248
  await onError(err, ctx);
4149
4249
  } catch {
4150
4250
  }
4151
4251
  }
4152
4252
  }
4153
- async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
4154
- const { request, ctx, meta, method, urlPath } = prepareRequest(req, config, bodyLimit);
4155
- const routePipeline = createRoutePipeline({
4156
- routes,
4157
- method,
4158
- urlPath,
4159
- ctx,
4160
- request,
4161
- rootDir,
4162
- dist,
4163
- globalMiddlewares,
4164
- globalInjectors
4165
- });
4166
- const outerMiddlewares = [];
4167
- if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
4168
- if (globalMiddlewares && globalMiddlewares.length > 0) {
4169
- outerMiddlewares.push(...globalMiddlewares);
4170
- }
4253
+ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy) {
4254
+ let meta = { headers: {}, setCookies: [] };
4255
+ let ctx;
4171
4256
  try {
4257
+ const prepared = prepareRequest(req, config, bodyLimit, trustedProxy);
4258
+ ctx = prepared.ctx;
4259
+ meta = prepared.meta;
4260
+ const { request, url, method, urlPath } = prepared;
4261
+ const routePipeline = createRoutePipeline({
4262
+ routes,
4263
+ method,
4264
+ urlPath,
4265
+ url,
4266
+ ctx,
4267
+ request,
4268
+ rootDir,
4269
+ dist,
4270
+ globalInjectors
4271
+ });
4172
4272
  const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
4173
4273
  await sendSuccessResponse(response, res);
4174
4274
  } catch (err) {
@@ -4205,7 +4305,7 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
4205
4305
  }
4206
4306
 
4207
4307
  // src/cli/generateRoutes.ts
4208
- import fs14 from "fs";
4308
+ import fs13 from "fs";
4209
4309
  import path13 from "path";
4210
4310
  async function hydrateRoutes(manifest) {
4211
4311
  const hydrateRoute = (serialized) => ({
@@ -4232,7 +4332,7 @@ async function hydrateRoutes(manifest) {
4232
4332
 
4233
4333
  // src/cli/generateAgentArtifacts.ts
4234
4334
  import path14 from "path";
4235
- import fs15 from "fs/promises";
4335
+ import fs14 from "fs/promises";
4236
4336
 
4237
4337
  // src/ast/extractAgentMetadata.ts
4238
4338
  import ts8 from "typescript";
@@ -4447,11 +4547,11 @@ function serializeAgents(agents, dist = "dist") {
4447
4547
  }
4448
4548
  async function writeAgentsModule(manifest, outputPath) {
4449
4549
  const dir = path14.dirname(outputPath);
4450
- await fs15.mkdir(dir, { recursive: true });
4550
+ await fs14.mkdir(dir, { recursive: true });
4451
4551
  const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
4452
4552
  export const agents = ${JSON.stringify(manifest, null, 2)};
4453
4553
  `;
4454
- await fs15.writeFile(outputPath, content, "utf-8");
4554
+ await fs14.writeFile(outputPath, content, "utf-8");
4455
4555
  }
4456
4556
  function hydrateAgents(manifest) {
4457
4557
  return manifest.map((a) => ({
@@ -4468,9 +4568,10 @@ function hydrateAgents(manifest) {
4468
4568
  }
4469
4569
  async function generateAgentArtifacts(agents, rootDir, dist) {
4470
4570
  const metadata = [];
4571
+ const programByFile = createPrograms(agents.map((m) => path14.resolve(rootDir, m.filePath)));
4471
4572
  for (const manifest of agents) {
4472
4573
  const absPath = path14.resolve(rootDir, manifest.filePath);
4473
- const program = createProgram(absPath);
4574
+ const program = programByFile.get(absPath);
4474
4575
  const result = extractAgentMetadata(program, absPath, {
4475
4576
  name: manifest.name,
4476
4577
  filePath: manifest.filePath,
@@ -4554,7 +4655,7 @@ var AGENTS_FILE2 = "faapi-agents.js";
4554
4655
  var PATTERNS = ["src/api/**/*.ts"];
4555
4656
  async function loadAndHydrateTools(rootDir, dist) {
4556
4657
  const toolsPath = path15.resolve(rootDir, dist, TOOLS_FILE2);
4557
- if (!fs16.existsSync(toolsPath)) {
4658
+ if (!fs15.existsSync(toolsPath)) {
4558
4659
  return [];
4559
4660
  }
4560
4661
  const serialized = await importWithCacheBust(toolsPath);
@@ -4564,7 +4665,7 @@ async function loadAndHydrateTools(rootDir, dist) {
4564
4665
  }
4565
4666
  async function loadAndHydrateAgents(rootDir, dist) {
4566
4667
  const agentsPath = path15.resolve(rootDir, dist, AGENTS_FILE2);
4567
- if (!fs16.existsSync(agentsPath)) {
4668
+ if (!fs15.existsSync(agentsPath)) {
4568
4669
  return [];
4569
4670
  }
4570
4671
  const serialized = await importWithCacheBust(agentsPath);
@@ -4603,6 +4704,7 @@ var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
4603
4704
  "bodyLimit",
4604
4705
  "logger",
4605
4706
  "http2",
4707
+ "trustedProxy",
4606
4708
  "response"
4607
4709
  ]);
4608
4710
  function isFaapiConfigKey(key) {
@@ -4612,7 +4714,7 @@ async function createAppBase(options) {
4612
4714
  const rootDir = options?.rootDir ?? process.cwd();
4613
4715
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
4614
4716
  const routesPath = path15.resolve(rootDir, dist, ROUTES_FILE);
4615
- if (!fs16.existsSync(routesPath)) {
4717
+ if (!fs15.existsSync(routesPath)) {
4616
4718
  throw new Error(
4617
4719
  `[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
4618
4720
  );
@@ -4647,7 +4749,8 @@ async function createAppBase(options) {
4647
4749
  helmet: config?.helmet,
4648
4750
  logger: config?.logger,
4649
4751
  bodyLimit: config?.bodyLimit,
4650
- http2: config?.http2
4752
+ http2: config?.http2,
4753
+ trustedProxy: config?.trustedProxy
4651
4754
  });
4652
4755
  const { handlerWrappers, upgradeWrappers } = await loadPlugins(config?.plugins, {
4653
4756
  rootDir,
@@ -4834,7 +4937,7 @@ async function createAppBase(options) {
4834
4937
  // src/router/scanRoutes.ts
4835
4938
  import fg2 from "fast-glob";
4836
4939
  import path16 from "path";
4837
- import fs17 from "fs";
4940
+ import fs16 from "fs";
4838
4941
 
4839
4942
  // src/router/constants.ts
4840
4943
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
@@ -4914,7 +5017,7 @@ function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
4914
5017
  const mwJsPath = path16.join(currentDir, "middlewares.js");
4915
5018
  const absTsPath = path16.resolve(rootDir, mwTsPath);
4916
5019
  const absJsPath = path16.resolve(rootDir, mwJsPath);
4917
- const absMwPath = fs17.existsSync(absTsPath) ? absTsPath : fs17.existsSync(absJsPath) ? absJsPath : null;
5020
+ const absMwPath = fs16.existsSync(absTsPath) ? absTsPath : fs16.existsSync(absJsPath) ? absJsPath : null;
4918
5021
  if (absMwPath) {
4919
5022
  const relMwPath = path16.relative(rootDir, absMwPath);
4920
5023
  const prodAbsPath = path16.resolve(rootDir, toProdFilePath3(relMwPath, dist));
@@ -4924,7 +5027,7 @@ function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
4924
5027
  for (const ext of [".ts", ".js"]) {
4925
5028
  const mwPath = path16.join(currentDir, `middlewares${ext}`);
4926
5029
  const absMwPath = path16.resolve(rootDir, mwPath);
4927
- if (fs17.existsSync(absMwPath)) {
5030
+ if (fs16.existsSync(absMwPath)) {
4928
5031
  paths.push(absMwPath);
4929
5032
  break;
4930
5033
  }
@@ -4971,7 +5074,7 @@ async function scanRoutes(rootDir, patterns, dist) {
4971
5074
  const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
4972
5075
  middlewareBundle = await loadMergedMiddlewares(mwPaths);
4973
5076
  }
4974
- const source = await fs17.promises.readFile(absPath, "utf8").catch(() => "");
5077
+ const source = await fs16.promises.readFile(absPath, "utf8").catch(() => "");
4975
5078
  const exportNames = extractExportsFromSource(source);
4976
5079
  const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
4977
5080
  for (const method of methods) {
@@ -5008,7 +5111,7 @@ async function scanRoutes(rootDir, patterns, dist) {
5008
5111
  // src/tools/scanTools.ts
5009
5112
  import fg3 from "fast-glob";
5010
5113
  import path17 from "path";
5011
- import fs18 from "fs";
5114
+ import fs17 from "fs";
5012
5115
  var TOOL_PATTERNS = ["src/tools/**/*.ts"];
5013
5116
  var TOOL_EXPORT_RE = new RegExp(
5014
5117
  String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
@@ -5059,7 +5162,7 @@ async function scanTools(rootDir, patterns) {
5059
5162
  continue;
5060
5163
  }
5061
5164
  const absPath = path17.resolve(rootDir, normalizedFile);
5062
- const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
5165
+ const source = await fs17.promises.readFile(absPath, "utf8").catch(() => "");
5063
5166
  const exportNames = extractToolExportsFromSource(source);
5064
5167
  const namespace = filePathToToolNamespace(normalizedFile);
5065
5168
  for (const fnName of exportNames) {
@@ -5084,7 +5187,7 @@ async function scanTools(rootDir, patterns) {
5084
5187
  // src/agents/scanAgents.ts
5085
5188
  import fg4 from "fast-glob";
5086
5189
  import path18 from "path";
5087
- import fs19 from "fs";
5190
+ import fs18 from "fs";
5088
5191
  var DEFAULT_AGENT_PATTERNS = ["src/agents/*/handler.ts"];
5089
5192
  var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
5090
5193
  function extractAgentNameFromPath(filePath) {
@@ -5117,7 +5220,7 @@ async function scanAgents(rootDir, patterns) {
5117
5220
  continue;
5118
5221
  }
5119
5222
  const absPath = path18.resolve(rootDir, normalizedFile);
5120
- const source = await fs19.promises.readFile(absPath, "utf8").catch(() => "");
5223
+ const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
5121
5224
  const { hasRun } = detectAgentExports(source);
5122
5225
  const name = extractAgentNameFromPath(normalizedFile);
5123
5226
  const prevFile = seen.get(name);
@@ -5197,6 +5300,7 @@ export {
5197
5300
  createDevApp,
5198
5301
  createProdApp,
5199
5302
  createProgram,
5303
+ createPrograms,
5200
5304
  extractTypeInfo,
5201
5305
  getAgent,
5202
5306
  getAgentEntry,
@@ -5204,6 +5308,7 @@ export {
5204
5308
  getInputTypeForMethod,
5205
5309
  getSkill,
5206
5310
  getTool,
5311
+ getToolSchemaPath,
5207
5312
  helmet,
5208
5313
  hydrateSkillRegistry,
5209
5314
  invalidateProgramCache,