@faapi/faapi 3.2.0 → 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/testing.js CHANGED
@@ -129,14 +129,14 @@ var ValidationError = class extends FaapiError {
129
129
  issues;
130
130
  };
131
131
  var RouteNotFoundError = class extends FaapiError {
132
- constructor(path11) {
133
- super("ROUTE_NOT_FOUND", `Route not found: ${path11}`, 404);
132
+ constructor(path12) {
133
+ super("ROUTE_NOT_FOUND", `Route not found: ${path12}`, 404);
134
134
  this.name = "RouteNotFoundError";
135
135
  }
136
136
  };
137
137
  var MethodNotAllowedError = class extends FaapiError {
138
- constructor(method, path11, allowedMethods) {
139
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path11}`, 405);
138
+ constructor(method, path12, allowedMethods) {
139
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path12}`, 405);
140
140
  this.allowedMethods = allowedMethods;
141
141
  this.name = "MethodNotAllowedError";
142
142
  }
@@ -266,7 +266,9 @@ function formatSetCookie(name, value, options) {
266
266
  return cookie;
267
267
  }
268
268
  function createContext(request, params, config = {}, ip = "") {
269
- const url = new URL(request.url);
269
+ return createContextFromUrl(request, new URL(request.url), params, config, ip);
270
+ }
271
+ function createContextFromUrl(request, url, params, config = {}, ip = "") {
270
272
  const meta = { headers: {}, setCookies: [] };
271
273
  const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
272
274
  const cookiesObj = {};
@@ -370,8 +372,8 @@ function createContext(request, params, config = {}, ip = "") {
370
372
  return ctx;
371
373
  }
372
374
  function createTestContext(options) {
373
- const { method = "GET", path: path11, query, headers, params = {}, config = {}, ip = "" } = options;
374
- const url = new URL(`http://localhost${path11}`);
375
+ const { method = "GET", path: path12, query, headers, params = {}, config = {}, ip = "" } = options;
376
+ const url = new URL(`http://localhost${path12}`);
375
377
  if (query) {
376
378
  for (const [key, value] of Object.entries(query)) {
377
379
  if (Array.isArray(value)) {
@@ -756,7 +758,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
756
758
  }
757
759
 
758
760
  // src/testServer.ts
759
- import path10 from "path";
761
+ import path11 from "path";
760
762
  import os from "os";
761
763
  import fs9 from "fs/promises";
762
764
 
@@ -770,9 +772,9 @@ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
770
772
  var HTTP_METHOD_SET = new Set(HTTP_METHODS);
771
773
 
772
774
  // src/utils/normalizePath.ts
773
- function normalizePath(path11) {
774
- if (!path11) return "";
775
- let result = path11.replace(/\\/g, "/");
775
+ function normalizePath(path12) {
776
+ if (!path12) return "";
777
+ let result = path12.replace(/\\/g, "/");
776
778
  result = result.replace(/\/+/g, "/");
777
779
  result = result.replace(/\/+$/, "");
778
780
  if (result && !result.startsWith("/")) {
@@ -1045,27 +1047,131 @@ function sortRoutes(routes) {
1045
1047
  }
1046
1048
 
1047
1049
  // src/cli/generateSchemaFiles.ts
1048
- import path3 from "path";
1049
- import fs2 from "fs/promises";
1050
+ import path4 from "path";
1051
+ import fs3 from "fs/promises";
1050
1052
 
1051
1053
  // src/ast/createProgram.ts
1052
1054
  import ts2 from "typescript";
1055
+ import fs2 from "fs";
1056
+ import path2 from "path";
1053
1057
  var programCache = /* @__PURE__ */ new Map();
1058
+ var tsConfigCache = /* @__PURE__ */ new Map();
1059
+ function findTsConfig(filePath) {
1060
+ let dir = path2.dirname(filePath);
1061
+ const root = path2.parse(dir).root;
1062
+ while (true) {
1063
+ const candidate = path2.join(dir, "tsconfig.json");
1064
+ if (fs2.existsSync(candidate)) {
1065
+ return candidate;
1066
+ }
1067
+ if (dir === root) return null;
1068
+ const parent = path2.dirname(dir);
1069
+ if (parent === dir) return null;
1070
+ dir = parent;
1071
+ }
1072
+ }
1073
+ function parseTsConfig(tsconfigPath) {
1074
+ const cached = tsConfigCache.get(tsconfigPath);
1075
+ if (cached) return cached;
1076
+ const result = { fileNames: [] };
1077
+ try {
1078
+ const configFile = ts2.readConfigFile(tsconfigPath, (p) => fs2.readFileSync(p, "utf-8"));
1079
+ if (configFile.error) {
1080
+ tsConfigCache.set(tsconfigPath, result);
1081
+ return result;
1082
+ }
1083
+ const config = configFile.config ?? {};
1084
+ const basePath = path2.dirname(tsconfigPath);
1085
+ const parsed = ts2.parseJsonConfigFileContent(
1086
+ config,
1087
+ ts2.sys,
1088
+ basePath,
1089
+ /* existingOptions */
1090
+ void 0,
1091
+ tsconfigPath
1092
+ );
1093
+ result.fileNames = parsed.fileNames;
1094
+ if (parsed.options.module !== void 0) {
1095
+ result.module = parsed.options.module;
1096
+ }
1097
+ if (parsed.options.moduleResolution !== void 0) {
1098
+ result.moduleResolution = parsed.options.moduleResolution;
1099
+ }
1100
+ } catch {
1101
+ }
1102
+ tsConfigCache.set(tsconfigPath, result);
1103
+ return result;
1104
+ }
1054
1105
  function createProgram(filePath) {
1055
1106
  const cached = programCache.get(filePath);
1056
1107
  if (cached) {
1057
1108
  return cached;
1058
1109
  }
1059
- const program = ts2.createProgram([filePath], {
1110
+ const program = buildProgram([filePath], findTsConfig(filePath));
1111
+ programCache.set(filePath, program);
1112
+ return program;
1113
+ }
1114
+ function createPrograms(filePaths) {
1115
+ const unique = [...new Set(filePaths)];
1116
+ const result = /* @__PURE__ */ new Map();
1117
+ const groups = /* @__PURE__ */ new Map();
1118
+ const noTsconfigFiles = [];
1119
+ for (const filePath of unique) {
1120
+ const tsconfigPath = findTsConfig(filePath);
1121
+ if (!tsconfigPath) {
1122
+ noTsconfigFiles.push(filePath);
1123
+ continue;
1124
+ }
1125
+ const group = groups.get(tsconfigPath);
1126
+ if (group) {
1127
+ group.files.push(filePath);
1128
+ } else {
1129
+ groups.set(tsconfigPath, { tsconfigPath, files: [filePath] });
1130
+ }
1131
+ }
1132
+ for (const { tsconfigPath, files } of groups.values()) {
1133
+ const cacheKey = `shared::${tsconfigPath}::${[...files].sort().join("|")}`;
1134
+ let program = programCache.get(cacheKey);
1135
+ if (!program) {
1136
+ program = buildProgram(files, tsconfigPath);
1137
+ programCache.set(cacheKey, program);
1138
+ }
1139
+ for (const filePath of files) {
1140
+ result.set(filePath, program);
1141
+ }
1142
+ }
1143
+ for (const filePath of noTsconfigFiles) {
1144
+ result.set(filePath, createProgram(filePath));
1145
+ }
1146
+ return result;
1147
+ }
1148
+ function buildProgram(entryFiles, tsconfigPath) {
1149
+ const options = {
1060
1150
  strict: true,
1061
1151
  target: ts2.ScriptTarget.ES2022,
1062
1152
  module: ts2.ModuleKind.NodeNext,
1063
1153
  moduleResolution: ts2.ModuleResolutionKind.NodeNext,
1064
1154
  skipLibCheck: true,
1065
1155
  noEmit: true
1066
- });
1067
- programCache.set(filePath, program);
1068
- return program;
1156
+ };
1157
+ const rootNames = [...entryFiles];
1158
+ if (tsconfigPath) {
1159
+ const tsOptions = parseTsConfig(tsconfigPath);
1160
+ if (tsOptions.module !== void 0) {
1161
+ options.module = tsOptions.module;
1162
+ }
1163
+ if (tsOptions.moduleResolution !== void 0) {
1164
+ options.moduleResolution = tsOptions.moduleResolution;
1165
+ }
1166
+ if (tsOptions.fileNames.length > 0) {
1167
+ for (const fileName of tsOptions.fileNames) {
1168
+ if (!rootNames.includes(fileName)) {
1169
+ rootNames.push(fileName);
1170
+ }
1171
+ }
1172
+ }
1173
+ }
1174
+ return ts2.createProgram(rootNames, options);
1069
1175
  }
1070
1176
 
1071
1177
  // src/ast/extractHandlerTypes.ts
@@ -1073,6 +1179,10 @@ import ts4 from "typescript";
1073
1179
 
1074
1180
  // src/ast/resolveTypeNode.ts
1075
1181
  import ts3 from "typescript";
1182
+ var currentProgram = null;
1183
+ function setProgramContext(program) {
1184
+ currentProgram = program;
1185
+ }
1076
1186
  var SchemaExtractionError = class extends Error {
1077
1187
  constructor(typeText, reason, options) {
1078
1188
  super(`\u65E0\u6CD5\u89E3\u6790\u7C7B\u578B "${typeText}": ${reason}`, options);
@@ -1395,11 +1505,69 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
1395
1505
  if (ts3.isEnumDeclaration(declaration)) {
1396
1506
  return resolveEnumDeclaration(declaration);
1397
1507
  }
1508
+ if (ts3.isImportSpecifier(declaration) || ts3.isImportClause(declaration)) {
1509
+ const resolved = resolveImportAlias(typeNode, symbol, checker, visited);
1510
+ if (resolved) return resolved;
1511
+ }
1398
1512
  }
1399
1513
  }
1400
1514
  }
1401
1515
  throw new SchemaExtractionError(typeNode.getText(), `\u65E0\u6CD5\u89E3\u6790\u7684\u5F15\u7528\u7C7B\u578B "${typeName}"`);
1402
1516
  }
1517
+ function resolveImportAlias(typeNode, symbol, checker, visited) {
1518
+ const typeName = typeNode.typeName.getText();
1519
+ try {
1520
+ const aliased = checker.getAliasedSymbol(symbol);
1521
+ if (aliased && aliased.declarations && aliased.declarations.length > 0) {
1522
+ const decl = aliased.declarations[0];
1523
+ if (ts3.isInterfaceDeclaration(decl)) {
1524
+ return resolveInterfaceDeclaration(decl, checker, visited);
1525
+ }
1526
+ if (ts3.isTypeAliasDeclaration(decl)) {
1527
+ return resolveTypeNode(decl.type, checker, visited);
1528
+ }
1529
+ if (ts3.isEnumDeclaration(decl)) {
1530
+ return resolveEnumDeclaration(decl);
1531
+ }
1532
+ }
1533
+ } catch {
1534
+ }
1535
+ const program = currentProgram;
1536
+ if (!program) return null;
1537
+ const allSFs = program.getSourceFiles();
1538
+ for (const sourceFile of allSFs) {
1539
+ if (sourceFile.fileName.includes("/node_modules/") || sourceFile.fileName.includes("typescript/lib/")) {
1540
+ continue;
1541
+ }
1542
+ const found = findTopLevelDecl(sourceFile, typeName);
1543
+ if (found) {
1544
+ if (found.kind === "interface") {
1545
+ return resolveInterfaceDeclaration(found.node, checker, visited);
1546
+ }
1547
+ if (found.kind === "typeAlias") {
1548
+ return resolveTypeNode(found.node.type, checker, visited);
1549
+ }
1550
+ if (found.kind === "enum") {
1551
+ return resolveEnumDeclaration(found.node);
1552
+ }
1553
+ }
1554
+ }
1555
+ return null;
1556
+ }
1557
+ function findTopLevelDecl(sourceFile, typeName) {
1558
+ let found = null;
1559
+ ts3.forEachChild(sourceFile, (node) => {
1560
+ if (found) return;
1561
+ if (ts3.isInterfaceDeclaration(node) && node.name.text === typeName) {
1562
+ found = { kind: "interface", node };
1563
+ } else if (ts3.isTypeAliasDeclaration(node) && node.name.text === typeName) {
1564
+ found = { kind: "typeAlias", node };
1565
+ } else if (ts3.isEnumDeclaration(node) && node.name.text === typeName) {
1566
+ found = { kind: "enum", node };
1567
+ }
1568
+ });
1569
+ return found;
1570
+ }
1403
1571
  function resolveEnumDeclaration(node) {
1404
1572
  const members = [];
1405
1573
  let nextNumericValue = 0;
@@ -1604,80 +1772,90 @@ function extractTypeInfo(program, filePath, typeName) {
1604
1772
  const sourceFile = program.getSourceFile(filePath);
1605
1773
  if (!sourceFile) return null;
1606
1774
  const checker = program.getTypeChecker();
1607
- let result = null;
1608
- ts4.forEachChild(sourceFile, (node) => {
1609
- if (result) return;
1610
- if (ts4.isInterfaceDeclaration(node) && node.name.text === typeName) {
1611
- const visited = /* @__PURE__ */ new Set();
1612
- visited.add(typeName);
1613
- const runtimeType = withFileContext(
1614
- filePath,
1615
- typeName,
1616
- () => resolveInterfaceDeclaration(node, checker, visited)
1617
- );
1618
- result = {
1619
- name: typeName,
1620
- properties: runtimeType.kind === "object" ? runtimeType.properties : [],
1621
- runtimeType
1622
- };
1623
- return;
1624
- }
1625
- if (ts4.isTypeAliasDeclaration(node) && node.name.text === typeName) {
1626
- const visited = /* @__PURE__ */ new Set();
1627
- visited.add(typeName);
1628
- const runtimeType = withFileContext(
1629
- filePath,
1630
- typeName,
1631
- () => resolveTypeNode(node.type, checker, visited)
1632
- );
1633
- result = {
1634
- name: typeName,
1635
- properties: runtimeType.kind === "object" ? runtimeType.properties : [],
1636
- runtimeType
1637
- };
1638
- return;
1639
- }
1640
- });
1641
- return result;
1775
+ setProgramContext(program);
1776
+ try {
1777
+ let result = null;
1778
+ ts4.forEachChild(sourceFile, (node) => {
1779
+ if (result) return;
1780
+ if (ts4.isInterfaceDeclaration(node) && node.name.text === typeName) {
1781
+ const visited = /* @__PURE__ */ new Set();
1782
+ visited.add(typeName);
1783
+ const runtimeType = withFileContext(
1784
+ filePath,
1785
+ typeName,
1786
+ () => resolveInterfaceDeclaration(node, checker, visited)
1787
+ );
1788
+ result = {
1789
+ name: typeName,
1790
+ properties: runtimeType.kind === "object" ? runtimeType.properties : [],
1791
+ runtimeType
1792
+ };
1793
+ return;
1794
+ }
1795
+ if (ts4.isTypeAliasDeclaration(node) && node.name.text === typeName) {
1796
+ const visited = /* @__PURE__ */ new Set();
1797
+ visited.add(typeName);
1798
+ const runtimeType = withFileContext(
1799
+ filePath,
1800
+ typeName,
1801
+ () => resolveTypeNode(node.type, checker, visited)
1802
+ );
1803
+ result = {
1804
+ name: typeName,
1805
+ properties: runtimeType.kind === "object" ? runtimeType.properties : [],
1806
+ runtimeType
1807
+ };
1808
+ return;
1809
+ }
1810
+ });
1811
+ return result;
1812
+ } finally {
1813
+ setProgramContext(null);
1814
+ }
1642
1815
  }
1643
1816
  function extractAllTypes(program, filePath) {
1644
1817
  const sourceFile = program.getSourceFile(filePath);
1645
1818
  if (!sourceFile) return /* @__PURE__ */ new Map();
1646
1819
  const checker = program.getTypeChecker();
1647
- const result = /* @__PURE__ */ new Map();
1648
- ts4.forEachChild(sourceFile, (node) => {
1649
- if (ts4.isInterfaceDeclaration(node)) {
1650
- const visited = /* @__PURE__ */ new Set();
1651
- visited.add(node.name.text);
1652
- const runtimeType = withFileContext(
1653
- filePath,
1654
- node.name.text,
1655
- () => resolveInterfaceDeclaration(node, checker, visited)
1656
- );
1657
- result.set(node.name.text, {
1658
- name: node.name.text,
1659
- properties: runtimeType.kind === "object" ? runtimeType.properties : [],
1660
- runtimeType
1661
- });
1662
- return;
1663
- }
1664
- if (ts4.isTypeAliasDeclaration(node)) {
1665
- const visited = /* @__PURE__ */ new Set();
1666
- visited.add(node.name.text);
1667
- const runtimeType = withFileContext(
1668
- filePath,
1669
- node.name.text,
1670
- () => resolveTypeNode(node.type, checker, visited)
1671
- );
1672
- result.set(node.name.text, {
1673
- name: node.name.text,
1674
- properties: runtimeType.kind === "object" ? runtimeType.properties : [],
1675
- runtimeType
1676
- });
1677
- return;
1678
- }
1679
- });
1680
- return result;
1820
+ setProgramContext(program);
1821
+ try {
1822
+ const result = /* @__PURE__ */ new Map();
1823
+ ts4.forEachChild(sourceFile, (node) => {
1824
+ if (ts4.isInterfaceDeclaration(node)) {
1825
+ const visited = /* @__PURE__ */ new Set();
1826
+ visited.add(node.name.text);
1827
+ const runtimeType = withFileContext(
1828
+ filePath,
1829
+ node.name.text,
1830
+ () => resolveInterfaceDeclaration(node, checker, visited)
1831
+ );
1832
+ result.set(node.name.text, {
1833
+ name: node.name.text,
1834
+ properties: runtimeType.kind === "object" ? runtimeType.properties : [],
1835
+ runtimeType
1836
+ });
1837
+ return;
1838
+ }
1839
+ if (ts4.isTypeAliasDeclaration(node)) {
1840
+ const visited = /* @__PURE__ */ new Set();
1841
+ visited.add(node.name.text);
1842
+ const runtimeType = withFileContext(
1843
+ filePath,
1844
+ node.name.text,
1845
+ () => resolveTypeNode(node.type, checker, visited)
1846
+ );
1847
+ result.set(node.name.text, {
1848
+ name: node.name.text,
1849
+ properties: runtimeType.kind === "object" ? runtimeType.properties : [],
1850
+ runtimeType
1851
+ });
1852
+ return;
1853
+ }
1854
+ });
1855
+ return result;
1856
+ } finally {
1857
+ setProgramContext(null);
1858
+ }
1681
1859
  }
1682
1860
  function withFileContext(filePath, typeName, fn) {
1683
1861
  try {
@@ -1760,11 +1938,11 @@ function extractSchema(typeNode, sourceFile) {
1760
1938
  }
1761
1939
 
1762
1940
  // src/cli/collectRouteSchemaSources.ts
1763
- import path2 from "path";
1941
+ import path3 from "path";
1764
1942
  function collectRouteSchemaSources(routes, rootDir) {
1765
1943
  const methodsByFile = /* @__PURE__ */ new Map();
1766
1944
  for (const route of routes) {
1767
- const filePath = rootDir ? path2.resolve(rootDir, route.filePath) : route.filePath;
1945
+ const filePath = rootDir ? path3.resolve(rootDir, route.filePath) : route.filePath;
1768
1946
  let entry = methodsByFile.get(filePath);
1769
1947
  if (!entry) {
1770
1948
  entry = { urlPath: route.urlPath, methods: /* @__PURE__ */ new Set() };
@@ -1772,12 +1950,11 @@ function collectRouteSchemaSources(routes, rootDir) {
1772
1950
  }
1773
1951
  entry.methods.add(route.method);
1774
1952
  }
1775
- const programByFile = /* @__PURE__ */ new Map();
1953
+ const programByFile = createPrograms([...methodsByFile.keys()]);
1776
1954
  const allTypesByFile = /* @__PURE__ */ new Map();
1777
1955
  const mergedAllTypes = /* @__PURE__ */ new Map();
1778
1956
  for (const filePath of methodsByFile.keys()) {
1779
- const program = createProgram(filePath);
1780
- programByFile.set(filePath, program);
1957
+ const program = programByFile.get(filePath);
1781
1958
  const allTypes = extractAllTypes(program, filePath);
1782
1959
  allTypesByFile.set(filePath, allTypes);
1783
1960
  for (const [name, info] of allTypes) {
@@ -2115,7 +2292,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
2115
2292
  }
2116
2293
  const idx = rel.lastIndexOf("/");
2117
2294
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
2118
- return path3.resolve(rootDir, dist, relDir, "zod.js");
2295
+ return path4.resolve(rootDir, dist, relDir, "zod.js");
2119
2296
  }
2120
2297
  function getRuntimeSchemaPath(filePath, dist, rootDir) {
2121
2298
  let rel = filePath.replace(/\\/g, "/");
@@ -2126,7 +2303,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
2126
2303
  }
2127
2304
  const idx = rel.lastIndexOf("/");
2128
2305
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
2129
- return path3.resolve(rootDir, dist, relDir, "zod.js");
2306
+ return path4.resolve(rootDir, dist, relDir, "zod.js");
2130
2307
  }
2131
2308
  function getHelpersImportPath(relDir) {
2132
2309
  if (!relDir) return `./${HELPERS_FILENAME}`;
@@ -2176,7 +2353,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
2176
2353
  }
2177
2354
  const fileEntries = [];
2178
2355
  for (const [filePath, fileSources] of sourcesByFile) {
2179
- const relFile = path3.relative(rootDir, filePath).replace(/\\/g, "/");
2356
+ const relFile = path4.relative(rootDir, filePath).replace(/\\/g, "/");
2180
2357
  const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
2181
2358
  const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
2182
2359
  let relForDir = relFile;
@@ -2191,7 +2368,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
2191
2368
  }
2192
2369
  const allSourceCode = fileEntries.map((e) => e.source).join("\n");
2193
2370
  if (usesCoerceHelpers(allSourceCode)) {
2194
- const helpersPath = path3.resolve(rootDir, dist, HELPERS_FILENAME);
2371
+ const helpersPath = path4.resolve(rootDir, dist, HELPERS_FILENAME);
2195
2372
  await writeSchemaFile(helpersPath, generateHelpersFileSource());
2196
2373
  }
2197
2374
  await Promise.all(
@@ -2199,22 +2376,22 @@ async function generateSchemaFiles(routes, rootDir, dist) {
2199
2376
  );
2200
2377
  }
2201
2378
  async function writeSchemaFile(outputPath, source) {
2202
- await fs2.mkdir(path3.dirname(outputPath), { recursive: true });
2203
- await fs2.writeFile(outputPath, source, "utf-8");
2379
+ await fs3.mkdir(path4.dirname(outputPath), { recursive: true });
2380
+ await fs3.writeFile(outputPath, source, "utf-8");
2204
2381
  }
2205
2382
 
2206
2383
  // src/cli/compileOnDemand.ts
2207
- import path7 from "path";
2208
- import fs6 from "fs";
2384
+ import path8 from "path";
2385
+ import fs7 from "fs";
2209
2386
 
2210
2387
  // src/cli/compileDevRoutes.ts
2211
- import path6 from "path";
2212
- import fs5 from "fs";
2388
+ import path7 from "path";
2389
+ import fs6 from "fs";
2213
2390
  import fg2 from "fast-glob";
2214
2391
 
2215
2392
  // src/cli/aliasPlugin.ts
2216
- import path5 from "path";
2217
- import fs4 from "fs";
2393
+ import path6 from "path";
2394
+ import fs5 from "fs";
2218
2395
 
2219
2396
  // src/utils/resolveAlias.ts
2220
2397
  function resolveAlias(specifier, config) {
@@ -2241,11 +2418,11 @@ function resolveAlias(specifier, config) {
2241
2418
 
2242
2419
  // src/utils/readTsconfig.ts
2243
2420
  import ts6 from "typescript";
2244
- import path4 from "path";
2245
- import fs3 from "fs";
2421
+ import path5 from "path";
2422
+ import fs4 from "fs";
2246
2423
  function readTsconfig(rootDir) {
2247
- const tsconfigPath = path4.resolve(rootDir, "tsconfig.json");
2248
- if (!fs3.existsSync(tsconfigPath)) return null;
2424
+ const tsconfigPath = path5.resolve(rootDir, "tsconfig.json");
2425
+ if (!fs4.existsSync(tsconfigPath)) return null;
2249
2426
  const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
2250
2427
  if (configFile.error || !configFile.config) return null;
2251
2428
  const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
@@ -2254,7 +2431,7 @@ function readTsconfig(rootDir) {
2254
2431
  if (!rawPaths) return null;
2255
2432
  const paths = {};
2256
2433
  for (const [pattern, targets] of Object.entries(rawPaths)) {
2257
- paths[pattern] = targets.map((t) => path4.resolve(baseUrl, t));
2434
+ paths[pattern] = targets.map((t) => path5.resolve(baseUrl, t));
2258
2435
  }
2259
2436
  return { baseUrl, paths };
2260
2437
  }
@@ -2267,29 +2444,29 @@ function toProdExtension(filePath) {
2267
2444
  return filePath;
2268
2445
  }
2269
2446
  function toProdImportPath(sourceFile, importer) {
2270
- const importerDir = path5.dirname(importer);
2271
- let rel = path5.relative(importerDir, sourceFile);
2272
- rel = rel.split(path5.sep).join("/");
2447
+ const importerDir = path6.dirname(importer);
2448
+ let rel = path6.relative(importerDir, sourceFile);
2449
+ rel = rel.split(path6.sep).join("/");
2273
2450
  if (!rel.startsWith(".")) rel = "./" + rel;
2274
2451
  return toProdExtension(rel);
2275
2452
  }
2276
2453
  function toRealPath(p) {
2277
2454
  try {
2278
- return fs4.realpathSync(p);
2455
+ return fs5.realpathSync(p);
2279
2456
  } catch {
2280
2457
  return p;
2281
2458
  }
2282
2459
  }
2283
2460
  function isInsideDir(filePath, dir) {
2284
- const rel = path5.relative(dir, filePath);
2285
- return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
2461
+ const rel = path6.relative(dir, filePath);
2462
+ return rel !== "" && !rel.startsWith("..") && !path6.isAbsolute(rel);
2286
2463
  }
2287
2464
  var APP_DIR = "src";
2288
2465
  function toStrippedProdImportPath(sourceFile, rootDir) {
2289
- const appDirAbs = toRealPath(path5.resolve(rootDir, APP_DIR));
2466
+ const appDirAbs = toRealPath(path6.resolve(rootDir, APP_DIR));
2290
2467
  const sourceReal = toRealPath(sourceFile);
2291
- let rel = path5.relative(appDirAbs, sourceReal);
2292
- rel = rel.split(path5.sep).join("/");
2468
+ let rel = path6.relative(appDirAbs, sourceReal);
2469
+ rel = rel.split(path6.sep).join("/");
2293
2470
  if (!rel.startsWith(".")) rel = "./" + rel;
2294
2471
  return toProdExtension(rel);
2295
2472
  }
@@ -2304,34 +2481,34 @@ var INDEX_EXTS = [
2304
2481
  "/index.cjs"
2305
2482
  ];
2306
2483
  function resolveRelativeSpecifier(importer, specifier) {
2307
- const importerDir = path5.dirname(importer);
2308
- const base = path5.resolve(importerDir, specifier);
2484
+ const importerDir = path6.dirname(importer);
2485
+ const base = path6.resolve(importerDir, specifier);
2309
2486
  if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
2310
- return fs4.existsSync(base) ? base : null;
2487
+ return fs5.existsSync(base) ? base : null;
2311
2488
  }
2312
2489
  if (/\.(ts|tsx|jsx)$/.test(specifier)) {
2313
- return fs4.existsSync(base) ? base : null;
2490
+ return fs5.existsSync(base) ? base : null;
2314
2491
  }
2315
2492
  for (const ext of SOURCE_EXTS) {
2316
2493
  const file = base + ext;
2317
- if (fs4.existsSync(file)) return file;
2494
+ if (fs5.existsSync(file)) return file;
2318
2495
  }
2319
2496
  for (const indexExt of INDEX_EXTS) {
2320
2497
  const file = base + indexExt;
2321
- if (fs4.existsSync(file)) return file;
2498
+ if (fs5.existsSync(file)) return file;
2322
2499
  }
2323
2500
  return null;
2324
2501
  }
2325
2502
  function createAliasPlugin(config, options) {
2326
2503
  const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
2327
- const appDirAbs = options?.rootDir ? toRealPath(path5.resolve(options.rootDir, APP_DIR)) : null;
2504
+ const appDirAbs = options?.rootDir ? toRealPath(path6.resolve(options.rootDir, APP_DIR)) : null;
2328
2505
  return {
2329
2506
  name: "faapi-alias",
2330
2507
  setup(build) {
2331
2508
  build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
2332
2509
  let source;
2333
2510
  try {
2334
- source = fs4.readFileSync(args.path, "utf8");
2511
+ source = fs5.readFileSync(args.path, "utf8");
2335
2512
  } catch {
2336
2513
  return void 0;
2337
2514
  }
@@ -2364,7 +2541,7 @@ function createAliasPlugin(config, options) {
2364
2541
  for (const candidate of candidates) {
2365
2542
  for (const ext of SOURCE_EXTS) {
2366
2543
  const file = candidate + ext;
2367
- if (fs4.existsSync(file)) {
2544
+ if (fs5.existsSync(file)) {
2368
2545
  modified = true;
2369
2546
  if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
2370
2547
  return `${prefix}${quote}${toStrippedProdImportPath(
@@ -2377,7 +2554,7 @@ function createAliasPlugin(config, options) {
2377
2554
  }
2378
2555
  for (const indexExt of INDEX_EXTS) {
2379
2556
  const file = candidate + indexExt;
2380
- if (fs4.existsSync(file)) {
2557
+ if (fs5.existsSync(file)) {
2381
2558
  modified = true;
2382
2559
  if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
2383
2560
  return `${prefix}${quote}${toStrippedProdImportPath(
@@ -2415,11 +2592,11 @@ async function compileDevRoutes(options) {
2415
2592
  if (entryPoints.length === 0) {
2416
2593
  return { compiledFiles: [] };
2417
2594
  }
2418
- const absDist = path6.resolve(rootDir, dist);
2419
- await fs5.promises.mkdir(absDist, { recursive: true });
2595
+ const absDist = path7.resolve(rootDir, dist);
2596
+ await fs6.promises.mkdir(absDist, { recursive: true });
2420
2597
  const plugins = buildAliasPlugins(rootDir);
2421
2598
  const esbuild = await import("esbuild");
2422
- const outbase = path6.resolve(rootDir, APP_DIR2);
2599
+ const outbase = path7.resolve(rootDir, APP_DIR2);
2423
2600
  const result = await esbuild.build({
2424
2601
  entryPoints,
2425
2602
  outdir: absDist,
@@ -2436,10 +2613,10 @@ async function compileDevRoutes(options) {
2436
2613
  if (result.outputFiles) {
2437
2614
  await Promise.all(
2438
2615
  result.outputFiles.map(async (file) => {
2439
- await fs5.promises.mkdir(path6.dirname(file.path), { recursive: true });
2616
+ await fs6.promises.mkdir(path7.dirname(file.path), { recursive: true });
2440
2617
  const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
2441
- await fs5.promises.writeFile(tmp, file.contents);
2442
- await fs5.promises.rename(tmp, file.path);
2618
+ await fs6.promises.writeFile(tmp, file.contents);
2619
+ await fs6.promises.rename(tmp, file.path);
2443
2620
  })
2444
2621
  );
2445
2622
  }
@@ -2449,8 +2626,8 @@ async function compileDevRoutes(options) {
2449
2626
  // src/cli/compileOnDemand.ts
2450
2627
  function isProductFresh(sourceAbsPath, productAbsPath) {
2451
2628
  try {
2452
- const srcStat = fs6.statSync(sourceAbsPath);
2453
- const prodStat = fs6.statSync(productAbsPath);
2629
+ const srcStat = fs7.statSync(sourceAbsPath);
2630
+ const prodStat = fs7.statSync(productAbsPath);
2454
2631
  return prodStat.mtimeMs >= srcStat.mtimeMs;
2455
2632
  } catch {
2456
2633
  return false;
@@ -2477,7 +2654,7 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
2477
2654
  if (state.compiledFiles.has(sourceAbsPath)) {
2478
2655
  return false;
2479
2656
  }
2480
- if (!fs6.existsSync(sourceAbsPath)) {
2657
+ if (!fs7.existsSync(sourceAbsPath)) {
2481
2658
  return false;
2482
2659
  }
2483
2660
  const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
@@ -2503,11 +2680,11 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
2503
2680
  }
2504
2681
  }
2505
2682
  function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
2506
- const rel = path7.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2683
+ const rel = path8.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2507
2684
  if (!rel.startsWith("src/")) return null;
2508
2685
  const relWithoutSrc = rel.slice(4);
2509
2686
  const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
2510
- return path7.resolve(rootDir, dist, jsRel);
2687
+ return path8.resolve(rootDir, dist, jsRel);
2511
2688
  }
2512
2689
  async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
2513
2690
  const inFlight = state.inFlightSchemaGenerations.get(schemaPath);
@@ -2519,9 +2696,9 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
2519
2696
  if (state.generatedSchemas.has(schemaPath)) {
2520
2697
  return false;
2521
2698
  }
2522
- const prodAbsPath = path7.resolve(rootDir, routeFilePath);
2699
+ const prodAbsPath = path8.resolve(rootDir, routeFilePath);
2523
2700
  const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
2524
- if (!fs6.existsSync(sourceAbsPath)) {
2701
+ if (!fs7.existsSync(sourceAbsPath)) {
2525
2702
  return false;
2526
2703
  }
2527
2704
  if (isProductFresh(sourceAbsPath, schemaPath)) {
@@ -2532,7 +2709,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
2532
2709
  if (fileRoutes.length === 0) {
2533
2710
  return false;
2534
2711
  }
2535
- const sourceRelPath = path7.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2712
+ const sourceRelPath = path8.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2536
2713
  const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
2537
2714
  const generatePromise = (async () => {
2538
2715
  await generateSchemaFiles(sourceRoutes, rootDir, dist);
@@ -2546,17 +2723,26 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
2546
2723
  state.inFlightSchemaGenerations.delete(schemaPath);
2547
2724
  }
2548
2725
  }
2726
+ var sourcePathCache = /* @__PURE__ */ new Map();
2549
2727
  function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
2550
- const rel = path7.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
2728
+ const cached = sourcePathCache.get(prodAbsPath);
2729
+ if (cached) return cached;
2730
+ const rel = path8.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
2551
2731
  let relWithoutDist = rel;
2552
2732
  if (relWithoutDist.startsWith(`${dist}/`)) {
2553
2733
  relWithoutDist = relWithoutDist.slice(dist.length + 1);
2554
2734
  }
2555
2735
  const srcRel = `src/${relWithoutDist}`;
2556
2736
  const tsRel = srcRel.replace(/\.js$/, ".ts");
2557
- const tsAbs = path7.resolve(rootDir, tsRel);
2558
- if (fs6.existsSync(tsAbs)) return tsAbs;
2559
- return path7.resolve(rootDir, srcRel);
2737
+ const tsAbs = path8.resolve(rootDir, tsRel);
2738
+ let result;
2739
+ if (fs7.existsSync(tsAbs)) {
2740
+ result = tsAbs;
2741
+ } else {
2742
+ result = path8.resolve(rootDir, srcRel);
2743
+ }
2744
+ sourcePathCache.set(prodAbsPath, result);
2745
+ return result;
2560
2746
  }
2561
2747
  function isDevOnDemandEnabled() {
2562
2748
  return state.enabled;
@@ -2609,9 +2795,9 @@ async function validateInput(schemaPath, method, inputType, input) {
2609
2795
  function mapZodIssues(error) {
2610
2796
  return error.issues.map((issue) => {
2611
2797
  const code = mapZodCode(issue.code, issue.message);
2612
- const path11 = issue.path.map(String).join(".") || "";
2798
+ const path12 = issue.path.map(String).join(".") || "";
2613
2799
  return {
2614
- path: path11,
2800
+ path: path12,
2615
2801
  code,
2616
2802
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
2617
2803
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -2659,45 +2845,96 @@ import {
2659
2845
  import { createSecureServer as createHttp2SecureServer } from "http2";
2660
2846
  import { readFileSync } from "fs";
2661
2847
  import { Readable as Readable2 } from "stream";
2662
- import path9 from "path";
2848
+ import path10 from "path";
2663
2849
 
2664
2850
  // src/router/matchRoute.ts
2665
- function matchRoute(routes, method, path11) {
2851
+ var httpIndexCache = /* @__PURE__ */ new WeakMap();
2852
+ var wsIndexCache = /* @__PURE__ */ new WeakMap();
2853
+ function getHttpIndex(routes) {
2854
+ let index = httpIndexCache.get(routes);
2855
+ if (index) return index;
2856
+ index = { static: /* @__PURE__ */ new Map(), methodsByStaticPath: /* @__PURE__ */ new Map(), dynamics: [] };
2666
2857
  for (const route of routes) {
2667
- if (route.method !== method) {
2668
- continue;
2669
- }
2670
- if (!route.isDynamic) {
2671
- if (route.urlPath === path11) {
2672
- return { route, params: {} };
2858
+ if (route.isDynamic) {
2859
+ index.dynamics.push(route);
2860
+ } else {
2861
+ index.static.set(`${route.method}|${route.urlPath}`, route);
2862
+ let methods = index.methodsByStaticPath.get(route.urlPath);
2863
+ if (!methods) {
2864
+ methods = /* @__PURE__ */ new Set();
2865
+ index.methodsByStaticPath.set(route.urlPath, methods);
2673
2866
  }
2867
+ methods.add(route.method);
2868
+ }
2869
+ }
2870
+ httpIndexCache.set(routes, index);
2871
+ return index;
2872
+ }
2873
+ function getWsIndex(routes) {
2874
+ let index = wsIndexCache.get(routes);
2875
+ if (index) return index;
2876
+ index = { static: /* @__PURE__ */ new Map(), dynamics: [] };
2877
+ for (const route of routes) {
2878
+ if (route.isDynamic) {
2879
+ index.dynamics.push(route);
2880
+ } else {
2881
+ index.static.set(route.urlPath, route);
2882
+ }
2883
+ }
2884
+ wsIndexCache.set(routes, index);
2885
+ return index;
2886
+ }
2887
+ function matchRoute(routes, method, path12) {
2888
+ const index = getHttpIndex(routes);
2889
+ const staticHit = index.static.get(`${method}|${path12}`);
2890
+ if (staticHit) {
2891
+ return { route: staticHit, params: {} };
2892
+ }
2893
+ for (const route of index.dynamics) {
2894
+ if (route.method !== method) {
2674
2895
  continue;
2675
2896
  }
2676
- const params = matchDynamicPath(route.urlPath, path11, route.paramNames, route.isCatchAll);
2897
+ const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
2677
2898
  if (params !== null) {
2678
2899
  return { route, params };
2679
2900
  }
2680
2901
  }
2681
2902
  return null;
2682
2903
  }
2683
- function matchWsRoute(wsRoutes, path11) {
2684
- for (const route of wsRoutes) {
2685
- if (!route.isDynamic) {
2686
- if (route.urlPath === path11) {
2687
- return { route, params: {} };
2688
- }
2689
- continue;
2690
- }
2691
- const params = matchDynamicPath(route.urlPath, path11, route.paramNames, route.isCatchAll);
2904
+ function matchWsRoute(wsRoutes, path12) {
2905
+ const index = getWsIndex(wsRoutes);
2906
+ const staticHit = index.static.get(path12);
2907
+ if (staticHit) {
2908
+ return { route: staticHit, params: {} };
2909
+ }
2910
+ for (const route of index.dynamics) {
2911
+ const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
2692
2912
  if (params !== null) {
2693
2913
  return { route, params };
2694
2914
  }
2695
2915
  }
2696
2916
  return null;
2697
2917
  }
2698
- function matchDynamicPath(pattern, path11, paramNames, isCatchAll) {
2918
+ function findAllowedMethods(routes, path12) {
2919
+ const index = getHttpIndex(routes);
2920
+ const methods = /* @__PURE__ */ new Set();
2921
+ const staticMethods = index.methodsByStaticPath.get(path12);
2922
+ if (staticMethods) {
2923
+ for (const method of staticMethods) {
2924
+ methods.add(method);
2925
+ }
2926
+ }
2927
+ for (const route of index.dynamics) {
2928
+ const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
2929
+ if (params !== null) {
2930
+ methods.add(route.method);
2931
+ }
2932
+ }
2933
+ return Array.from(methods);
2934
+ }
2935
+ function matchDynamicPath(pattern, path12, paramNames, isCatchAll) {
2699
2936
  const patternSegments = pattern.split("/").filter(Boolean);
2700
- const pathSegments = path11.split("/").filter(Boolean);
2937
+ const pathSegments = path12.split("/").filter(Boolean);
2701
2938
  if (isCatchAll) {
2702
2939
  const nonCatchAllCount = patternSegments.length - 1;
2703
2940
  if (pathSegments.length <= nonCatchAllCount) {
@@ -2742,9 +2979,6 @@ function matchDynamicPath(pattern, path11, paramNames, isCatchAll) {
2742
2979
  return params;
2743
2980
  }
2744
2981
 
2745
- // src/loader/loadRouteModule.ts
2746
- import fs7 from "fs";
2747
-
2748
2982
  // src/loader/resolveExports.ts
2749
2983
  function resolveExport(module, exportName) {
2750
2984
  if (exportName in module && typeof module[exportName] !== "undefined") {
@@ -2775,7 +3009,7 @@ async function loadRouteModule(filePath, method, rootDir) {
2775
3009
  const dist = getDevDist();
2776
3010
  if (dist) {
2777
3011
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
2778
- if (sourcePath && fs7.existsSync(sourcePath)) {
3012
+ if (sourcePath) {
2779
3013
  try {
2780
3014
  await ensureCompiled(sourcePath, rootDir, dist);
2781
3015
  } catch (compileErr) {
@@ -2840,7 +3074,7 @@ async function parseMultipart(request) {
2840
3074
  }
2841
3075
 
2842
3076
  // src/runtime/resolveInput.ts
2843
- async function resolveInput(method, request) {
3077
+ async function resolveInputFromUrl(method, request, url) {
2844
3078
  const inputType = getInputTypeForMethod(method);
2845
3079
  if (inputType === "body") {
2846
3080
  const contentType = request.headers.get("content-type") ?? "";
@@ -2875,7 +3109,6 @@ async function resolveInput(method, request) {
2875
3109
  }
2876
3110
  return result.data;
2877
3111
  }
2878
- const url = new URL(request.url);
2879
3112
  return queryToObject(url.searchParams);
2880
3113
  }
2881
3114
 
@@ -2904,11 +3137,13 @@ async function sendNodeResponse(response, res) {
2904
3137
  }
2905
3138
 
2906
3139
  // src/utils/getClientIp.ts
2907
- function getClientIp(req) {
2908
- const xff = req.headers["x-forwarded-for"];
2909
- if (typeof xff === "string" && xff.length > 0) {
2910
- const first = xff.split(",")[0]?.trim();
2911
- if (first) return first;
3140
+ function getClientIp(req, trustedProxy = false) {
3141
+ if (trustedProxy) {
3142
+ const xff = req.headers["x-forwarded-for"];
3143
+ if (typeof xff === "string" && xff.length > 0) {
3144
+ const first = xff.split(",")[0]?.trim();
3145
+ if (first) return first;
3146
+ }
2912
3147
  }
2913
3148
  const remote = req.socket?.remoteAddress;
2914
3149
  if (remote) {
@@ -3083,7 +3318,7 @@ function logger(options = {}) {
3083
3318
  // src/server/handleWsUpgrade.ts
3084
3319
  import fs8 from "fs";
3085
3320
  import { WebSocketServer, WebSocket } from "ws";
3086
- import path8 from "path";
3321
+ import path9 from "path";
3087
3322
 
3088
3323
  // src/server/serverUtils.ts
3089
3324
  function nodeHttpToWebHeaders(req) {
@@ -3197,7 +3432,7 @@ async function sendResponseToSocket(socket, response) {
3197
3432
  socket.destroy();
3198
3433
  }
3199
3434
  function attachWebSocket(options) {
3200
- const { server, routesRef, rootDir, config, globalMiddlewares } = options;
3435
+ const { server, routesRef, rootDir, config, globalMiddlewares, trustedProxy = false } = options;
3201
3436
  const wss = new WebSocketServer({ noServer: true });
3202
3437
  server.on("upgrade", async (req, socket, head) => {
3203
3438
  const currentWsRoutes = routesRef.wsCurrent;
@@ -3213,13 +3448,13 @@ function attachWebSocket(options) {
3213
3448
  const host = req.headers.host ?? "localhost";
3214
3449
  const url = `http://${host}${req.url ?? "/"}`;
3215
3450
  const request = new Request(url, { method: "GET", headers });
3216
- const ctx = createContext(request, params, config, getClientIp(req));
3451
+ const ctx = createContext(request, params, config, getClientIp(req, trustedProxy));
3217
3452
  const meta = ctx.meta;
3218
3453
  let upgraded = false;
3219
3454
  const finalHandler = async () => {
3220
3455
  let handlers;
3221
3456
  try {
3222
- const absoluteFilePath = path8.resolve(rootDir, route.filePath);
3457
+ const absoluteFilePath = path9.resolve(rootDir, route.filePath);
3223
3458
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
3224
3459
  } catch (err) {
3225
3460
  const reason = err instanceof Error ? err.message : String(err);
@@ -3282,16 +3517,26 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
3282
3517
  const headers = nodeHttpToWebHeaders(req);
3283
3518
  const method = req.method ?? "GET";
3284
3519
  if (method === "GET" || method === "HEAD") {
3285
- return new Request(url.toString(), { method, headers });
3520
+ return { request: new Request(url.toString(), { method, headers }), url };
3521
+ }
3522
+ const contentLength = req.headers["content-length"];
3523
+ if (contentLength !== void 0) {
3524
+ const declared = Number(Array.isArray(contentLength) ? contentLength[0] : contentLength);
3525
+ if (Number.isFinite(declared) && declared > bodyLimit) {
3526
+ throw new PayloadTooLargeError(bodyLimit);
3527
+ }
3286
3528
  }
3287
3529
  const stream = Readable2.toWeb(req);
3288
3530
  const limitedStream = limitStreamSize(stream, bodyLimit);
3289
- return new Request(url.toString(), {
3290
- method,
3291
- headers,
3292
- body: limitedStream,
3293
- duplex: "half"
3294
- });
3531
+ return {
3532
+ request: new Request(url.toString(), {
3533
+ method,
3534
+ headers,
3535
+ body: limitedStream,
3536
+ duplex: "half"
3537
+ }),
3538
+ url
3539
+ };
3295
3540
  }
3296
3541
  function limitStreamSize(stream, maxSize) {
3297
3542
  let totalSize = 0;
@@ -3343,22 +3588,6 @@ function limitStreamSize(stream, maxSize) {
3343
3588
  }
3344
3589
  });
3345
3590
  }
3346
- function findAllowedMethods(routes, path11) {
3347
- const methods = /* @__PURE__ */ new Set();
3348
- for (const route of routes) {
3349
- if (route.urlPath === path11) {
3350
- methods.add(route.method);
3351
- continue;
3352
- }
3353
- if (route.isDynamic) {
3354
- const params = matchDynamicPath(route.urlPath, path11, route.paramNames, route.isCatchAll);
3355
- if (params !== null) {
3356
- methods.add(route.method);
3357
- }
3358
- }
3359
- }
3360
- return Array.from(methods);
3361
- }
3362
3591
  function createServer(options) {
3363
3592
  const {
3364
3593
  routes,
@@ -3373,7 +3602,8 @@ function createServer(options) {
3373
3602
  helmet: helmetOption,
3374
3603
  logger: loggerOption,
3375
3604
  bodyLimit = DEFAULT_BODY_LIMIT,
3376
- http2: http2Option
3605
+ http2: http2Option,
3606
+ trustedProxy = false
3377
3607
  } = options;
3378
3608
  const routesRef = { current: routes, wsCurrent: wsRoutes ?? [] };
3379
3609
  const configMiddlewares = [];
@@ -3385,6 +3615,10 @@ function createServer(options) {
3385
3615
  }
3386
3616
  const loggerMiddlewareInst = loggerOption === false ? null : loggerOption === true || loggerOption === void 0 ? logger() : logger(loggerOption);
3387
3617
  if (loggerMiddlewareInst) configMiddlewares.push(loggerMiddlewareInst);
3618
+ const outerMiddlewares = [...configMiddlewares];
3619
+ if (globalMiddlewares && globalMiddlewares.length > 0) {
3620
+ outerMiddlewares.push(...globalMiddlewares);
3621
+ }
3388
3622
  const server = (() => {
3389
3623
  if (http2Option) {
3390
3624
  const h2Opts = typeof http2Option === "object" ? http2Option : {};
@@ -3404,29 +3638,29 @@ function createServer(options) {
3404
3638
  dist,
3405
3639
  req,
3406
3640
  res,
3407
- configMiddlewares,
3641
+ outerMiddlewares,
3408
3642
  onError,
3409
3643
  config,
3410
- globalMiddlewares,
3411
3644
  globalInjectors,
3412
- bodyLimit
3645
+ bodyLimit,
3646
+ trustedProxy
3413
3647
  ).catch(() => {
3414
3648
  res.statusCode = 500;
3415
3649
  res.end();
3416
3650
  });
3417
3651
  });
3418
3652
  if (routesRef.wsCurrent.length > 0) {
3419
- attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares });
3653
+ attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares, trustedProxy });
3420
3654
  }
3421
3655
  return { server, routesRef };
3422
3656
  }
3423
- function prepareRequest(req, config, bodyLimit) {
3424
- const request = toWebRequest(req, bodyLimit);
3657
+ function prepareRequest(req, config, bodyLimit, trustedProxy) {
3658
+ const { request, url } = toWebRequest(req, bodyLimit);
3425
3659
  const method = request.method.toUpperCase();
3426
- const urlPath = new URL(request.url).pathname;
3427
- const ctx = createContext(request, {}, config, getClientIp(req));
3660
+ const urlPath = url.pathname;
3661
+ const ctx = createContextFromUrl(request, url, {}, config, getClientIp(req, trustedProxy));
3428
3662
  const meta = ctx.meta;
3429
- return { request, ctx, meta, method, urlPath };
3663
+ return { request, url, ctx, meta, method, urlPath };
3430
3664
  }
3431
3665
  function resolveRouteOrThrow(routes, method, urlPath) {
3432
3666
  const match = matchRoute(routes, method, urlPath);
@@ -3438,14 +3672,14 @@ function resolveRouteOrThrow(routes, method, urlPath) {
3438
3672
  throw new RouteNotFoundError(urlPath);
3439
3673
  }
3440
3674
  function createRoutePipeline(opts) {
3441
- const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
3675
+ const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;
3442
3676
  return async () => {
3443
3677
  const match = resolveRouteOrThrow(routes, method, urlPath);
3444
3678
  ctx.params = match.params;
3445
3679
  const { route } = match;
3446
- const absoluteFilePath = path9.resolve(rootDir, route.filePath);
3680
+ const absoluteFilePath = path10.resolve(rootDir, route.filePath);
3447
3681
  const routeModule = await loadRouteModule(absoluteFilePath, route.method, rootDir);
3448
- const input = await resolveInput(route.method, request);
3682
+ const input = await resolveInputFromUrl(route.method, request, url);
3449
3683
  const inputType = getInputTypeForMethod(route.method);
3450
3684
  const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
3451
3685
  if (isDevOnDemandEnabled()) {
@@ -3477,33 +3711,33 @@ async function sendSuccessResponse(response, res) {
3477
3711
  await sendNodeResponse(response, res);
3478
3712
  }
3479
3713
  async function sendErrorResponse(err, meta, res, onError, ctx) {
3480
- await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx.config), meta), res);
3481
- if (onError) {
3714
+ await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx?.config), meta), res);
3715
+ if (onError && ctx) {
3482
3716
  try {
3483
3717
  await onError(err, ctx);
3484
3718
  } catch {
3485
3719
  }
3486
3720
  }
3487
3721
  }
3488
- async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
3489
- const { request, ctx, meta, method, urlPath } = prepareRequest(req, config, bodyLimit);
3490
- const routePipeline = createRoutePipeline({
3491
- routes,
3492
- method,
3493
- urlPath,
3494
- ctx,
3495
- request,
3496
- rootDir,
3497
- dist,
3498
- globalMiddlewares,
3499
- globalInjectors
3500
- });
3501
- const outerMiddlewares = [];
3502
- if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
3503
- if (globalMiddlewares && globalMiddlewares.length > 0) {
3504
- outerMiddlewares.push(...globalMiddlewares);
3505
- }
3722
+ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy) {
3723
+ let meta = { headers: {}, setCookies: [] };
3724
+ let ctx;
3506
3725
  try {
3726
+ const prepared = prepareRequest(req, config, bodyLimit, trustedProxy);
3727
+ ctx = prepared.ctx;
3728
+ meta = prepared.meta;
3729
+ const { request, url, method, urlPath } = prepared;
3730
+ const routePipeline = createRoutePipeline({
3731
+ routes,
3732
+ method,
3733
+ urlPath,
3734
+ url,
3735
+ ctx,
3736
+ request,
3737
+ rootDir,
3738
+ dist,
3739
+ globalInjectors
3740
+ });
3507
3741
  const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
3508
3742
  await sendSuccessResponse(response, res);
3509
3743
  } catch (err) {
@@ -3530,7 +3764,7 @@ async function createTestServer(options) {
3530
3764
  } = options;
3531
3765
  const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);
3532
3766
  const sorted = sortRoutes(routes);
3533
- const schemaDist = dist ? path10.isAbsolute(dist) ? dist : path10.resolve(rootDir, dist) : await fs9.mkdtemp(path10.join(os.tmpdir(), "faapi-test-schema-"));
3767
+ const schemaDist = dist ? path11.isAbsolute(dist) ? dist : path11.resolve(rootDir, dist) : await fs9.mkdtemp(path11.join(os.tmpdir(), "faapi-test-schema-"));
3534
3768
  await generateSchemaFiles(sorted, rootDir, schemaDist);
3535
3769
  const { server } = createServer({
3536
3770
  routes: sorted,