@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/index.js CHANGED
@@ -10,35 +10,143 @@ var __export = (target, all) => {
10
10
 
11
11
  // src/ast/createProgram.ts
12
12
  import ts from "typescript";
13
+ import fs from "fs";
14
+ import path from "path";
13
15
  function invalidateProgramCache() {
14
16
  programCache.clear();
17
+ tsConfigCache.clear();
18
+ }
19
+ function findTsConfig(filePath) {
20
+ let dir = path.dirname(filePath);
21
+ const root = path.parse(dir).root;
22
+ while (true) {
23
+ const candidate = path.join(dir, "tsconfig.json");
24
+ if (fs.existsSync(candidate)) {
25
+ return candidate;
26
+ }
27
+ if (dir === root) return null;
28
+ const parent = path.dirname(dir);
29
+ if (parent === dir) return null;
30
+ dir = parent;
31
+ }
32
+ }
33
+ function parseTsConfig(tsconfigPath) {
34
+ const cached = tsConfigCache.get(tsconfigPath);
35
+ if (cached) return cached;
36
+ const result = { fileNames: [] };
37
+ try {
38
+ const configFile = ts.readConfigFile(tsconfigPath, (p) => fs.readFileSync(p, "utf-8"));
39
+ if (configFile.error) {
40
+ tsConfigCache.set(tsconfigPath, result);
41
+ return result;
42
+ }
43
+ const config = configFile.config ?? {};
44
+ const basePath = path.dirname(tsconfigPath);
45
+ const parsed = ts.parseJsonConfigFileContent(
46
+ config,
47
+ ts.sys,
48
+ basePath,
49
+ /* existingOptions */
50
+ void 0,
51
+ tsconfigPath
52
+ );
53
+ result.fileNames = parsed.fileNames;
54
+ if (parsed.options.module !== void 0) {
55
+ result.module = parsed.options.module;
56
+ }
57
+ if (parsed.options.moduleResolution !== void 0) {
58
+ result.moduleResolution = parsed.options.moduleResolution;
59
+ }
60
+ } catch {
61
+ }
62
+ tsConfigCache.set(tsconfigPath, result);
63
+ return result;
15
64
  }
16
65
  function createProgram(filePath) {
17
66
  const cached = programCache.get(filePath);
18
67
  if (cached) {
19
68
  return cached;
20
69
  }
21
- const program = ts.createProgram([filePath], {
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) {
109
+ const options = {
22
110
  strict: true,
23
111
  target: ts.ScriptTarget.ES2022,
24
112
  module: ts.ModuleKind.NodeNext,
25
113
  moduleResolution: ts.ModuleResolutionKind.NodeNext,
26
114
  skipLibCheck: true,
27
115
  noEmit: true
28
- });
29
- programCache.set(filePath, program);
30
- return program;
116
+ };
117
+ const rootNames = [...entryFiles];
118
+ if (tsconfigPath) {
119
+ const tsOptions = parseTsConfig(tsconfigPath);
120
+ if (tsOptions.module !== void 0) {
121
+ options.module = tsOptions.module;
122
+ }
123
+ if (tsOptions.moduleResolution !== void 0) {
124
+ options.moduleResolution = tsOptions.moduleResolution;
125
+ }
126
+ if (tsOptions.fileNames.length > 0) {
127
+ for (const fileName of tsOptions.fileNames) {
128
+ if (!rootNames.includes(fileName)) {
129
+ rootNames.push(fileName);
130
+ }
131
+ }
132
+ }
133
+ }
134
+ return ts.createProgram(rootNames, options);
31
135
  }
32
- var programCache;
136
+ var programCache, tsConfigCache;
33
137
  var init_createProgram = __esm({
34
138
  "src/ast/createProgram.ts"() {
35
139
  "use strict";
36
140
  programCache = /* @__PURE__ */ new Map();
141
+ tsConfigCache = /* @__PURE__ */ new Map();
37
142
  }
38
143
  });
39
144
 
40
145
  // src/ast/resolveTypeNode.ts
41
146
  import ts2 from "typescript";
147
+ function setProgramContext(program) {
148
+ currentProgram = program;
149
+ }
42
150
  function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
43
151
  const kind = typeNode.kind;
44
152
  switch (kind) {
@@ -351,11 +459,69 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
351
459
  if (ts2.isEnumDeclaration(declaration)) {
352
460
  return resolveEnumDeclaration(declaration);
353
461
  }
462
+ if (ts2.isImportSpecifier(declaration) || ts2.isImportClause(declaration)) {
463
+ const resolved = resolveImportAlias(typeNode, symbol, checker, visited);
464
+ if (resolved) return resolved;
465
+ }
354
466
  }
355
467
  }
356
468
  }
357
469
  throw new SchemaExtractionError(typeNode.getText(), `\u65E0\u6CD5\u89E3\u6790\u7684\u5F15\u7528\u7C7B\u578B "${typeName}"`);
358
470
  }
471
+ function resolveImportAlias(typeNode, symbol, checker, visited) {
472
+ const typeName = typeNode.typeName.getText();
473
+ try {
474
+ const aliased = checker.getAliasedSymbol(symbol);
475
+ if (aliased && aliased.declarations && aliased.declarations.length > 0) {
476
+ const decl = aliased.declarations[0];
477
+ if (ts2.isInterfaceDeclaration(decl)) {
478
+ return resolveInterfaceDeclaration(decl, checker, visited);
479
+ }
480
+ if (ts2.isTypeAliasDeclaration(decl)) {
481
+ return resolveTypeNode(decl.type, checker, visited);
482
+ }
483
+ if (ts2.isEnumDeclaration(decl)) {
484
+ return resolveEnumDeclaration(decl);
485
+ }
486
+ }
487
+ } catch {
488
+ }
489
+ const program = currentProgram;
490
+ if (!program) return null;
491
+ const allSFs = program.getSourceFiles();
492
+ for (const sourceFile of allSFs) {
493
+ if (sourceFile.fileName.includes("/node_modules/") || sourceFile.fileName.includes("typescript/lib/")) {
494
+ continue;
495
+ }
496
+ const found = findTopLevelDecl(sourceFile, typeName);
497
+ if (found) {
498
+ if (found.kind === "interface") {
499
+ return resolveInterfaceDeclaration(found.node, checker, visited);
500
+ }
501
+ if (found.kind === "typeAlias") {
502
+ return resolveTypeNode(found.node.type, checker, visited);
503
+ }
504
+ if (found.kind === "enum") {
505
+ return resolveEnumDeclaration(found.node);
506
+ }
507
+ }
508
+ }
509
+ return null;
510
+ }
511
+ function findTopLevelDecl(sourceFile, typeName) {
512
+ let found = null;
513
+ ts2.forEachChild(sourceFile, (node) => {
514
+ if (found) return;
515
+ if (ts2.isInterfaceDeclaration(node) && node.name.text === typeName) {
516
+ found = { kind: "interface", node };
517
+ } else if (ts2.isTypeAliasDeclaration(node) && node.name.text === typeName) {
518
+ found = { kind: "typeAlias", node };
519
+ } else if (ts2.isEnumDeclaration(node) && node.name.text === typeName) {
520
+ found = { kind: "enum", node };
521
+ }
522
+ });
523
+ return found;
524
+ }
359
525
  function resolveEnumDeclaration(node) {
360
526
  const members = [];
361
527
  let nextNumericValue = 0;
@@ -534,10 +700,11 @@ function validateConstraints(constraints, type, fieldName) {
534
700
  }
535
701
  }
536
702
  }
537
- var SchemaExtractionError, NUMBER_CONSTRAINT_KINDS, LENGTH_CONSTRAINT_KINDS, STRING_FORMAT_CONSTRAINT_KINDS;
703
+ var currentProgram, SchemaExtractionError, NUMBER_CONSTRAINT_KINDS, LENGTH_CONSTRAINT_KINDS, STRING_FORMAT_CONSTRAINT_KINDS;
538
704
  var init_resolveTypeNode = __esm({
539
705
  "src/ast/resolveTypeNode.ts"() {
540
706
  "use strict";
707
+ currentProgram = null;
541
708
  SchemaExtractionError = class extends Error {
542
709
  constructor(typeText, reason, options) {
543
710
  super(`\u65E0\u6CD5\u89E3\u6790\u7C7B\u578B "${typeText}": ${reason}`, options);
@@ -577,80 +744,90 @@ function extractTypeInfo(program, filePath, typeName) {
577
744
  const sourceFile = program.getSourceFile(filePath);
578
745
  if (!sourceFile) return null;
579
746
  const checker = program.getTypeChecker();
580
- let result = null;
581
- ts3.forEachChild(sourceFile, (node) => {
582
- if (result) return;
583
- if (ts3.isInterfaceDeclaration(node) && node.name.text === typeName) {
584
- const visited = /* @__PURE__ */ new Set();
585
- visited.add(typeName);
586
- const runtimeType = withFileContext(
587
- filePath,
588
- typeName,
589
- () => resolveInterfaceDeclaration(node, checker, visited)
590
- );
591
- result = {
592
- name: typeName,
593
- properties: runtimeType.kind === "object" ? runtimeType.properties : [],
594
- runtimeType
595
- };
596
- return;
597
- }
598
- if (ts3.isTypeAliasDeclaration(node) && node.name.text === typeName) {
599
- const visited = /* @__PURE__ */ new Set();
600
- visited.add(typeName);
601
- const runtimeType = withFileContext(
602
- filePath,
603
- typeName,
604
- () => resolveTypeNode(node.type, checker, visited)
605
- );
606
- result = {
607
- name: typeName,
608
- properties: runtimeType.kind === "object" ? runtimeType.properties : [],
609
- runtimeType
610
- };
611
- return;
612
- }
613
- });
614
- return result;
747
+ setProgramContext(program);
748
+ try {
749
+ let result = null;
750
+ ts3.forEachChild(sourceFile, (node) => {
751
+ if (result) return;
752
+ if (ts3.isInterfaceDeclaration(node) && node.name.text === typeName) {
753
+ const visited = /* @__PURE__ */ new Set();
754
+ visited.add(typeName);
755
+ const runtimeType = withFileContext(
756
+ filePath,
757
+ typeName,
758
+ () => resolveInterfaceDeclaration(node, checker, visited)
759
+ );
760
+ result = {
761
+ name: typeName,
762
+ properties: runtimeType.kind === "object" ? runtimeType.properties : [],
763
+ runtimeType
764
+ };
765
+ return;
766
+ }
767
+ if (ts3.isTypeAliasDeclaration(node) && node.name.text === typeName) {
768
+ const visited = /* @__PURE__ */ new Set();
769
+ visited.add(typeName);
770
+ const runtimeType = withFileContext(
771
+ filePath,
772
+ typeName,
773
+ () => resolveTypeNode(node.type, checker, visited)
774
+ );
775
+ result = {
776
+ name: typeName,
777
+ properties: runtimeType.kind === "object" ? runtimeType.properties : [],
778
+ runtimeType
779
+ };
780
+ return;
781
+ }
782
+ });
783
+ return result;
784
+ } finally {
785
+ setProgramContext(null);
786
+ }
615
787
  }
616
788
  function extractAllTypes(program, filePath) {
617
789
  const sourceFile = program.getSourceFile(filePath);
618
790
  if (!sourceFile) return /* @__PURE__ */ new Map();
619
791
  const checker = program.getTypeChecker();
620
- const result = /* @__PURE__ */ new Map();
621
- ts3.forEachChild(sourceFile, (node) => {
622
- if (ts3.isInterfaceDeclaration(node)) {
623
- const visited = /* @__PURE__ */ new Set();
624
- visited.add(node.name.text);
625
- const runtimeType = withFileContext(
626
- filePath,
627
- node.name.text,
628
- () => resolveInterfaceDeclaration(node, checker, visited)
629
- );
630
- result.set(node.name.text, {
631
- name: node.name.text,
632
- properties: runtimeType.kind === "object" ? runtimeType.properties : [],
633
- runtimeType
634
- });
635
- return;
636
- }
637
- if (ts3.isTypeAliasDeclaration(node)) {
638
- const visited = /* @__PURE__ */ new Set();
639
- visited.add(node.name.text);
640
- const runtimeType = withFileContext(
641
- filePath,
642
- node.name.text,
643
- () => resolveTypeNode(node.type, checker, visited)
644
- );
645
- result.set(node.name.text, {
646
- name: node.name.text,
647
- properties: runtimeType.kind === "object" ? runtimeType.properties : [],
648
- runtimeType
649
- });
650
- return;
651
- }
652
- });
653
- return result;
792
+ setProgramContext(program);
793
+ try {
794
+ const result = /* @__PURE__ */ new Map();
795
+ ts3.forEachChild(sourceFile, (node) => {
796
+ if (ts3.isInterfaceDeclaration(node)) {
797
+ const visited = /* @__PURE__ */ new Set();
798
+ visited.add(node.name.text);
799
+ const runtimeType = withFileContext(
800
+ filePath,
801
+ node.name.text,
802
+ () => resolveInterfaceDeclaration(node, checker, visited)
803
+ );
804
+ result.set(node.name.text, {
805
+ name: node.name.text,
806
+ properties: runtimeType.kind === "object" ? runtimeType.properties : [],
807
+ runtimeType
808
+ });
809
+ return;
810
+ }
811
+ if (ts3.isTypeAliasDeclaration(node)) {
812
+ const visited = /* @__PURE__ */ new Set();
813
+ visited.add(node.name.text);
814
+ const runtimeType = withFileContext(
815
+ filePath,
816
+ node.name.text,
817
+ () => resolveTypeNode(node.type, checker, visited)
818
+ );
819
+ result.set(node.name.text, {
820
+ name: node.name.text,
821
+ properties: runtimeType.kind === "object" ? runtimeType.properties : [],
822
+ runtimeType
823
+ });
824
+ return;
825
+ }
826
+ });
827
+ return result;
828
+ } finally {
829
+ setProgramContext(null);
830
+ }
654
831
  }
655
832
  function withFileContext(filePath, typeName, fn) {
656
833
  try {
@@ -851,11 +1028,11 @@ var init_analyzeInjection = __esm({
851
1028
  });
852
1029
 
853
1030
  // src/cli/collectRouteSchemaSources.ts
854
- import path from "path";
1031
+ import path2 from "path";
855
1032
  function collectRouteSchemaSources(routes, rootDir) {
856
1033
  const methodsByFile = /* @__PURE__ */ new Map();
857
1034
  for (const route of routes) {
858
- const filePath = rootDir ? path.resolve(rootDir, route.filePath) : route.filePath;
1035
+ const filePath = rootDir ? path2.resolve(rootDir, route.filePath) : route.filePath;
859
1036
  let entry = methodsByFile.get(filePath);
860
1037
  if (!entry) {
861
1038
  entry = { urlPath: route.urlPath, methods: /* @__PURE__ */ new Set() };
@@ -863,12 +1040,11 @@ function collectRouteSchemaSources(routes, rootDir) {
863
1040
  }
864
1041
  entry.methods.add(route.method);
865
1042
  }
866
- const programByFile = /* @__PURE__ */ new Map();
1043
+ const programByFile = createPrograms([...methodsByFile.keys()]);
867
1044
  const allTypesByFile = /* @__PURE__ */ new Map();
868
1045
  const mergedAllTypes = /* @__PURE__ */ new Map();
869
1046
  for (const filePath of methodsByFile.keys()) {
870
- const program = createProgram(filePath);
871
- programByFile.set(filePath, program);
1047
+ const program = programByFile.get(filePath);
872
1048
  const allTypes = extractAllTypes(program, filePath);
873
1049
  allTypesByFile.set(filePath, allTypes);
874
1050
  for (const [name, info] of allTypes) {
@@ -1223,8 +1399,8 @@ __export(generateSchemaFiles_exports, {
1223
1399
  getRuntimeSchemaPath: () => getRuntimeSchemaPath,
1224
1400
  getSchemaOutputPath: () => getSchemaOutputPath
1225
1401
  });
1226
- import path5 from "path";
1227
- import fs4 from "fs/promises";
1402
+ import path6 from "path";
1403
+ import fs5 from "fs/promises";
1228
1404
  function getSchemaOutputPath(sourceFile, dist, rootDir) {
1229
1405
  let rel = sourceFile.replace(/\\/g, "/");
1230
1406
  if (rel.startsWith("src/")) {
@@ -1232,7 +1408,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
1232
1408
  }
1233
1409
  const idx = rel.lastIndexOf("/");
1234
1410
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
1235
- return path5.resolve(rootDir, dist, relDir, "zod.js");
1411
+ return path6.resolve(rootDir, dist, relDir, "zod.js");
1236
1412
  }
1237
1413
  function getRuntimeSchemaPath(filePath, dist, rootDir) {
1238
1414
  let rel = filePath.replace(/\\/g, "/");
@@ -1243,7 +1419,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
1243
1419
  }
1244
1420
  const idx = rel.lastIndexOf("/");
1245
1421
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
1246
- return path5.resolve(rootDir, dist, relDir, "zod.js");
1422
+ return path6.resolve(rootDir, dist, relDir, "zod.js");
1247
1423
  }
1248
1424
  function getHelpersImportPath(relDir) {
1249
1425
  if (!relDir) return `./${HELPERS_FILENAME}`;
@@ -1293,7 +1469,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
1293
1469
  }
1294
1470
  const fileEntries = [];
1295
1471
  for (const [filePath, fileSources] of sourcesByFile) {
1296
- const relFile = path5.relative(rootDir, filePath).replace(/\\/g, "/");
1472
+ const relFile = path6.relative(rootDir, filePath).replace(/\\/g, "/");
1297
1473
  const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
1298
1474
  const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
1299
1475
  let relForDir = relFile;
@@ -1308,7 +1484,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
1308
1484
  }
1309
1485
  const allSourceCode = fileEntries.map((e) => e.source).join("\n");
1310
1486
  if (usesCoerceHelpers(allSourceCode)) {
1311
- const helpersPath = path5.resolve(rootDir, dist, HELPERS_FILENAME);
1487
+ const helpersPath = path6.resolve(rootDir, dist, HELPERS_FILENAME);
1312
1488
  await writeSchemaFile(helpersPath, generateHelpersFileSource());
1313
1489
  }
1314
1490
  await Promise.all(
@@ -1316,8 +1492,8 @@ async function generateSchemaFiles(routes, rootDir, dist) {
1316
1492
  );
1317
1493
  }
1318
1494
  async function writeSchemaFile(outputPath, source) {
1319
- await fs4.mkdir(path5.dirname(outputPath), { recursive: true });
1320
- await fs4.writeFile(outputPath, source, "utf-8");
1495
+ await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
1496
+ await fs5.writeFile(outputPath, source, "utf-8");
1321
1497
  }
1322
1498
  var init_generateSchemaFiles = __esm({
1323
1499
  "src/cli/generateSchemaFiles.ts"() {
@@ -1422,7 +1598,7 @@ function listSkills() {
1422
1598
  }
1423
1599
 
1424
1600
  // src/loader/loadAgentModule.ts
1425
- import fs6 from "fs";
1601
+ import fs7 from "fs";
1426
1602
 
1427
1603
  // src/loader/resolveExports.ts
1428
1604
  function resolveExport(module, exportName) {
@@ -1468,17 +1644,17 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
1468
1644
  }
1469
1645
 
1470
1646
  // src/cli/compileOnDemand.ts
1471
- import path6 from "path";
1472
- import fs5 from "fs";
1647
+ import path7 from "path";
1648
+ import fs6 from "fs";
1473
1649
 
1474
1650
  // src/cli/compileDevRoutes.ts
1475
- import path4 from "path";
1476
- import fs3 from "fs";
1651
+ import path5 from "path";
1652
+ import fs4 from "fs";
1477
1653
  import fg from "fast-glob";
1478
1654
 
1479
1655
  // src/cli/aliasPlugin.ts
1480
- import path3 from "path";
1481
- import fs2 from "fs";
1656
+ import path4 from "path";
1657
+ import fs3 from "fs";
1482
1658
 
1483
1659
  // src/utils/resolveAlias.ts
1484
1660
  function resolveAlias(specifier, config) {
@@ -1505,11 +1681,11 @@ function resolveAlias(specifier, config) {
1505
1681
 
1506
1682
  // src/utils/readTsconfig.ts
1507
1683
  import ts6 from "typescript";
1508
- import path2 from "path";
1509
- import fs from "fs";
1684
+ import path3 from "path";
1685
+ import fs2 from "fs";
1510
1686
  function readTsconfig(rootDir) {
1511
- const tsconfigPath = path2.resolve(rootDir, "tsconfig.json");
1512
- if (!fs.existsSync(tsconfigPath)) return null;
1687
+ const tsconfigPath = path3.resolve(rootDir, "tsconfig.json");
1688
+ if (!fs2.existsSync(tsconfigPath)) return null;
1513
1689
  const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
1514
1690
  if (configFile.error || !configFile.config) return null;
1515
1691
  const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
@@ -1518,7 +1694,7 @@ function readTsconfig(rootDir) {
1518
1694
  if (!rawPaths) return null;
1519
1695
  const paths = {};
1520
1696
  for (const [pattern, targets] of Object.entries(rawPaths)) {
1521
- paths[pattern] = targets.map((t) => path2.resolve(baseUrl, t));
1697
+ paths[pattern] = targets.map((t) => path3.resolve(baseUrl, t));
1522
1698
  }
1523
1699
  return { baseUrl, paths };
1524
1700
  }
@@ -1531,29 +1707,29 @@ function toProdExtension(filePath) {
1531
1707
  return filePath;
1532
1708
  }
1533
1709
  function toProdImportPath(sourceFile, importer) {
1534
- const importerDir = path3.dirname(importer);
1535
- let rel = path3.relative(importerDir, sourceFile);
1536
- rel = rel.split(path3.sep).join("/");
1710
+ const importerDir = path4.dirname(importer);
1711
+ let rel = path4.relative(importerDir, sourceFile);
1712
+ rel = rel.split(path4.sep).join("/");
1537
1713
  if (!rel.startsWith(".")) rel = "./" + rel;
1538
1714
  return toProdExtension(rel);
1539
1715
  }
1540
1716
  function toRealPath(p) {
1541
1717
  try {
1542
- return fs2.realpathSync(p);
1718
+ return fs3.realpathSync(p);
1543
1719
  } catch {
1544
1720
  return p;
1545
1721
  }
1546
1722
  }
1547
1723
  function isInsideDir(filePath, dir) {
1548
- const rel = path3.relative(dir, filePath);
1549
- return rel !== "" && !rel.startsWith("..") && !path3.isAbsolute(rel);
1724
+ const rel = path4.relative(dir, filePath);
1725
+ return rel !== "" && !rel.startsWith("..") && !path4.isAbsolute(rel);
1550
1726
  }
1551
1727
  var APP_DIR = "src";
1552
1728
  function toStrippedProdImportPath(sourceFile, rootDir) {
1553
- const appDirAbs = toRealPath(path3.resolve(rootDir, APP_DIR));
1729
+ const appDirAbs = toRealPath(path4.resolve(rootDir, APP_DIR));
1554
1730
  const sourceReal = toRealPath(sourceFile);
1555
- let rel = path3.relative(appDirAbs, sourceReal);
1556
- rel = rel.split(path3.sep).join("/");
1731
+ let rel = path4.relative(appDirAbs, sourceReal);
1732
+ rel = rel.split(path4.sep).join("/");
1557
1733
  if (!rel.startsWith(".")) rel = "./" + rel;
1558
1734
  return toProdExtension(rel);
1559
1735
  }
@@ -1568,34 +1744,34 @@ var INDEX_EXTS = [
1568
1744
  "/index.cjs"
1569
1745
  ];
1570
1746
  function resolveRelativeSpecifier(importer, specifier) {
1571
- const importerDir = path3.dirname(importer);
1572
- const base = path3.resolve(importerDir, specifier);
1747
+ const importerDir = path4.dirname(importer);
1748
+ const base = path4.resolve(importerDir, specifier);
1573
1749
  if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
1574
- return fs2.existsSync(base) ? base : null;
1750
+ return fs3.existsSync(base) ? base : null;
1575
1751
  }
1576
1752
  if (/\.(ts|tsx|jsx)$/.test(specifier)) {
1577
- return fs2.existsSync(base) ? base : null;
1753
+ return fs3.existsSync(base) ? base : null;
1578
1754
  }
1579
1755
  for (const ext of SOURCE_EXTS) {
1580
1756
  const file = base + ext;
1581
- if (fs2.existsSync(file)) return file;
1757
+ if (fs3.existsSync(file)) return file;
1582
1758
  }
1583
1759
  for (const indexExt of INDEX_EXTS) {
1584
1760
  const file = base + indexExt;
1585
- if (fs2.existsSync(file)) return file;
1761
+ if (fs3.existsSync(file)) return file;
1586
1762
  }
1587
1763
  return null;
1588
1764
  }
1589
1765
  function createAliasPlugin(config, options) {
1590
1766
  const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
1591
- const appDirAbs = options?.rootDir ? toRealPath(path3.resolve(options.rootDir, APP_DIR)) : null;
1767
+ const appDirAbs = options?.rootDir ? toRealPath(path4.resolve(options.rootDir, APP_DIR)) : null;
1592
1768
  return {
1593
1769
  name: "faapi-alias",
1594
1770
  setup(build) {
1595
1771
  build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
1596
1772
  let source;
1597
1773
  try {
1598
- source = fs2.readFileSync(args.path, "utf8");
1774
+ source = fs3.readFileSync(args.path, "utf8");
1599
1775
  } catch {
1600
1776
  return void 0;
1601
1777
  }
@@ -1628,7 +1804,7 @@ function createAliasPlugin(config, options) {
1628
1804
  for (const candidate of candidates) {
1629
1805
  for (const ext of SOURCE_EXTS) {
1630
1806
  const file = candidate + ext;
1631
- if (fs2.existsSync(file)) {
1807
+ if (fs3.existsSync(file)) {
1632
1808
  modified = true;
1633
1809
  if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
1634
1810
  return `${prefix}${quote}${toStrippedProdImportPath(
@@ -1641,7 +1817,7 @@ function createAliasPlugin(config, options) {
1641
1817
  }
1642
1818
  for (const indexExt of INDEX_EXTS) {
1643
1819
  const file = candidate + indexExt;
1644
- if (fs2.existsSync(file)) {
1820
+ if (fs3.existsSync(file)) {
1645
1821
  modified = true;
1646
1822
  if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
1647
1823
  return `${prefix}${quote}${toStrippedProdImportPath(
@@ -1679,11 +1855,11 @@ async function compileDevRoutes(options) {
1679
1855
  if (entryPoints.length === 0) {
1680
1856
  return { compiledFiles: [] };
1681
1857
  }
1682
- const absDist = path4.resolve(rootDir, dist);
1683
- await fs3.promises.mkdir(absDist, { recursive: true });
1858
+ const absDist = path5.resolve(rootDir, dist);
1859
+ await fs4.promises.mkdir(absDist, { recursive: true });
1684
1860
  const plugins = buildAliasPlugins(rootDir);
1685
1861
  const esbuild = await import("esbuild");
1686
- const outbase = path4.resolve(rootDir, APP_DIR2);
1862
+ const outbase = path5.resolve(rootDir, APP_DIR2);
1687
1863
  const result = await esbuild.build({
1688
1864
  entryPoints,
1689
1865
  outdir: absDist,
@@ -1700,10 +1876,10 @@ async function compileDevRoutes(options) {
1700
1876
  if (result.outputFiles) {
1701
1877
  await Promise.all(
1702
1878
  result.outputFiles.map(async (file) => {
1703
- await fs3.promises.mkdir(path4.dirname(file.path), { recursive: true });
1879
+ await fs4.promises.mkdir(path5.dirname(file.path), { recursive: true });
1704
1880
  const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
1705
- await fs3.promises.writeFile(tmp, file.contents);
1706
- await fs3.promises.rename(tmp, file.path);
1881
+ await fs4.promises.writeFile(tmp, file.contents);
1882
+ await fs4.promises.rename(tmp, file.path);
1707
1883
  })
1708
1884
  );
1709
1885
  }
@@ -1715,8 +1891,8 @@ init_generateSchemaFiles();
1715
1891
  init_generateSchemaFiles();
1716
1892
  function isProductFresh(sourceAbsPath, productAbsPath) {
1717
1893
  try {
1718
- const srcStat = fs5.statSync(sourceAbsPath);
1719
- const prodStat = fs5.statSync(productAbsPath);
1894
+ const srcStat = fs6.statSync(sourceAbsPath);
1895
+ const prodStat = fs6.statSync(productAbsPath);
1720
1896
  return prodStat.mtimeMs >= srcStat.mtimeMs;
1721
1897
  } catch {
1722
1898
  return false;
@@ -1736,6 +1912,7 @@ var state = createDevOnDemandState();
1736
1912
  function clearCompiledFiles() {
1737
1913
  state.compiledFiles.clear();
1738
1914
  state.inFlightCompilations.clear();
1915
+ sourcePathCache.clear();
1739
1916
  }
1740
1917
  async function ensureCompiled(sourceAbsPath, rootDir, dist) {
1741
1918
  const inFlight = state.inFlightCompilations.get(sourceAbsPath);
@@ -1747,7 +1924,7 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
1747
1924
  if (state.compiledFiles.has(sourceAbsPath)) {
1748
1925
  return false;
1749
1926
  }
1750
- if (!fs5.existsSync(sourceAbsPath)) {
1927
+ if (!fs6.existsSync(sourceAbsPath)) {
1751
1928
  return false;
1752
1929
  }
1753
1930
  const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
@@ -1773,11 +1950,11 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
1773
1950
  }
1774
1951
  }
1775
1952
  function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
1776
- const rel = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
1953
+ const rel = path7.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
1777
1954
  if (!rel.startsWith("src/")) return null;
1778
1955
  const relWithoutSrc = rel.slice(4);
1779
1956
  const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
1780
- return path6.resolve(rootDir, dist, jsRel);
1957
+ return path7.resolve(rootDir, dist, jsRel);
1781
1958
  }
1782
1959
  function clearGeneratedSchemas() {
1783
1960
  state.generatedSchemas.clear();
@@ -1793,9 +1970,9 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
1793
1970
  if (state.generatedSchemas.has(schemaPath)) {
1794
1971
  return false;
1795
1972
  }
1796
- const prodAbsPath = path6.resolve(rootDir, routeFilePath);
1973
+ const prodAbsPath = path7.resolve(rootDir, routeFilePath);
1797
1974
  const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
1798
- if (!fs5.existsSync(sourceAbsPath)) {
1975
+ if (!fs6.existsSync(sourceAbsPath)) {
1799
1976
  return false;
1800
1977
  }
1801
1978
  if (isProductFresh(sourceAbsPath, schemaPath)) {
@@ -1806,7 +1983,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
1806
1983
  if (fileRoutes.length === 0) {
1807
1984
  return false;
1808
1985
  }
1809
- const sourceRelPath = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
1986
+ const sourceRelPath = path7.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
1810
1987
  const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
1811
1988
  const generatePromise = (async () => {
1812
1989
  await generateSchemaFiles(sourceRoutes, rootDir, dist);
@@ -1827,22 +2004,31 @@ async function deleteSchemaFiles(routes, rootDir, dist) {
1827
2004
  if (deleted.has(schemaPath)) continue;
1828
2005
  deleted.add(schemaPath);
1829
2006
  try {
1830
- await fs5.promises.unlink(schemaPath);
2007
+ await fs6.promises.unlink(schemaPath);
1831
2008
  } catch {
1832
2009
  }
1833
2010
  }
1834
2011
  }
2012
+ var sourcePathCache = /* @__PURE__ */ new Map();
1835
2013
  function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
1836
- const rel = path6.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
2014
+ const cached = sourcePathCache.get(prodAbsPath);
2015
+ if (cached) return cached;
2016
+ const rel = path7.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
1837
2017
  let relWithoutDist = rel;
1838
2018
  if (relWithoutDist.startsWith(`${dist}/`)) {
1839
2019
  relWithoutDist = relWithoutDist.slice(dist.length + 1);
1840
2020
  }
1841
2021
  const srcRel = `src/${relWithoutDist}`;
1842
2022
  const tsRel = srcRel.replace(/\.js$/, ".ts");
1843
- const tsAbs = path6.resolve(rootDir, tsRel);
1844
- if (fs5.existsSync(tsAbs)) return tsAbs;
1845
- return path6.resolve(rootDir, srcRel);
2023
+ const tsAbs = path7.resolve(rootDir, tsRel);
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;
1846
2032
  }
1847
2033
  function isDevOnDemandEnabled() {
1848
2034
  return state.enabled;
@@ -1857,7 +2043,7 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
1857
2043
  const dist = getDevDist();
1858
2044
  if (dist) {
1859
2045
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
1860
- if (sourcePath && fs6.existsSync(sourcePath)) {
2046
+ if (sourcePath && fs7.existsSync(sourcePath)) {
1861
2047
  try {
1862
2048
  await ensureCompiled(sourcePath, rootDir, dist);
1863
2049
  } catch (compileErr) {
@@ -1890,13 +2076,13 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
1890
2076
  }
1891
2077
 
1892
2078
  // src/loader/loadToolModule.ts
1893
- import fs7 from "fs";
2079
+ import fs8 from "fs";
1894
2080
  async function loadToolModule(filePath, functionName, rootDir) {
1895
2081
  if (isDevOnDemandEnabled() && rootDir) {
1896
2082
  const dist = getDevDist();
1897
2083
  if (dist) {
1898
2084
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
1899
- if (sourcePath && fs7.existsSync(sourcePath)) {
2085
+ if (sourcePath && fs8.existsSync(sourcePath)) {
1900
2086
  try {
1901
2087
  await ensureCompiled(sourcePath, rootDir, dist);
1902
2088
  } catch (compileErr) {
@@ -1928,8 +2114,8 @@ async function loadToolModule(filePath, functionName, rootDir) {
1928
2114
  import { existsSync as existsSync2 } from "fs";
1929
2115
 
1930
2116
  // src/cli/generateToolArtifacts.ts
1931
- import path7 from "path";
1932
- import fs8 from "fs/promises";
2117
+ import path8 from "path";
2118
+ import fs9 from "fs/promises";
1933
2119
  import { existsSync } from "fs";
1934
2120
 
1935
2121
  // src/ast/extractToolMetadata.ts
@@ -2024,7 +2210,7 @@ function getToolSchemaOutputPath(sourceFile, dist, rootDir) {
2024
2210
  }
2025
2211
  const idx = rel.lastIndexOf("/");
2026
2212
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
2027
- return path7.resolve(rootDir, dist, relDir, "zod.js");
2213
+ return path8.resolve(rootDir, dist, relDir, "zod.js");
2028
2214
  }
2029
2215
  function getRuntimeToolSchemaPath(filePath, dist, rootDir) {
2030
2216
  let rel = filePath.replace(/\\/g, "/");
@@ -2035,7 +2221,7 @@ function getRuntimeToolSchemaPath(filePath, dist, rootDir) {
2035
2221
  }
2036
2222
  const idx = rel.lastIndexOf("/");
2037
2223
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
2038
- return path7.resolve(rootDir, dist, relDir, "zod.js");
2224
+ return path8.resolve(rootDir, dist, relDir, "zod.js");
2039
2225
  }
2040
2226
  function toProdFilePath(filePath, dist) {
2041
2227
  let rel = filePath.replace(/\\/g, "/");
@@ -2055,12 +2241,12 @@ function serializeTools(tools, dist = "dist") {
2055
2241
  }));
2056
2242
  }
2057
2243
  async function writeToolsModule(manifest, outputPath) {
2058
- const dir = path7.dirname(outputPath);
2059
- await fs8.mkdir(dir, { recursive: true });
2244
+ const dir = path8.dirname(outputPath);
2245
+ await fs9.mkdir(dir, { recursive: true });
2060
2246
  const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
2061
2247
  export const tools = ${JSON.stringify(manifest, null, 2)};
2062
2248
  `;
2063
- await fs8.writeFile(outputPath, content, "utf-8");
2249
+ await fs9.writeFile(outputPath, content, "utf-8");
2064
2250
  }
2065
2251
  function hydrateTools(manifest) {
2066
2252
  return manifest.map((t) => ({
@@ -2075,7 +2261,7 @@ function collectToolSchemaSources(tools, rootDir) {
2075
2261
  const toolsByFile = /* @__PURE__ */ new Map();
2076
2262
  for (const tool of tools) {
2077
2263
  if (!tool.inputTypeName) continue;
2078
- const absPath = path7.resolve(rootDir, tool.filePath);
2264
+ const absPath = path8.resolve(rootDir, tool.filePath);
2079
2265
  let list = toolsByFile.get(absPath);
2080
2266
  if (!list) {
2081
2267
  list = [];
@@ -2083,15 +2269,16 @@ function collectToolSchemaSources(tools, rootDir) {
2083
2269
  }
2084
2270
  list.push(tool);
2085
2271
  }
2272
+ const programByFile = createPrograms([...toolsByFile.keys()]);
2086
2273
  const allTypesByFile = /* @__PURE__ */ new Map();
2087
2274
  for (const filePath of toolsByFile.keys()) {
2088
- const program = createProgram(filePath);
2275
+ const program = programByFile.get(filePath);
2089
2276
  const allTypes = extractAllTypes(program, filePath);
2090
2277
  allTypesByFile.set(filePath, allTypes);
2091
2278
  }
2092
2279
  const sources = [];
2093
2280
  for (const [filePath, fileTools] of toolsByFile) {
2094
- const program = createProgram(filePath);
2281
+ const program = programByFile.get(filePath);
2095
2282
  for (const tool of fileTools) {
2096
2283
  const inputTypeName = tool.inputTypeName;
2097
2284
  const typeInfo = extractTypeInfo(program, filePath, inputTypeName);
@@ -2137,16 +2324,17 @@ function generateToolSchemaFileSource(sources, allTypes, helpersImportPath) {
2137
2324
  }
2138
2325
  async function maybeGenerateHelpers(allSourceCode, distDir) {
2139
2326
  if (!usesCoerceHelpers(allSourceCode)) return;
2140
- const helpersPath = path7.resolve(distDir, HELPERS_FILENAME);
2327
+ const helpersPath = path8.resolve(distDir, HELPERS_FILENAME);
2141
2328
  if (existsSync(helpersPath)) return;
2142
- await fs8.mkdir(path7.dirname(helpersPath), { recursive: true });
2143
- await fs8.writeFile(helpersPath, generateHelpersFileSource(), "utf-8");
2329
+ await fs9.mkdir(path8.dirname(helpersPath), { recursive: true });
2330
+ await fs9.writeFile(helpersPath, generateHelpersFileSource(), "utf-8");
2144
2331
  }
2145
2332
  async function generateToolArtifacts(tools, rootDir, dist, options) {
2146
2333
  const metadata = [];
2334
+ const programByFile = createPrograms(tools.map((m) => path8.resolve(rootDir, m.filePath)));
2147
2335
  for (const manifest of tools) {
2148
- const absPath = path7.resolve(rootDir, manifest.filePath);
2149
- const program = createProgram(absPath);
2336
+ const absPath = path8.resolve(rootDir, manifest.filePath);
2337
+ const program = programByFile.get(absPath);
2150
2338
  const result = extractToolMetadata(program, absPath, manifest.functionName, {
2151
2339
  name: manifest.name,
2152
2340
  filePath: manifest.filePath
@@ -2156,7 +2344,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
2156
2344
  }
2157
2345
  }
2158
2346
  const serialized = serializeTools(metadata, dist);
2159
- const toolsPath = path7.resolve(rootDir, dist, TOOLS_FILE);
2347
+ const toolsPath = path8.resolve(rootDir, dist, TOOLS_FILE);
2160
2348
  await writeToolsModule(serialized, toolsPath);
2161
2349
  if (options?.skipSchema) {
2162
2350
  return metadata;
@@ -2179,7 +2367,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
2179
2367
  }
2180
2368
  const fileEntries = [];
2181
2369
  for (const [filePath, fileSources] of sourcesByFile) {
2182
- const relFile = path7.relative(rootDir, filePath).replace(/\\/g, "/");
2370
+ const relFile = path8.relative(rootDir, filePath).replace(/\\/g, "/");
2183
2371
  const outputPath = getToolSchemaOutputPath(relFile, dist, rootDir);
2184
2372
  const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
2185
2373
  let relForDir = relFile;
@@ -2193,7 +2381,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
2193
2381
  fileEntries.push({ outputPath, source });
2194
2382
  }
2195
2383
  const allSourceCode = fileEntries.map((e) => e.source).join("\n");
2196
- const distDir = path7.resolve(rootDir, dist);
2384
+ const distDir = path8.resolve(rootDir, dist);
2197
2385
  await maybeGenerateHelpers(allSourceCode, distDir);
2198
2386
  await Promise.all(
2199
2387
  fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source))
@@ -2201,8 +2389,8 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
2201
2389
  return metadata;
2202
2390
  }
2203
2391
  async function writeToolSchemaFile(outputPath, source) {
2204
- await fs8.mkdir(path7.dirname(outputPath), { recursive: true });
2205
- await fs8.writeFile(outputPath, source, "utf-8");
2392
+ await fs9.mkdir(path8.dirname(outputPath), { recursive: true });
2393
+ await fs9.writeFile(outputPath, source, "utf-8");
2206
2394
  }
2207
2395
 
2208
2396
  // src/loader/loadToolSchema.ts
@@ -2212,11 +2400,14 @@ function getDist() {
2212
2400
  }
2213
2401
  return process.env.FAAPI_DIST ?? "dist";
2214
2402
  }
2403
+ function getToolSchemaPath(tool, rootDir) {
2404
+ const dist = getDist();
2405
+ return getRuntimeToolSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());
2406
+ }
2215
2407
  async function loadToolSchema(tool, rootDir) {
2216
2408
  if (!tool.inputTypeName) return void 0;
2217
2409
  const schemaName = `${tool.inputTypeName}Schema`;
2218
- const dist = getDist();
2219
- const zodPath = getRuntimeToolSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());
2410
+ const zodPath = getToolSchemaPath(tool, rootDir);
2220
2411
  if (!existsSync2(zodPath)) return void 0;
2221
2412
  try {
2222
2413
  const mod = await importWithCacheBust(zodPath, isDevOnDemandEnabled());
@@ -2402,16 +2593,16 @@ function helmet(options = {}) {
2402
2593
  }
2403
2594
 
2404
2595
  // src/config/loadConfig.ts
2405
- import path8 from "path";
2406
- import fs9 from "fs";
2596
+ import path9 from "path";
2597
+ import fs10 from "fs";
2407
2598
  var CONFIG_PRODUCT_FILE = "faapi-config.js";
2408
2599
  async function loadConfig(rootDir, dist) {
2409
- const configProductPath = path8.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
2410
- if (fs9.existsSync(configProductPath)) {
2600
+ const configProductPath = path9.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
2601
+ if (fs10.existsSync(configProductPath)) {
2411
2602
  const module = await importWithCacheBust(configProductPath);
2412
2603
  return module.default ?? {};
2413
2604
  }
2414
- const hasSourceConfig = fs9.existsSync(path8.join(rootDir, "faapi.config.ts")) || fs9.existsSync(path8.join(rootDir, "faapi.config.js"));
2605
+ const hasSourceConfig = fs10.existsSync(path9.join(rootDir, "faapi.config.ts")) || fs10.existsSync(path9.join(rootDir, "faapi.config.js"));
2415
2606
  if (hasSourceConfig) {
2416
2607
  throw new Error(
2417
2608
  `[faapi] ${dist}/${CONFIG_PRODUCT_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
@@ -2421,8 +2612,8 @@ async function loadConfig(rootDir, dist) {
2421
2612
  }
2422
2613
 
2423
2614
  // src/cli/loadEnv.ts
2424
- import fs10 from "fs";
2425
- import path9 from "path";
2615
+ import fs11 from "fs";
2616
+ import path10 from "path";
2426
2617
  function resolveEnv() {
2427
2618
  return process.env.NODE_ENV || "development";
2428
2619
  }
@@ -2488,9 +2679,9 @@ function loadEnv(rootDir) {
2488
2679
  const files = getEnvFiles(env);
2489
2680
  const merged = {};
2490
2681
  for (const file of files) {
2491
- const filePath = path9.join(rootDir, file);
2492
- if (!fs10.existsSync(filePath)) continue;
2493
- const content = fs10.readFileSync(filePath, "utf-8");
2682
+ const filePath = path10.join(rootDir, file);
2683
+ if (!fs11.existsSync(filePath)) continue;
2684
+ const content = fs11.readFileSync(filePath, "utf-8");
2494
2685
  const parsed = parseEnvFile(content, merged);
2495
2686
  Object.assign(merged, parsed);
2496
2687
  }
@@ -2527,14 +2718,14 @@ var ValidationError = class extends FaapiError {
2527
2718
  issues;
2528
2719
  };
2529
2720
  var RouteNotFoundError = class extends FaapiError {
2530
- constructor(path18) {
2531
- super("ROUTE_NOT_FOUND", `Route not found: ${path18}`, 404);
2721
+ constructor(path19) {
2722
+ super("ROUTE_NOT_FOUND", `Route not found: ${path19}`, 404);
2532
2723
  this.name = "RouteNotFoundError";
2533
2724
  }
2534
2725
  };
2535
2726
  var MethodNotAllowedError = class extends FaapiError {
2536
- constructor(method, path18, allowedMethods) {
2537
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path18}`, 405);
2727
+ constructor(method, path19, allowedMethods) {
2728
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path19}`, 405);
2538
2729
  this.allowedMethods = allowedMethods;
2539
2730
  this.name = "MethodNotAllowedError";
2540
2731
  }
@@ -2561,7 +2752,7 @@ var PayloadTooLargeError = class extends FaapiError {
2561
2752
 
2562
2753
  // src/cli/createAppCore.ts
2563
2754
  import fs15 from "fs";
2564
- import path14 from "path";
2755
+ import path15 from "path";
2565
2756
  import { PassThrough, Readable as Readable3 } from "stream";
2566
2757
 
2567
2758
  // src/router/sortRoutes.ts
@@ -2614,45 +2805,96 @@ import {
2614
2805
  import { createSecureServer as createHttp2SecureServer } from "http2";
2615
2806
  import { readFileSync } from "fs";
2616
2807
  import { Readable as Readable2 } from "stream";
2617
- import path11 from "path";
2808
+ import path12 from "path";
2618
2809
 
2619
2810
  // src/router/matchRoute.ts
2620
- function matchRoute(routes, method, path18) {
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: [] };
2621
2817
  for (const route of routes) {
2622
- if (route.method !== method) {
2623
- continue;
2624
- }
2625
- if (!route.isDynamic) {
2626
- if (route.urlPath === path18) {
2627
- 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);
2628
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) {
2629
2855
  continue;
2630
2856
  }
2631
- const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
2857
+ const params = matchDynamicPath(route.urlPath, path19, route.paramNames, route.isCatchAll);
2632
2858
  if (params !== null) {
2633
2859
  return { route, params };
2634
2860
  }
2635
2861
  }
2636
2862
  return null;
2637
2863
  }
2638
- function matchWsRoute(wsRoutes, path18) {
2639
- for (const route of wsRoutes) {
2640
- if (!route.isDynamic) {
2641
- if (route.urlPath === path18) {
2642
- return { route, params: {} };
2643
- }
2644
- continue;
2645
- }
2646
- const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
2864
+ function matchWsRoute(wsRoutes, path19) {
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) {
2871
+ const params = matchDynamicPath(route.urlPath, path19, route.paramNames, route.isCatchAll);
2647
2872
  if (params !== null) {
2648
2873
  return { route, params };
2649
2874
  }
2650
2875
  }
2651
2876
  return null;
2652
2877
  }
2653
- function matchDynamicPath(pattern, path18, paramNames, isCatchAll) {
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
+ }
2895
+ function matchDynamicPath(pattern, path19, paramNames, isCatchAll) {
2654
2896
  const patternSegments = pattern.split("/").filter(Boolean);
2655
- const pathSegments = path18.split("/").filter(Boolean);
2897
+ const pathSegments = path19.split("/").filter(Boolean);
2656
2898
  if (isCatchAll) {
2657
2899
  const nonCatchAllCount = patternSegments.length - 1;
2658
2900
  if (pathSegments.length <= nonCatchAllCount) {
@@ -2697,9 +2939,6 @@ function matchDynamicPath(pattern, path18, paramNames, isCatchAll) {
2697
2939
  return params;
2698
2940
  }
2699
2941
 
2700
- // src/loader/loadRouteModule.ts
2701
- import fs11 from "fs";
2702
-
2703
2942
  // src/loader/validateRouteModule.ts
2704
2943
  function validateRouteModule(value, method, filePath) {
2705
2944
  if (typeof value !== "function") {
@@ -2715,7 +2954,7 @@ async function loadRouteModule(filePath, method, rootDir) {
2715
2954
  const dist = getDevDist();
2716
2955
  if (dist) {
2717
2956
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
2718
- if (sourcePath && fs11.existsSync(sourcePath)) {
2957
+ if (sourcePath) {
2719
2958
  try {
2720
2959
  await ensureCompiled(sourcePath, rootDir, dist);
2721
2960
  } catch (compileErr) {
@@ -2955,7 +3194,9 @@ function formatSetCookie(name, value, options) {
2955
3194
  return cookie;
2956
3195
  }
2957
3196
  function createContext(request, params, config = {}, ip = "") {
2958
- 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 = "") {
2959
3200
  const meta = { headers: {}, setCookies: [] };
2960
3201
  const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
2961
3202
  const cookiesObj = {};
@@ -3110,7 +3351,7 @@ async function parseMultipart(request) {
3110
3351
 
3111
3352
  // src/runtime/resolveInput.ts
3112
3353
  init_inputType();
3113
- async function resolveInput(method, request) {
3354
+ async function resolveInputFromUrl(method, request, url) {
3114
3355
  const inputType = getInputTypeForMethod(method);
3115
3356
  if (inputType === "body") {
3116
3357
  const contentType = request.headers.get("content-type") ?? "";
@@ -3145,7 +3386,6 @@ async function resolveInput(method, request) {
3145
3386
  }
3146
3387
  return result.data;
3147
3388
  }
3148
- const url = new URL(request.url);
3149
3389
  return queryToObject(url.searchParams);
3150
3390
  }
3151
3391
 
@@ -3470,9 +3710,9 @@ async function validateInput(schemaPath, method, inputType, input) {
3470
3710
  function mapZodIssues(error) {
3471
3711
  return error.issues.map((issue) => {
3472
3712
  const code = mapZodCode(issue.code, issue.message);
3473
- const path18 = issue.path.map(String).join(".") || "";
3713
+ const path19 = issue.path.map(String).join(".") || "";
3474
3714
  return {
3475
- path: path18,
3715
+ path: path19,
3476
3716
  code,
3477
3717
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
3478
3718
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -3517,11 +3757,13 @@ function mapReceivedFromMessage(message) {
3517
3757
  init_inputType();
3518
3758
 
3519
3759
  // src/utils/getClientIp.ts
3520
- function getClientIp(req) {
3521
- const xff = req.headers["x-forwarded-for"];
3522
- if (typeof xff === "string" && xff.length > 0) {
3523
- const first = xff.split(",")[0]?.trim();
3524
- 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
+ }
3525
3767
  }
3526
3768
  const remote = req.socket?.remoteAddress;
3527
3769
  if (remote) {
@@ -3536,7 +3778,7 @@ function getClientIp(req) {
3536
3778
  // src/server/handleWsUpgrade.ts
3537
3779
  import fs12 from "fs";
3538
3780
  import { WebSocketServer, WebSocket } from "ws";
3539
- import path10 from "path";
3781
+ import path11 from "path";
3540
3782
 
3541
3783
  // src/server/serverUtils.ts
3542
3784
  function nodeHttpToWebHeaders(req) {
@@ -3720,7 +3962,7 @@ async function sendResponseToSocket(socket, response) {
3720
3962
  socket.destroy();
3721
3963
  }
3722
3964
  function attachWebSocket(options) {
3723
- const { server, routesRef, rootDir, config, globalMiddlewares } = options;
3965
+ const { server, routesRef, rootDir, config, globalMiddlewares, trustedProxy = false } = options;
3724
3966
  const wss = new WebSocketServer({ noServer: true });
3725
3967
  server.on("upgrade", async (req, socket, head) => {
3726
3968
  const currentWsRoutes = routesRef.wsCurrent;
@@ -3736,13 +3978,13 @@ function attachWebSocket(options) {
3736
3978
  const host = req.headers.host ?? "localhost";
3737
3979
  const url = `http://${host}${req.url ?? "/"}`;
3738
3980
  const request = new Request(url, { method: "GET", headers });
3739
- const ctx = createContext(request, params, config, getClientIp(req));
3981
+ const ctx = createContext(request, params, config, getClientIp(req, trustedProxy));
3740
3982
  const meta = ctx.meta;
3741
3983
  let upgraded = false;
3742
3984
  const finalHandler = async () => {
3743
3985
  let handlers;
3744
3986
  try {
3745
- const absoluteFilePath = path10.resolve(rootDir, route.filePath);
3987
+ const absoluteFilePath = path11.resolve(rootDir, route.filePath);
3746
3988
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
3747
3989
  } catch (err) {
3748
3990
  const reason = err instanceof Error ? err.message : String(err);
@@ -3806,16 +4048,26 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
3806
4048
  const headers = nodeHttpToWebHeaders(req);
3807
4049
  const method = req.method ?? "GET";
3808
4050
  if (method === "GET" || method === "HEAD") {
3809
- 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
+ }
3810
4059
  }
3811
4060
  const stream = Readable2.toWeb(req);
3812
4061
  const limitedStream = limitStreamSize(stream, bodyLimit);
3813
- return new Request(url.toString(), {
3814
- method,
3815
- headers,
3816
- body: limitedStream,
3817
- duplex: "half"
3818
- });
4062
+ return {
4063
+ request: new Request(url.toString(), {
4064
+ method,
4065
+ headers,
4066
+ body: limitedStream,
4067
+ duplex: "half"
4068
+ }),
4069
+ url
4070
+ };
3819
4071
  }
3820
4072
  function limitStreamSize(stream, maxSize) {
3821
4073
  let totalSize = 0;
@@ -3867,22 +4119,6 @@ function limitStreamSize(stream, maxSize) {
3867
4119
  }
3868
4120
  });
3869
4121
  }
3870
- function findAllowedMethods(routes, path18) {
3871
- const methods = /* @__PURE__ */ new Set();
3872
- for (const route of routes) {
3873
- if (route.urlPath === path18) {
3874
- methods.add(route.method);
3875
- continue;
3876
- }
3877
- if (route.isDynamic) {
3878
- const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
3879
- if (params !== null) {
3880
- methods.add(route.method);
3881
- }
3882
- }
3883
- }
3884
- return Array.from(methods);
3885
- }
3886
4122
  function createServer(options) {
3887
4123
  const {
3888
4124
  routes,
@@ -3897,7 +4133,8 @@ function createServer(options) {
3897
4133
  helmet: helmetOption,
3898
4134
  logger: loggerOption,
3899
4135
  bodyLimit = DEFAULT_BODY_LIMIT,
3900
- http2: http2Option
4136
+ http2: http2Option,
4137
+ trustedProxy = false
3901
4138
  } = options;
3902
4139
  const routesRef = { current: routes, wsCurrent: wsRoutes ?? [] };
3903
4140
  const configMiddlewares = [];
@@ -3909,6 +4146,10 @@ function createServer(options) {
3909
4146
  }
3910
4147
  const loggerMiddlewareInst = loggerOption === false ? null : loggerOption === true || loggerOption === void 0 ? logger() : logger(loggerOption);
3911
4148
  if (loggerMiddlewareInst) configMiddlewares.push(loggerMiddlewareInst);
4149
+ const outerMiddlewares = [...configMiddlewares];
4150
+ if (globalMiddlewares && globalMiddlewares.length > 0) {
4151
+ outerMiddlewares.push(...globalMiddlewares);
4152
+ }
3912
4153
  const server = (() => {
3913
4154
  if (http2Option) {
3914
4155
  const h2Opts = typeof http2Option === "object" ? http2Option : {};
@@ -3928,29 +4169,29 @@ function createServer(options) {
3928
4169
  dist,
3929
4170
  req,
3930
4171
  res,
3931
- configMiddlewares,
4172
+ outerMiddlewares,
3932
4173
  onError,
3933
4174
  config,
3934
- globalMiddlewares,
3935
4175
  globalInjectors,
3936
- bodyLimit
4176
+ bodyLimit,
4177
+ trustedProxy
3937
4178
  ).catch(() => {
3938
4179
  res.statusCode = 500;
3939
4180
  res.end();
3940
4181
  });
3941
4182
  });
3942
4183
  if (routesRef.wsCurrent.length > 0) {
3943
- attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares });
4184
+ attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares, trustedProxy });
3944
4185
  }
3945
4186
  return { server, routesRef };
3946
4187
  }
3947
- function prepareRequest(req, config, bodyLimit) {
3948
- const request = toWebRequest(req, bodyLimit);
4188
+ function prepareRequest(req, config, bodyLimit, trustedProxy) {
4189
+ const { request, url } = toWebRequest(req, bodyLimit);
3949
4190
  const method = request.method.toUpperCase();
3950
- const urlPath = new URL(request.url).pathname;
3951
- const ctx = createContext(request, {}, config, getClientIp(req));
4191
+ const urlPath = url.pathname;
4192
+ const ctx = createContextFromUrl(request, url, {}, config, getClientIp(req, trustedProxy));
3952
4193
  const meta = ctx.meta;
3953
- return { request, ctx, meta, method, urlPath };
4194
+ return { request, url, ctx, meta, method, urlPath };
3954
4195
  }
3955
4196
  function resolveRouteOrThrow(routes, method, urlPath) {
3956
4197
  const match = matchRoute(routes, method, urlPath);
@@ -3962,14 +4203,14 @@ function resolveRouteOrThrow(routes, method, urlPath) {
3962
4203
  throw new RouteNotFoundError(urlPath);
3963
4204
  }
3964
4205
  function createRoutePipeline(opts) {
3965
- const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
4206
+ const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;
3966
4207
  return async () => {
3967
4208
  const match = resolveRouteOrThrow(routes, method, urlPath);
3968
4209
  ctx.params = match.params;
3969
4210
  const { route } = match;
3970
- const absoluteFilePath = path11.resolve(rootDir, route.filePath);
4211
+ const absoluteFilePath = path12.resolve(rootDir, route.filePath);
3971
4212
  const routeModule = await loadRouteModule(absoluteFilePath, route.method, rootDir);
3972
- const input = await resolveInput(route.method, request);
4213
+ const input = await resolveInputFromUrl(route.method, request, url);
3973
4214
  const inputType = getInputTypeForMethod(route.method);
3974
4215
  const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
3975
4216
  if (isDevOnDemandEnabled()) {
@@ -4001,33 +4242,33 @@ async function sendSuccessResponse(response, res) {
4001
4242
  await sendNodeResponse(response, res);
4002
4243
  }
4003
4244
  async function sendErrorResponse(err, meta, res, onError, ctx) {
4004
- await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx.config), meta), res);
4005
- if (onError) {
4245
+ await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx?.config), meta), res);
4246
+ if (onError && ctx) {
4006
4247
  try {
4007
4248
  await onError(err, ctx);
4008
4249
  } catch {
4009
4250
  }
4010
4251
  }
4011
4252
  }
4012
- async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
4013
- const { request, ctx, meta, method, urlPath } = prepareRequest(req, config, bodyLimit);
4014
- const routePipeline = createRoutePipeline({
4015
- routes,
4016
- method,
4017
- urlPath,
4018
- ctx,
4019
- request,
4020
- rootDir,
4021
- dist,
4022
- globalMiddlewares,
4023
- globalInjectors
4024
- });
4025
- const outerMiddlewares = [];
4026
- if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
4027
- if (globalMiddlewares && globalMiddlewares.length > 0) {
4028
- outerMiddlewares.push(...globalMiddlewares);
4029
- }
4253
+ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy) {
4254
+ let meta = { headers: {}, setCookies: [] };
4255
+ let ctx;
4030
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
+ });
4031
4272
  const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
4032
4273
  await sendSuccessResponse(response, res);
4033
4274
  } catch (err) {
@@ -4065,7 +4306,7 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
4065
4306
 
4066
4307
  // src/cli/generateRoutes.ts
4067
4308
  import fs13 from "fs";
4068
- import path12 from "path";
4309
+ import path13 from "path";
4069
4310
  async function hydrateRoutes(manifest) {
4070
4311
  const hydrateRoute = (serialized) => ({
4071
4312
  method: serialized.method,
@@ -4090,7 +4331,7 @@ async function hydrateRoutes(manifest) {
4090
4331
  }
4091
4332
 
4092
4333
  // src/cli/generateAgentArtifacts.ts
4093
- import path13 from "path";
4334
+ import path14 from "path";
4094
4335
  import fs14 from "fs/promises";
4095
4336
 
4096
4337
  // src/ast/extractAgentMetadata.ts
@@ -4305,7 +4546,7 @@ function serializeAgents(agents, dist = "dist") {
4305
4546
  }));
4306
4547
  }
4307
4548
  async function writeAgentsModule(manifest, outputPath) {
4308
- const dir = path13.dirname(outputPath);
4549
+ const dir = path14.dirname(outputPath);
4309
4550
  await fs14.mkdir(dir, { recursive: true });
4310
4551
  const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
4311
4552
  export const agents = ${JSON.stringify(manifest, null, 2)};
@@ -4327,9 +4568,10 @@ function hydrateAgents(manifest) {
4327
4568
  }
4328
4569
  async function generateAgentArtifacts(agents, rootDir, dist) {
4329
4570
  const metadata = [];
4571
+ const programByFile = createPrograms(agents.map((m) => path14.resolve(rootDir, m.filePath)));
4330
4572
  for (const manifest of agents) {
4331
- const absPath = path13.resolve(rootDir, manifest.filePath);
4332
- const program = createProgram(absPath);
4573
+ const absPath = path14.resolve(rootDir, manifest.filePath);
4574
+ const program = programByFile.get(absPath);
4333
4575
  const result = extractAgentMetadata(program, absPath, {
4334
4576
  name: manifest.name,
4335
4577
  filePath: manifest.filePath,
@@ -4340,7 +4582,7 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
4340
4582
  }
4341
4583
  }
4342
4584
  const serialized = serializeAgents(metadata, dist);
4343
- const agentsPath = path13.resolve(rootDir, dist, AGENTS_FILE);
4585
+ const agentsPath = path14.resolve(rootDir, dist, AGENTS_FILE);
4344
4586
  await writeAgentsModule(serialized, agentsPath);
4345
4587
  return metadata;
4346
4588
  }
@@ -4412,7 +4654,7 @@ var TOOLS_FILE2 = "faapi-tools.js";
4412
4654
  var AGENTS_FILE2 = "faapi-agents.js";
4413
4655
  var PATTERNS = ["src/api/**/*.ts"];
4414
4656
  async function loadAndHydrateTools(rootDir, dist) {
4415
- const toolsPath = path14.resolve(rootDir, dist, TOOLS_FILE2);
4657
+ const toolsPath = path15.resolve(rootDir, dist, TOOLS_FILE2);
4416
4658
  if (!fs15.existsSync(toolsPath)) {
4417
4659
  return [];
4418
4660
  }
@@ -4422,7 +4664,7 @@ async function loadAndHydrateTools(rootDir, dist) {
4422
4664
  return hydrated;
4423
4665
  }
4424
4666
  async function loadAndHydrateAgents(rootDir, dist) {
4425
- const agentsPath = path14.resolve(rootDir, dist, AGENTS_FILE2);
4667
+ const agentsPath = path15.resolve(rootDir, dist, AGENTS_FILE2);
4426
4668
  if (!fs15.existsSync(agentsPath)) {
4427
4669
  return [];
4428
4670
  }
@@ -4462,6 +4704,7 @@ var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
4462
4704
  "bodyLimit",
4463
4705
  "logger",
4464
4706
  "http2",
4707
+ "trustedProxy",
4465
4708
  "response"
4466
4709
  ]);
4467
4710
  function isFaapiConfigKey(key) {
@@ -4470,7 +4713,7 @@ function isFaapiConfigKey(key) {
4470
4713
  async function createAppBase(options) {
4471
4714
  const rootDir = options?.rootDir ?? process.cwd();
4472
4715
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
4473
- const routesPath = path14.resolve(rootDir, dist, ROUTES_FILE);
4716
+ const routesPath = path15.resolve(rootDir, dist, ROUTES_FILE);
4474
4717
  if (!fs15.existsSync(routesPath)) {
4475
4718
  throw new Error(
4476
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`
@@ -4506,7 +4749,8 @@ async function createAppBase(options) {
4506
4749
  helmet: config?.helmet,
4507
4750
  logger: config?.logger,
4508
4751
  bodyLimit: config?.bodyLimit,
4509
- http2: config?.http2
4752
+ http2: config?.http2,
4753
+ trustedProxy: config?.trustedProxy
4510
4754
  });
4511
4755
  const { handlerWrappers, upgradeWrappers } = await loadPlugins(config?.plugins, {
4512
4756
  rootDir,
@@ -4692,7 +4936,7 @@ async function createAppBase(options) {
4692
4936
 
4693
4937
  // src/router/scanRoutes.ts
4694
4938
  import fg2 from "fast-glob";
4695
- import path15 from "path";
4939
+ import path16 from "path";
4696
4940
  import fs16 from "fs";
4697
4941
 
4698
4942
  // src/router/constants.ts
@@ -4700,9 +4944,9 @@ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
4700
4944
  var HTTP_METHOD_SET = new Set(HTTP_METHODS);
4701
4945
 
4702
4946
  // src/utils/normalizePath.ts
4703
- function normalizePath(path18) {
4704
- if (!path18) return "";
4705
- let result = path18.replace(/\\/g, "/");
4947
+ function normalizePath(path19) {
4948
+ if (!path19) return "";
4949
+ let result = path19.replace(/\\/g, "/");
4706
4950
  result = result.replace(/\/+/g, "/");
4707
4951
  result = result.replace(/\/+$/, "");
4708
4952
  if (result && !result.startsWith("/")) {
@@ -4763,26 +5007,26 @@ function extractExportsFromSource(source) {
4763
5007
  return names;
4764
5008
  }
4765
5009
  function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
4766
- const routeDir = path15.dirname(routeFilePath);
4767
- const resolvedRoot = path15.resolve(rootDir);
5010
+ const routeDir = path16.dirname(routeFilePath);
5011
+ const resolvedRoot = path16.resolve(rootDir);
4768
5012
  const paths = [];
4769
- let currentDir = path15.resolve(rootDir, routeDir);
5013
+ let currentDir = path16.resolve(rootDir, routeDir);
4770
5014
  while (true) {
4771
5015
  if (dist) {
4772
- const mwTsPath = path15.join(currentDir, "middlewares.ts");
4773
- const mwJsPath = path15.join(currentDir, "middlewares.js");
4774
- const absTsPath = path15.resolve(rootDir, mwTsPath);
4775
- const absJsPath = path15.resolve(rootDir, mwJsPath);
5016
+ const mwTsPath = path16.join(currentDir, "middlewares.ts");
5017
+ const mwJsPath = path16.join(currentDir, "middlewares.js");
5018
+ const absTsPath = path16.resolve(rootDir, mwTsPath);
5019
+ const absJsPath = path16.resolve(rootDir, mwJsPath);
4776
5020
  const absMwPath = fs16.existsSync(absTsPath) ? absTsPath : fs16.existsSync(absJsPath) ? absJsPath : null;
4777
5021
  if (absMwPath) {
4778
- const relMwPath = path15.relative(rootDir, absMwPath);
4779
- const prodAbsPath = path15.resolve(rootDir, toProdFilePath3(relMwPath, dist));
5022
+ const relMwPath = path16.relative(rootDir, absMwPath);
5023
+ const prodAbsPath = path16.resolve(rootDir, toProdFilePath3(relMwPath, dist));
4780
5024
  paths.push(prodAbsPath);
4781
5025
  }
4782
5026
  } else {
4783
5027
  for (const ext of [".ts", ".js"]) {
4784
- const mwPath = path15.join(currentDir, `middlewares${ext}`);
4785
- const absMwPath = path15.resolve(rootDir, mwPath);
5028
+ const mwPath = path16.join(currentDir, `middlewares${ext}`);
5029
+ const absMwPath = path16.resolve(rootDir, mwPath);
4786
5030
  if (fs16.existsSync(absMwPath)) {
4787
5031
  paths.push(absMwPath);
4788
5032
  break;
@@ -4790,7 +5034,7 @@ function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
4790
5034
  }
4791
5035
  }
4792
5036
  if (currentDir === resolvedRoot) break;
4793
- const parentDir = path15.dirname(currentDir);
5037
+ const parentDir = path16.dirname(currentDir);
4794
5038
  if (parentDir === currentDir) break;
4795
5039
  currentDir = parentDir;
4796
5040
  }
@@ -4817,7 +5061,7 @@ async function scanRoutes(rootDir, patterns, dist) {
4817
5061
  const normalizedFile = file.replace(/\\/g, "/");
4818
5062
  const fileName = normalizedFile.split("/").pop();
4819
5063
  if (fileName === "handler.ts" || fileName === "handler.js") {
4820
- const absPath = path15.resolve(rootDir, normalizedFile);
5064
+ const absPath = path16.resolve(rootDir, normalizedFile);
4821
5065
  const urlPath = filePathToUrlPath(normalizedFile);
4822
5066
  const paramNames = extractParamNames(urlPath);
4823
5067
  const isDynamic = paramNames.length > 0;
@@ -4866,7 +5110,7 @@ async function scanRoutes(rootDir, patterns, dist) {
4866
5110
 
4867
5111
  // src/tools/scanTools.ts
4868
5112
  import fg3 from "fast-glob";
4869
- import path16 from "path";
5113
+ import path17 from "path";
4870
5114
  import fs17 from "fs";
4871
5115
  var TOOL_PATTERNS = ["src/tools/**/*.ts"];
4872
5116
  var TOOL_EXPORT_RE = new RegExp(
@@ -4917,7 +5161,7 @@ async function scanTools(rootDir, patterns) {
4917
5161
  if (fileName !== "handler.ts" && fileName !== "handler.js") {
4918
5162
  continue;
4919
5163
  }
4920
- const absPath = path16.resolve(rootDir, normalizedFile);
5164
+ const absPath = path17.resolve(rootDir, normalizedFile);
4921
5165
  const source = await fs17.promises.readFile(absPath, "utf8").catch(() => "");
4922
5166
  const exportNames = extractToolExportsFromSource(source);
4923
5167
  const namespace = filePathToToolNamespace(normalizedFile);
@@ -4942,7 +5186,7 @@ async function scanTools(rootDir, patterns) {
4942
5186
 
4943
5187
  // src/agents/scanAgents.ts
4944
5188
  import fg4 from "fast-glob";
4945
- import path17 from "path";
5189
+ import path18 from "path";
4946
5190
  import fs18 from "fs";
4947
5191
  var DEFAULT_AGENT_PATTERNS = ["src/agents/*/handler.ts"];
4948
5192
  var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
@@ -4975,7 +5219,7 @@ async function scanAgents(rootDir, patterns) {
4975
5219
  if (fileName !== "handler.ts" && fileName !== "handler.js") {
4976
5220
  continue;
4977
5221
  }
4978
- const absPath = path17.resolve(rootDir, normalizedFile);
5222
+ const absPath = path18.resolve(rootDir, normalizedFile);
4979
5223
  const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
4980
5224
  const { hasRun } = detectAgentExports(source);
4981
5225
  const name = extractAgentNameFromPath(normalizedFile);
@@ -5056,6 +5300,7 @@ export {
5056
5300
  createDevApp,
5057
5301
  createProdApp,
5058
5302
  createProgram,
5303
+ createPrograms,
5059
5304
  extractTypeInfo,
5060
5305
  getAgent,
5061
5306
  getAgentEntry,
@@ -5063,6 +5308,7 @@ export {
5063
5308
  getInputTypeForMethod,
5064
5309
  getSkill,
5065
5310
  getTool,
5311
+ getToolSchemaPath,
5066
5312
  helmet,
5067
5313
  hydrateSkillRegistry,
5068
5314
  invalidateProgramCache,