@faapi/faapi 6.17.0 → 6.18.1

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
@@ -1234,21 +1234,6 @@ var init_collectRouteSchemaSources = __esm({
1234
1234
  }
1235
1235
  });
1236
1236
 
1237
- // src/utils/atomicWrite.ts
1238
- import path10 from "path";
1239
- import fs8 from "fs";
1240
- async function atomicWriteFile(outputPath, content) {
1241
- await fs8.promises.mkdir(path10.dirname(outputPath), { recursive: true });
1242
- const tmp = `${outputPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
1243
- await fs8.promises.writeFile(tmp, content, "utf-8");
1244
- await fs8.promises.rename(tmp, outputPath);
1245
- }
1246
- var init_atomicWrite = __esm({
1247
- "src/utils/atomicWrite.ts"() {
1248
- "use strict";
1249
- }
1250
- });
1251
-
1252
1237
  // src/ast/generateZodSchema.ts
1253
1238
  function collectNamedTypes(type, ctx) {
1254
1239
  switch (type.kind) {
@@ -1579,6 +1564,74 @@ var init_generateZodSchema = __esm({
1579
1564
  }
1580
1565
  });
1581
1566
 
1567
+ // src/utils/atomicWrite.ts
1568
+ import path10 from "path";
1569
+ import fs9 from "fs";
1570
+ async function atomicWriteFile(outputPath, content) {
1571
+ await fs9.promises.mkdir(path10.dirname(outputPath), { recursive: true });
1572
+ const tmp = `${outputPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
1573
+ await fs9.promises.writeFile(tmp, content, "utf-8");
1574
+ await fs9.promises.rename(tmp, outputPath);
1575
+ }
1576
+ var init_atomicWrite = __esm({
1577
+ "src/utils/atomicWrite.ts"() {
1578
+ "use strict";
1579
+ }
1580
+ });
1581
+
1582
+ // src/cli/generateZodArtifacts.ts
1583
+ import path11 from "path";
1584
+ import { existsSync } from "fs";
1585
+ async function generateZodArtifacts(sources, options) {
1586
+ const { generateFileSource, resolversByFile, rootDir, dist } = options;
1587
+ if (sources.length === 0) return;
1588
+ const sourcesByFile = /* @__PURE__ */ new Map();
1589
+ for (const source of sources) {
1590
+ let list = sourcesByFile.get(source.filePath);
1591
+ if (!list) {
1592
+ list = [];
1593
+ sourcesByFile.set(source.filePath, list);
1594
+ }
1595
+ list.push(source);
1596
+ }
1597
+ const fileEntries = [];
1598
+ for (const [filePath, fileSources] of sourcesByFile) {
1599
+ const relFile = path11.relative(rootDir, filePath).replace(/\\/g, "/");
1600
+ const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
1601
+ const resolver = resolversByFile.get(filePath);
1602
+ let relForDir = relFile;
1603
+ if (relForDir.startsWith("src/")) {
1604
+ relForDir = relForDir.slice(4);
1605
+ }
1606
+ const dirIdx = relForDir.lastIndexOf("/");
1607
+ const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
1608
+ const source = generateFileSource(
1609
+ fileSources,
1610
+ (name) => resolver?.resolve(name)?.runtimeType,
1611
+ getHelpersImportPath(zodRelDir)
1612
+ );
1613
+ fileEntries.push({ outputPath, source });
1614
+ }
1615
+ const allSourceCode = fileEntries.map((e) => e.source).join("\n");
1616
+ if (usesCoerceHelpers(allSourceCode)) {
1617
+ const helpersPath = path11.resolve(rootDir, dist, HELPERS_FILENAME);
1618
+ if (!existsSync(helpersPath)) {
1619
+ await atomicWriteFile(helpersPath, generateHelpersFileSource());
1620
+ }
1621
+ }
1622
+ await Promise.all(
1623
+ fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source))
1624
+ );
1625
+ }
1626
+ var init_generateZodArtifacts = __esm({
1627
+ "src/cli/generateZodArtifacts.ts"() {
1628
+ "use strict";
1629
+ init_atomicWrite();
1630
+ init_generateZodSchema();
1631
+ init_generateSchemaFiles();
1632
+ }
1633
+ });
1634
+
1582
1635
  // src/cli/generateSchemaFiles.ts
1583
1636
  var generateSchemaFiles_exports = {};
1584
1637
  __export(generateSchemaFiles_exports, {
@@ -1588,7 +1641,7 @@ __export(generateSchemaFiles_exports, {
1588
1641
  getRuntimeSchemaPath: () => getRuntimeSchemaPath,
1589
1642
  getSchemaOutputPath: () => getSchemaOutputPath
1590
1643
  });
1591
- import path11 from "path";
1644
+ import path12 from "path";
1592
1645
  function getSchemaOutputPath(sourceFile, dist, rootDir) {
1593
1646
  let rel = sourceFile.replace(/\\/g, "/");
1594
1647
  if (rel.startsWith("src/")) {
@@ -1596,7 +1649,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
1596
1649
  }
1597
1650
  const idx = rel.lastIndexOf("/");
1598
1651
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
1599
- return path11.resolve(rootDir, dist, relDir, "zod.js");
1652
+ return path12.resolve(rootDir, dist, relDir, "zod.js");
1600
1653
  }
1601
1654
  function getRuntimeSchemaPath(filePath, dist, rootDir) {
1602
1655
  let rel = filePath.replace(/\\/g, "/");
@@ -1607,7 +1660,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
1607
1660
  }
1608
1661
  const idx = rel.lastIndexOf("/");
1609
1662
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
1610
- return path11.resolve(rootDir, dist, relDir, "zod.js");
1663
+ return path12.resolve(rootDir, dist, relDir, "zod.js");
1611
1664
  }
1612
1665
  function getHelpersImportPath(relDir) {
1613
1666
  if (!relDir) return `./${HELPERS_FILENAME}`;
@@ -1655,52 +1708,19 @@ function generateSchemaFileSource(sources, resolveType, helpersImportPath) {
1655
1708
  async function generateSchemaFiles(routes, rootDir, dist) {
1656
1709
  if (routes.length === 0) return;
1657
1710
  const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir);
1658
- const sourcesByFile = /* @__PURE__ */ new Map();
1659
- for (const source of sources) {
1660
- let list = sourcesByFile.get(source.filePath);
1661
- if (!list) {
1662
- list = [];
1663
- sourcesByFile.set(source.filePath, list);
1664
- }
1665
- list.push(source);
1666
- }
1667
- const fileEntries = [];
1668
- for (const [filePath, fileSources] of sourcesByFile) {
1669
- const relFile = path11.relative(rootDir, filePath).replace(/\\/g, "/");
1670
- const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
1671
- let relForDir = relFile;
1672
- if (relForDir.startsWith("src/")) {
1673
- relForDir = relForDir.slice(4);
1674
- }
1675
- const dirIdx = relForDir.lastIndexOf("/");
1676
- const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
1677
- const helpersImportPath = getHelpersImportPath(zodRelDir);
1678
- const resolver = resolversByFile.get(filePath);
1679
- const source = generateSchemaFileSource(
1680
- fileSources,
1681
- (name) => resolver?.resolve(name)?.runtimeType,
1682
- helpersImportPath
1683
- );
1684
- fileEntries.push({ outputPath, source });
1685
- }
1686
- const allSourceCode = fileEntries.map((e) => e.source).join("\n");
1687
- if (usesCoerceHelpers(allSourceCode)) {
1688
- const helpersPath = path11.resolve(rootDir, dist, HELPERS_FILENAME);
1689
- await writeSchemaFile(helpersPath, generateHelpersFileSource());
1690
- }
1691
- await Promise.all(
1692
- fileEntries.map(({ outputPath, source }) => writeSchemaFile(outputPath, source))
1693
- );
1694
- }
1695
- async function writeSchemaFile(outputPath, source) {
1696
- await atomicWriteFile(outputPath, source);
1711
+ await generateZodArtifacts(sources, {
1712
+ generateFileSource: generateSchemaFileSource,
1713
+ resolversByFile,
1714
+ rootDir,
1715
+ dist
1716
+ });
1697
1717
  }
1698
1718
  var init_generateSchemaFiles = __esm({
1699
1719
  "src/cli/generateSchemaFiles.ts"() {
1700
1720
  "use strict";
1701
- init_atomicWrite();
1702
1721
  init_collectRouteSchemaSources();
1703
1722
  init_generateZodSchema();
1723
+ init_generateZodArtifacts();
1704
1724
  }
1705
1725
  });
1706
1726
 
@@ -1945,14 +1965,14 @@ var ValidationError = class extends FaapiError {
1945
1965
  issues;
1946
1966
  };
1947
1967
  var RouteNotFoundError = class extends FaapiError {
1948
- constructor(path29) {
1949
- super("ROUTE_NOT_FOUND", `Route not found: ${path29}`, 404);
1968
+ constructor(path31) {
1969
+ super("ROUTE_NOT_FOUND", `Route not found: ${path31}`, 404);
1950
1970
  this.name = "RouteNotFoundError";
1951
1971
  }
1952
1972
  };
1953
1973
  var MethodNotAllowedError = class extends FaapiError {
1954
- constructor(method, path29, allowedMethods) {
1955
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path29}`, 405);
1974
+ constructor(method, path31, allowedMethods) {
1975
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path31}`, 405);
1956
1976
  this.allowedMethods = allowedMethods;
1957
1977
  this.name = "MethodNotAllowedError";
1958
1978
  }
@@ -2352,15 +2372,19 @@ function createFileLogSink(options) {
2352
2372
  }
2353
2373
  drainWaiters.push(resolve);
2354
2374
  });
2355
- const close = () => Promise.all(
2356
- [...streams.values()].map(
2375
+ let closed = false;
2376
+ const close = () => {
2377
+ if (closed) return Promise.resolve();
2378
+ closed = true;
2379
+ const closing = [...streams.values()].map(
2357
2380
  (stream) => new Promise((resolve) => {
2358
2381
  stream.end(() => resolve());
2359
2382
  })
2360
- )
2361
- ).then(() => {
2383
+ );
2362
2384
  streams.clear();
2363
- });
2385
+ return Promise.all(closing).then(() => {
2386
+ });
2387
+ };
2364
2388
  return { write, flush, close };
2365
2389
  }
2366
2390
 
@@ -2692,7 +2716,11 @@ function createTaskQueue(deps) {
2692
2716
  const id = await driver.enqueue(name, data, {
2693
2717
  delayMs: opts?.delayMs,
2694
2718
  retries: meta.retries ?? 0,
2695
- dedupId: opts?.dedupId
2719
+ dedupId: opts?.dedupId,
2720
+ // 执行硬限预算透传驱动(pgboss 映射 expireInSeconds):未声明 timeoutMs 时
2721
+ // 两字段为 undefined,驱动用自身兜底
2722
+ timeoutMs: meta.timeoutMs,
2723
+ graceMs: meta.graceMs
2696
2724
  });
2697
2725
  if (!records.has(id)) {
2698
2726
  records.set(id, {
@@ -2841,16 +2869,19 @@ function createCronScheduler(registry, enqueue) {
2841
2869
  this.stop();
2842
2870
  for (const task of registry.list()) {
2843
2871
  if (!task.cron) continue;
2872
+ let pendingSlot = null;
2844
2873
  const schedule = new Cron(task.cron, () => {
2845
- const runAt = schedule.currentRun();
2874
+ const slot = pendingSlot;
2875
+ pendingSlot = schedule.nextRun();
2846
2876
  void Promise.resolve().then(
2847
2877
  () => enqueue(task.name, {
2848
- dedupId: runAt ? `cron:${task.name}:${runAt.toISOString()}` : void 0
2878
+ dedupId: slot ? `cron:${task.name}:${slot.toISOString()}` : void 0
2849
2879
  })
2850
2880
  ).catch((err) => {
2851
2881
  console.error(`[faapi] Cron enqueue failed for task "${task.name}":`, err);
2852
2882
  });
2853
2883
  });
2884
+ pendingSlot = schedule.nextRun();
2854
2885
  schedules.push(schedule);
2855
2886
  }
2856
2887
  },
@@ -2975,7 +3006,7 @@ function listSkills() {
2975
3006
  }
2976
3007
 
2977
3008
  // src/loader/loadAgentModule.ts
2978
- import fs11 from "fs";
3009
+ import fs12 from "fs";
2979
3010
 
2980
3011
  // src/loader/resolveExports.ts
2981
3012
  function resolveExport(module, exportName) {
@@ -2993,6 +3024,7 @@ function resolveExport(module, exportName) {
2993
3024
  }
2994
3025
 
2995
3026
  // src/utils/importWithCacheBust.ts
3027
+ import fs4 from "fs";
2996
3028
  import { pathToFileURL as pathToFileURL2 } from "url";
2997
3029
  var loadTs;
2998
3030
  function setLoadTimestamp(ts11) {
@@ -3003,13 +3035,26 @@ function getVitestImportActual() {
3003
3035
  if (typeof vi?.importActual !== "function") return void 0;
3004
3036
  return vi.importActual.bind(vi);
3005
3037
  }
3038
+ var bustCache = /* @__PURE__ */ new Map();
3039
+ var bustCounter = 0;
3006
3040
  async function importWithCacheBust(filePath, bustViteCache = false) {
3007
3041
  const importActual = getVitestImportActual();
3008
3042
  if (importActual) {
3009
3043
  if (bustViteCache) {
3044
+ let mtimeMs = 0;
3045
+ try {
3046
+ mtimeMs = fs4.statSync(filePath).mtimeMs;
3047
+ } catch {
3048
+ }
3049
+ const cached = bustCache.get(filePath);
3050
+ if (cached && cached.mtimeMs === mtimeMs) {
3051
+ return cached.module;
3052
+ }
3010
3053
  let url2 = pathToFileURL2(filePath).href;
3011
- url2 += `?t=${Date.now()}`;
3012
- return await import(url2);
3054
+ url2 += `?t=${Date.now()}-${++bustCounter}`;
3055
+ const module = await import(url2);
3056
+ bustCache.set(filePath, { mtimeMs, module });
3057
+ return module;
3013
3058
  }
3014
3059
  return await importActual(filePath);
3015
3060
  }
@@ -3021,18 +3066,18 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
3021
3066
  }
3022
3067
 
3023
3068
  // src/cli/compileOnDemand.ts
3024
- import path13 from "path";
3025
- import fs10 from "fs";
3069
+ import path14 from "path";
3070
+ import fs11 from "fs";
3026
3071
 
3027
3072
  // src/cli/compileSourceFiles.ts
3028
3073
  import path9 from "path";
3029
- import fs7 from "fs";
3074
+ import fs8 from "fs";
3030
3075
  import fg2 from "fast-glob";
3031
3076
 
3032
3077
  // src/cli/aliasPlugin.ts
3033
3078
  import ts7 from "typescript";
3034
3079
  import path8 from "path";
3035
- import fs6 from "fs";
3080
+ import fs7 from "fs";
3036
3081
 
3037
3082
  // src/utils/resolveAlias.ts
3038
3083
  function resolveAlias(specifier, config) {
@@ -3059,7 +3104,7 @@ function resolveAlias(specifier, config) {
3059
3104
 
3060
3105
  // src/utils/prodPaths.ts
3061
3106
  import path6 from "path";
3062
- import fs4 from "fs";
3107
+ import fs5 from "fs";
3063
3108
  var APP_DIR = "src";
3064
3109
  var ROUTE_PATTERNS = ["src/api/**/*.ts"];
3065
3110
  function toProdFilePath(filePath, dist) {
@@ -3078,7 +3123,7 @@ function toProdExtension(filePath) {
3078
3123
  }
3079
3124
  function toRealPath(p) {
3080
3125
  try {
3081
- return fs4.realpathSync(p);
3126
+ return fs5.realpathSync(p);
3082
3127
  } catch {
3083
3128
  return p;
3084
3129
  }
@@ -3091,14 +3136,14 @@ function isInsideDir(filePath, dir) {
3091
3136
  // src/utils/readTsconfig.ts
3092
3137
  import ts6 from "typescript";
3093
3138
  import path7 from "path";
3094
- import fs5 from "fs";
3139
+ import fs6 from "fs";
3095
3140
  var tsconfigCache = /* @__PURE__ */ new Map();
3096
3141
  function readTsconfig(rootDir) {
3097
3142
  const tsconfigPath = path7.resolve(rootDir, "tsconfig.json");
3098
- if (!fs5.existsSync(tsconfigPath)) return null;
3143
+ if (!fs6.existsSync(tsconfigPath)) return null;
3099
3144
  let mtimeMs;
3100
3145
  try {
3101
- mtimeMs = fs5.statSync(tsconfigPath).mtimeMs;
3146
+ mtimeMs = fs6.statSync(tsconfigPath).mtimeMs;
3102
3147
  } catch {
3103
3148
  return null;
3104
3149
  }
@@ -3161,25 +3206,25 @@ function resolveRelativeSpecifier(importer, specifier) {
3161
3206
  const importerDir = path8.dirname(importer);
3162
3207
  const base = path8.resolve(importerDir, specifier);
3163
3208
  if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
3164
- if (fs6.existsSync(base)) return base;
3209
+ if (fs7.existsSync(base)) return base;
3165
3210
  if (base.endsWith(".js")) {
3166
3211
  for (const ext of [".ts", ".tsx", ".jsx"]) {
3167
3212
  const file = base.slice(0, -3) + ext;
3168
- if (fs6.existsSync(file)) return file;
3213
+ if (fs7.existsSync(file)) return file;
3169
3214
  }
3170
3215
  }
3171
3216
  return null;
3172
3217
  }
3173
3218
  if (/\.(ts|tsx|jsx)$/.test(specifier)) {
3174
- return fs6.existsSync(base) ? base : null;
3219
+ return fs7.existsSync(base) ? base : null;
3175
3220
  }
3176
3221
  for (const ext of SOURCE_EXTS) {
3177
3222
  const file = base + ext;
3178
- if (fs6.existsSync(file)) return file;
3223
+ if (fs7.existsSync(file)) return file;
3179
3224
  }
3180
3225
  for (const indexExt of INDEX_EXTS) {
3181
3226
  const file = base + indexExt;
3182
- if (fs6.existsSync(file)) return file;
3227
+ if (fs7.existsSync(file)) return file;
3183
3228
  }
3184
3229
  return null;
3185
3230
  }
@@ -3188,11 +3233,11 @@ function createAliasPlugin(config, options) {
3188
3233
  const probeCandidateFile = (candidate) => {
3189
3234
  for (const ext of SOURCE_EXTS) {
3190
3235
  const file = candidate + ext;
3191
- if (fs6.existsSync(file)) return file;
3236
+ if (fs7.existsSync(file)) return file;
3192
3237
  }
3193
3238
  for (const indexExt of INDEX_EXTS) {
3194
3239
  const file = candidate + indexExt;
3195
- if (fs6.existsSync(file)) return file;
3240
+ if (fs7.existsSync(file)) return file;
3196
3241
  }
3197
3242
  return null;
3198
3243
  };
@@ -3202,7 +3247,7 @@ function createAliasPlugin(config, options) {
3202
3247
  build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
3203
3248
  let source;
3204
3249
  try {
3205
- source = fs6.readFileSync(args.path, "utf8");
3250
+ source = fs7.readFileSync(args.path, "utf8");
3206
3251
  } catch {
3207
3252
  return void 0;
3208
3253
  }
@@ -3328,7 +3373,7 @@ async function compileSourceFiles(options) {
3328
3373
  return { compiledFiles: [] };
3329
3374
  }
3330
3375
  const absDist = path9.resolve(rootDir, dist);
3331
- await fs7.promises.mkdir(absDist, { recursive: true });
3376
+ await fs8.promises.mkdir(absDist, { recursive: true });
3332
3377
  const plugins = buildAliasPlugins(rootDir);
3333
3378
  const esbuild = await import("esbuild");
3334
3379
  const outbase = path9.resolve(rootDir, APP_DIR);
@@ -3351,10 +3396,10 @@ async function compileSourceFiles(options) {
3351
3396
  if (atomicWrite && result.outputFiles) {
3352
3397
  await Promise.all(
3353
3398
  result.outputFiles.map(async (file) => {
3354
- await fs7.promises.mkdir(path9.dirname(file.path), { recursive: true });
3399
+ await fs8.promises.mkdir(path9.dirname(file.path), { recursive: true });
3355
3400
  const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
3356
- await fs7.promises.writeFile(tmp, file.contents);
3357
- await fs7.promises.rename(tmp, file.path);
3401
+ await fs8.promises.writeFile(tmp, file.contents);
3402
+ await fs8.promises.rename(tmp, file.path);
3358
3403
  })
3359
3404
  );
3360
3405
  }
@@ -3371,8 +3416,8 @@ init_generateSchemaFiles();
3371
3416
  init_generateSchemaFiles();
3372
3417
 
3373
3418
  // src/cli/collectImports.ts
3374
- import path12 from "path";
3375
- import fs9 from "fs";
3419
+ import path13 from "path";
3420
+ import fs10 from "fs";
3376
3421
  var SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
3377
3422
  function extractImportSpecifiers(source) {
3378
3423
  const specifiers = [];
@@ -3384,10 +3429,10 @@ function extractImportSpecifiers(source) {
3384
3429
  return specifiers;
3385
3430
  }
3386
3431
  function resolveSpecifierFromDir(dir, specifier) {
3387
- return resolveRelativeSpecifier(path12.join(dir, "__faapi_probe__.ts"), specifier);
3432
+ return resolveRelativeSpecifier(path13.join(dir, "__faapi_probe__.ts"), specifier);
3388
3433
  }
3389
3434
  async function collectRelativeImports(entryFiles, rootDir) {
3390
- const appDirAbs = toRealPath(path12.resolve(rootDir, "src"));
3435
+ const appDirAbs = toRealPath(path13.resolve(rootDir, "src"));
3391
3436
  const tsconfig = readTsconfig(rootDir);
3392
3437
  const visited = /* @__PURE__ */ new Set();
3393
3438
  const insideFiles = /* @__PURE__ */ new Set();
@@ -3397,7 +3442,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
3397
3442
  visited.add(filePath);
3398
3443
  let source;
3399
3444
  try {
3400
- source = await fs9.promises.readFile(filePath, "utf8");
3445
+ source = await fs10.promises.readFile(filePath, "utf8");
3401
3446
  } catch {
3402
3447
  return;
3403
3448
  }
@@ -3419,7 +3464,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
3419
3464
  resolved = null;
3420
3465
  }
3421
3466
  if (!resolved) continue;
3422
- if (!isInsideDir(toRealPath(resolved), toRealPath(path12.resolve(rootDir)))) continue;
3467
+ if (!isInsideDir(toRealPath(resolved), toRealPath(path13.resolve(rootDir)))) continue;
3423
3468
  if (isInsideDir(toRealPath(resolved), appDirAbs)) {
3424
3469
  insideFiles.add(resolved);
3425
3470
  } else {
@@ -3440,8 +3485,8 @@ async function collectRelativeImports(entryFiles, rootDir) {
3440
3485
  // src/cli/compileOnDemand.ts
3441
3486
  function isProductFresh(sourceAbsPath, productAbsPath) {
3442
3487
  try {
3443
- const srcStat = fs10.statSync(sourceAbsPath);
3444
- const prodStat = fs10.statSync(productAbsPath);
3488
+ const srcStat = fs11.statSync(sourceAbsPath);
3489
+ const prodStat = fs11.statSync(productAbsPath);
3445
3490
  return prodStat.mtimeMs >= srcStat.mtimeMs;
3446
3491
  } catch {
3447
3492
  return false;
@@ -3466,14 +3511,13 @@ function clearCompiledFiles() {
3466
3511
  async function ensureCompiled(sourceAbsPath, rootDir, dist) {
3467
3512
  const inFlight2 = state.inFlightCompilations.get(sourceAbsPath);
3468
3513
  if (inFlight2) {
3469
- await inFlight2.catch(() => {
3470
- });
3514
+ await inFlight2;
3471
3515
  return false;
3472
3516
  }
3473
3517
  if (state.compiledFiles.has(sourceAbsPath)) {
3474
3518
  return false;
3475
3519
  }
3476
- if (!fs10.existsSync(sourceAbsPath)) {
3520
+ if (!fs11.existsSync(sourceAbsPath)) {
3477
3521
  return false;
3478
3522
  }
3479
3523
  const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
@@ -3523,11 +3567,11 @@ async function ensureMiddlewaresCompiled(middlewarePaths, rootDir) {
3523
3567
  }
3524
3568
  }
3525
3569
  function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
3526
- const rel = path13.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
3570
+ const rel = path14.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
3527
3571
  if (!rel.startsWith("src/")) return null;
3528
3572
  const relWithoutSrc = rel.slice(4);
3529
3573
  const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
3530
- return path13.resolve(rootDir, dist, jsRel);
3574
+ return path14.resolve(rootDir, dist, jsRel);
3531
3575
  }
3532
3576
  function clearGeneratedSchemas() {
3533
3577
  state.generatedSchemas.clear();
@@ -3536,16 +3580,15 @@ function clearGeneratedSchemas() {
3536
3580
  async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
3537
3581
  const inFlight2 = state.inFlightSchemaGenerations.get(schemaPath);
3538
3582
  if (inFlight2) {
3539
- await inFlight2.catch(() => {
3540
- });
3583
+ await inFlight2;
3541
3584
  return false;
3542
3585
  }
3543
3586
  if (state.generatedSchemas.has(schemaPath)) {
3544
3587
  return false;
3545
3588
  }
3546
- const prodAbsPath = path13.resolve(rootDir, routeFilePath);
3589
+ const prodAbsPath = path14.resolve(rootDir, routeFilePath);
3547
3590
  const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
3548
- if (!fs10.existsSync(sourceAbsPath)) {
3591
+ if (!fs11.existsSync(sourceAbsPath)) {
3549
3592
  return false;
3550
3593
  }
3551
3594
  if (isProductFresh(sourceAbsPath, schemaPath)) {
@@ -3556,7 +3599,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
3556
3599
  if (fileRoutes.length === 0) {
3557
3600
  return false;
3558
3601
  }
3559
- const sourceRelPath = path13.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
3602
+ const sourceRelPath = path14.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
3560
3603
  const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
3561
3604
  const generatePromise = (async () => {
3562
3605
  await generateSchemaFiles(sourceRoutes, rootDir, dist);
@@ -3577,7 +3620,7 @@ async function deleteSchemaFiles(routes, rootDir, dist) {
3577
3620
  if (deleted.has(schemaPath)) continue;
3578
3621
  deleted.add(schemaPath);
3579
3622
  try {
3580
- await fs10.promises.unlink(schemaPath);
3623
+ await fs11.promises.unlink(schemaPath);
3581
3624
  } catch {
3582
3625
  }
3583
3626
  }
@@ -3586,19 +3629,19 @@ var sourcePathCache = /* @__PURE__ */ new Map();
3586
3629
  function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
3587
3630
  const cached = sourcePathCache.get(prodAbsPath);
3588
3631
  if (cached) return cached;
3589
- const rel = path13.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
3632
+ const rel = path14.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
3590
3633
  let relWithoutDist = rel;
3591
3634
  if (relWithoutDist.startsWith(`${dist}/`)) {
3592
3635
  relWithoutDist = relWithoutDist.slice(dist.length + 1);
3593
3636
  }
3594
3637
  const srcRel = `src/${relWithoutDist}`;
3595
3638
  const tsRel = srcRel.replace(/\.js$/, ".ts");
3596
- const tsAbs = path13.resolve(rootDir, tsRel);
3639
+ const tsAbs = path14.resolve(rootDir, tsRel);
3597
3640
  let result;
3598
- if (fs10.existsSync(tsAbs)) {
3641
+ if (fs11.existsSync(tsAbs)) {
3599
3642
  result = tsAbs;
3600
3643
  } else {
3601
- result = path13.resolve(rootDir, srcRel);
3644
+ result = path14.resolve(rootDir, srcRel);
3602
3645
  }
3603
3646
  sourcePathCache.set(prodAbsPath, result);
3604
3647
  return result;
@@ -3616,7 +3659,7 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
3616
3659
  const dist = getDevDist();
3617
3660
  if (dist) {
3618
3661
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
3619
- if (sourcePath && fs11.existsSync(sourcePath)) {
3662
+ if (sourcePath && fs12.existsSync(sourcePath)) {
3620
3663
  try {
3621
3664
  await ensureCompiled(sourcePath, rootDir, dist);
3622
3665
  } catch (compileErr) {
@@ -3649,13 +3692,13 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
3649
3692
  }
3650
3693
 
3651
3694
  // src/loader/loadToolModule.ts
3652
- import fs12 from "fs";
3695
+ import fs13 from "fs";
3653
3696
  async function loadToolModule(filePath, functionName, rootDir) {
3654
3697
  if (isDevOnDemandEnabled() && rootDir) {
3655
3698
  const dist = getDevDist();
3656
3699
  if (dist) {
3657
3700
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
3658
- if (sourcePath && fs12.existsSync(sourcePath)) {
3701
+ if (sourcePath && fs13.existsSync(sourcePath)) {
3659
3702
  try {
3660
3703
  await ensureCompiled(sourcePath, rootDir, dist);
3661
3704
  } catch (compileErr) {
@@ -3687,8 +3730,8 @@ async function loadToolModule(filePath, functionName, rootDir) {
3687
3730
  import { existsSync as existsSync2 } from "fs";
3688
3731
 
3689
3732
  // src/cli/generateToolArtifacts.ts
3690
- import path14 from "path";
3691
- import { existsSync } from "fs";
3733
+ init_generateZodArtifacts();
3734
+ import path15 from "path";
3692
3735
 
3693
3736
  // src/ast/extractToolMetadata.ts
3694
3737
  import ts9 from "typescript";
@@ -3777,11 +3820,9 @@ function getFirstParamTypeName(fn, sourceFile) {
3777
3820
  // src/cli/generateToolArtifacts.ts
3778
3821
  init_createProgram();
3779
3822
  init_atomicWrite();
3780
- init_generateSchemaFiles();
3781
3823
  init_extractHandlerTypes();
3782
3824
  init_generateZodSchema();
3783
3825
  init_generateSchemaFiles();
3784
- init_generateSchemaFiles();
3785
3826
  var TOOLS_FILE = "faapi-tools.js";
3786
3827
  function serializeTools(tools, dist = "dist") {
3787
3828
  return tools.map((t) => ({
@@ -3811,7 +3852,7 @@ function collectToolSchemaSources(tools, rootDir) {
3811
3852
  const toolsByFile = /* @__PURE__ */ new Map();
3812
3853
  for (const tool of tools) {
3813
3854
  if (!tool.inputTypeName) continue;
3814
- const absPath = path14.resolve(rootDir, tool.filePath);
3855
+ const absPath = path15.resolve(rootDir, tool.filePath);
3815
3856
  let list = toolsByFile.get(absPath);
3816
3857
  if (!list) {
3817
3858
  list = [];
@@ -3869,17 +3910,11 @@ function generateToolSchemaFileSource(sources, resolveType, helpersImportPath) {
3869
3910
  lines.push(...schemaBlocks);
3870
3911
  return lines.join("\n").replace(/\n+$/, "\n");
3871
3912
  }
3872
- async function maybeGenerateHelpers(allSourceCode, distDir) {
3873
- if (!usesCoerceHelpers(allSourceCode)) return;
3874
- const helpersPath = path14.resolve(distDir, HELPERS_FILENAME);
3875
- if (existsSync(helpersPath)) return;
3876
- await atomicWriteFile(helpersPath, generateHelpersFileSource());
3877
- }
3878
3913
  async function generateToolArtifacts(tools, rootDir, dist, options) {
3879
3914
  const metadata = [];
3880
- const programByFile = createPrograms(tools.map((m) => path14.resolve(rootDir, m.filePath)));
3915
+ const programByFile = createPrograms(tools.map((m) => path15.resolve(rootDir, m.filePath)));
3881
3916
  for (const manifest of tools) {
3882
- const absPath = path14.resolve(rootDir, manifest.filePath);
3917
+ const absPath = path15.resolve(rootDir, manifest.filePath);
3883
3918
  const program = programByFile.get(absPath);
3884
3919
  const result = extractToolMetadata(program, absPath, manifest.functionName, {
3885
3920
  name: manifest.name,
@@ -3890,7 +3925,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
3890
3925
  }
3891
3926
  }
3892
3927
  const serialized = serializeTools(metadata, dist);
3893
- const toolsPath = path14.resolve(rootDir, dist, TOOLS_FILE);
3928
+ const toolsPath = path15.resolve(rootDir, dist, TOOLS_FILE);
3894
3929
  await writeToolsModule(serialized, toolsPath);
3895
3930
  if (options?.skipSchema) {
3896
3931
  return metadata;
@@ -3902,45 +3937,14 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
3902
3937
  if (sources.length === 0) {
3903
3938
  return metadata;
3904
3939
  }
3905
- const sourcesByFile = /* @__PURE__ */ new Map();
3906
- for (const source of sources) {
3907
- let list = sourcesByFile.get(source.filePath);
3908
- if (!list) {
3909
- list = [];
3910
- sourcesByFile.set(source.filePath, list);
3911
- }
3912
- list.push(source);
3913
- }
3914
- const fileEntries = [];
3915
- for (const [filePath, fileSources] of sourcesByFile) {
3916
- const relFile = path14.relative(rootDir, filePath).replace(/\\/g, "/");
3917
- const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
3918
- const resolver = resolversByFile.get(filePath);
3919
- let relForDir = relFile;
3920
- if (relForDir.startsWith("src/")) {
3921
- relForDir = relForDir.slice(4);
3922
- }
3923
- const dirIdx = relForDir.lastIndexOf("/");
3924
- const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
3925
- const helpersImportPath = getHelpersImportPath(zodRelDir);
3926
- const source = generateToolSchemaFileSource(
3927
- fileSources,
3928
- (name) => resolver?.resolve(name)?.runtimeType,
3929
- helpersImportPath
3930
- );
3931
- fileEntries.push({ outputPath, source });
3932
- }
3933
- const allSourceCode = fileEntries.map((e) => e.source).join("\n");
3934
- const distDir = path14.resolve(rootDir, dist);
3935
- await maybeGenerateHelpers(allSourceCode, distDir);
3936
- await Promise.all(
3937
- fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source))
3938
- );
3940
+ await generateZodArtifacts(sources, {
3941
+ generateFileSource: generateToolSchemaFileSource,
3942
+ resolversByFile,
3943
+ rootDir,
3944
+ dist
3945
+ });
3939
3946
  return metadata;
3940
3947
  }
3941
- async function writeToolSchemaFile(outputPath, source) {
3942
- await atomicWriteFile(outputPath, source);
3943
- }
3944
3948
 
3945
3949
  // src/loader/loadToolSchema.ts
3946
3950
  function getDist() {
@@ -3979,6 +3983,20 @@ function clearAgentHandleFactory() {
3979
3983
  defaultRegistries.agentHandle.clear();
3980
3984
  }
3981
3985
 
3986
+ // src/middleware/mergeVary.ts
3987
+ function mergeVary(meta, value) {
3988
+ const existing = meta.headers["Vary"] ?? meta.headers["vary"];
3989
+ if (!existing) {
3990
+ meta.headers["Vary"] = value;
3991
+ return;
3992
+ }
3993
+ const segments = existing.split(",").map((s) => s.trim().toLowerCase());
3994
+ if (!segments.includes(value.toLowerCase())) {
3995
+ const key = meta.headers["Vary"] !== void 0 ? "Vary" : "vary";
3996
+ meta.headers[key] = `${existing}, ${value}`;
3997
+ }
3998
+ }
3999
+
3982
4000
  // src/middleware/cors.ts
3983
4001
  var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
3984
4002
  function cors(options = {}) {
@@ -3991,6 +4009,7 @@ function cors(options = {}) {
3991
4009
  maxAge
3992
4010
  } = options;
3993
4011
  return async (ctx, next) => {
4012
+ const meta = ctx.meta;
3994
4013
  const reqOrigin = ctx.headers.get("origin");
3995
4014
  if (!reqOrigin) {
3996
4015
  await next();
@@ -4005,14 +4024,7 @@ function cors(options = {}) {
4005
4024
  allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
4006
4025
  }
4007
4026
  if (origin === true || Array.isArray(origin)) {
4008
- const existingVary = ctx.headers.get("vary");
4009
- if (existingVary) {
4010
- if (!existingVary.toLowerCase().includes("origin")) {
4011
- ctx.setHeader("Vary", `${existingVary}, Origin`);
4012
- }
4013
- } else {
4014
- ctx.setHeader("Vary", "Origin");
4015
- }
4027
+ mergeVary(meta, "Origin");
4016
4028
  }
4017
4029
  if (!allowOrigin) {
4018
4030
  await next();
@@ -4157,16 +4169,16 @@ function helmet(options = {}) {
4157
4169
  }
4158
4170
 
4159
4171
  // src/config/loadConfig.ts
4160
- import path15 from "path";
4161
- import fs13 from "fs";
4172
+ import path16 from "path";
4173
+ import fs14 from "fs";
4162
4174
  var CONFIG_PRODUCT_FILE = "faapi-config.js";
4163
4175
  async function loadConfig(rootDir, dist) {
4164
- const configProductPath = path15.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
4165
- if (fs13.existsSync(configProductPath)) {
4176
+ const configProductPath = path16.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
4177
+ if (fs14.existsSync(configProductPath)) {
4166
4178
  const module = await importWithCacheBust(configProductPath);
4167
4179
  return module.default ?? {};
4168
4180
  }
4169
- const hasSourceConfig = fs13.existsSync(path15.join(rootDir, "faapi.config.ts")) || fs13.existsSync(path15.join(rootDir, "faapi.config.js"));
4181
+ const hasSourceConfig = fs14.existsSync(path16.join(rootDir, "faapi.config.ts")) || fs14.existsSync(path16.join(rootDir, "faapi.config.js"));
4170
4182
  if (hasSourceConfig) {
4171
4183
  throw new Error(
4172
4184
  `[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`
@@ -4176,8 +4188,8 @@ async function loadConfig(rootDir, dist) {
4176
4188
  }
4177
4189
 
4178
4190
  // src/cli/loadEnv.ts
4179
- import fs14 from "fs";
4180
- import path16 from "path";
4191
+ import fs15 from "fs";
4192
+ import path17 from "path";
4181
4193
  function resolveEnv() {
4182
4194
  return process.env.NODE_ENV || "development";
4183
4195
  }
@@ -4244,9 +4256,9 @@ function loadEnv(rootDir) {
4244
4256
  const files = getEnvFiles(env);
4245
4257
  const merged = {};
4246
4258
  for (const file of files) {
4247
- const filePath = path16.join(rootDir, file);
4248
- if (!fs14.existsSync(filePath)) continue;
4249
- const content = fs14.readFileSync(filePath, "utf-8");
4259
+ const filePath = path17.join(rootDir, file);
4260
+ if (!fs15.existsSync(filePath)) continue;
4261
+ const content = fs15.readFileSync(filePath, "utf-8");
4250
4262
  const parsed = parseEnvFile(content, merged);
4251
4263
  Object.assign(merged, parsed);
4252
4264
  }
@@ -4258,9 +4270,8 @@ function loadEnv(rootDir) {
4258
4270
  }
4259
4271
 
4260
4272
  // src/cli/createAppCore.ts
4261
- import fs19 from "fs";
4262
- import path24 from "path";
4263
- import { PassThrough, Readable as Readable3 } from "stream";
4273
+ import fs21 from "fs";
4274
+ import path26 from "path";
4264
4275
 
4265
4276
  // src/router/sortRoutes.ts
4266
4277
  function sortRoutes(routes) {
@@ -4304,6 +4315,18 @@ function detectRouteConflicts(routes) {
4304
4315
  }
4305
4316
  return conflicts;
4306
4317
  }
4318
+ function reportRouteConflicts(routes) {
4319
+ const conflicts = detectRouteConflicts(routes);
4320
+ if (conflicts.length === 0) return false;
4321
+ console.warn("! \u8DEF\u7531\u51B2\u7A81:");
4322
+ for (const conflict of conflicts) {
4323
+ console.warn(` ${conflict.method} ${conflict.urlPath}`);
4324
+ for (const file of conflict.files) {
4325
+ console.warn(` - ${file}`);
4326
+ }
4327
+ }
4328
+ return true;
4329
+ }
4307
4330
 
4308
4331
  // src/server/createServer.ts
4309
4332
  import {
@@ -4312,7 +4335,7 @@ import {
4312
4335
  import { createSecureServer as createHttp2SecureServer } from "http2";
4313
4336
  import { readFileSync } from "fs";
4314
4337
  import { Readable as Readable2 } from "stream";
4315
- import path18 from "path";
4338
+ import path19 from "path";
4316
4339
 
4317
4340
  // src/router/matchRoute.ts
4318
4341
  var httpIndexCache = /* @__PURE__ */ new WeakMap();
@@ -4354,65 +4377,72 @@ function getWsIndex(routes) {
4354
4377
  wsIndexCache.set(routes, index);
4355
4378
  return index;
4356
4379
  }
4357
- function matchRoute(routes, method, path29) {
4380
+ function matchRoute(routes, method, path31) {
4358
4381
  const index = getHttpIndex(routes);
4359
4382
  const upper = method.toUpperCase();
4360
- const hit = matchByMethod(index, upper, path29);
4383
+ const hit = matchByMethod(index, upper, path31);
4361
4384
  if (hit) return hit;
4362
4385
  if (upper === "HEAD") {
4363
- return matchByMethod(index, "GET", path29);
4386
+ return matchByMethod(index, "GET", path31);
4364
4387
  }
4365
4388
  return null;
4366
4389
  }
4367
- function matchByMethod(index, method, path29) {
4368
- const staticHit = index.static.get(`${method}|${path29}`);
4390
+ function matchByMethod(index, method, path31) {
4391
+ const staticHit = index.static.get(`${method}|${path31}`);
4369
4392
  if (staticHit) {
4370
4393
  return { route: staticHit, params: {} };
4371
4394
  }
4395
+ if (index.dynamics.length === 0) {
4396
+ return null;
4397
+ }
4398
+ const pathSegments = path31.split("/").filter(Boolean);
4372
4399
  for (const entry of index.dynamics) {
4373
4400
  const route = entry.route;
4374
4401
  if (route.method !== method) {
4375
4402
  continue;
4376
4403
  }
4377
- const params = matchSegments(entry.segments, path29, route.paramNames, route.isCatchAll);
4404
+ const params = matchSegments(entry.segments, pathSegments, route.paramNames, route.isCatchAll);
4378
4405
  if (params !== null) {
4379
4406
  return { route, params };
4380
4407
  }
4381
4408
  }
4382
4409
  return null;
4383
4410
  }
4384
- function matchWsRoute(wsRoutes, path29) {
4411
+ function matchWsRoute(wsRoutes, path31) {
4385
4412
  const index = getWsIndex(wsRoutes);
4386
- const staticHit = index.static.get(path29);
4413
+ const staticHit = index.static.get(path31);
4387
4414
  if (staticHit) {
4388
4415
  return { route: staticHit, params: {} };
4389
4416
  }
4390
4417
  for (const route of index.dynamics) {
4391
- const params = matchDynamicPath(route.urlPath, path29, route.paramNames, route.isCatchAll);
4418
+ const params = matchDynamicPath(route.urlPath, path31, route.paramNames, route.isCatchAll);
4392
4419
  if (params !== null) {
4393
4420
  return { route, params };
4394
4421
  }
4395
4422
  }
4396
4423
  return null;
4397
4424
  }
4398
- function findAllowedMethods(routes, path29) {
4425
+ function findAllowedMethods(routes, path31) {
4399
4426
  const index = getHttpIndex(routes);
4400
4427
  const methods = /* @__PURE__ */ new Set();
4401
- const staticMethods = index.methodsByStaticPath.get(path29);
4428
+ const staticMethods = index.methodsByStaticPath.get(path31);
4402
4429
  if (staticMethods) {
4403
4430
  for (const method of staticMethods) {
4404
4431
  methods.add(method);
4405
4432
  }
4406
4433
  }
4407
- for (const entry of index.dynamics) {
4408
- const params = matchSegments(
4409
- entry.segments,
4410
- path29,
4411
- entry.route.paramNames,
4412
- entry.route.isCatchAll
4413
- );
4414
- if (params !== null) {
4415
- methods.add(entry.route.method);
4434
+ if (index.dynamics.length > 0) {
4435
+ const pathSegments = path31.split("/").filter(Boolean);
4436
+ for (const entry of index.dynamics) {
4437
+ const params = matchSegments(
4438
+ entry.segments,
4439
+ pathSegments,
4440
+ entry.route.paramNames,
4441
+ entry.route.isCatchAll
4442
+ );
4443
+ if (params !== null) {
4444
+ methods.add(entry.route.method);
4445
+ }
4416
4446
  }
4417
4447
  }
4418
4448
  if (methods.has("GET")) {
@@ -4420,11 +4450,15 @@ function findAllowedMethods(routes, path29) {
4420
4450
  }
4421
4451
  return Array.from(methods);
4422
4452
  }
4423
- function matchDynamicPath(pattern, path29, paramNames, isCatchAll) {
4424
- return matchSegments(pattern.split("/").filter(Boolean), path29, paramNames, isCatchAll);
4453
+ function matchDynamicPath(pattern, path31, paramNames, isCatchAll) {
4454
+ return matchSegments(
4455
+ pattern.split("/").filter(Boolean),
4456
+ path31.split("/").filter(Boolean),
4457
+ paramNames,
4458
+ isCatchAll
4459
+ );
4425
4460
  }
4426
- function matchSegments(patternSegments, path29, paramNames, isCatchAll) {
4427
- const pathSegments = path29.split("/").filter(Boolean);
4461
+ function matchSegments(patternSegments, pathSegments, paramNames, isCatchAll) {
4428
4462
  if (isCatchAll) {
4429
4463
  const nonCatchAllCount = patternSegments.length - 1;
4430
4464
  if (pathSegments.length <= nonCatchAllCount) {
@@ -4508,6 +4542,15 @@ async function loadRouteModule(filePath, method, rootDir) {
4508
4542
  return { handler, method };
4509
4543
  }
4510
4544
 
4545
+ // src/response/bufferedBody.ts
4546
+ var bufferedBodyResponses = /* @__PURE__ */ new WeakSet();
4547
+ function markBufferedBody(response) {
4548
+ bufferedBodyResponses.add(response);
4549
+ }
4550
+ function hasBufferedBody(response) {
4551
+ return bufferedBodyResponses.has(response);
4552
+ }
4553
+
4511
4554
  // src/utils/isPlainObject.ts
4512
4555
  function isPlainObject(value) {
4513
4556
  if (value === null || typeof value !== "object") {
@@ -4522,8 +4565,50 @@ function isPlainObject(value) {
4522
4565
 
4523
4566
  // src/utils/stringifyJson.ts
4524
4567
  function stringifyJson(value) {
4568
+ if (!needsConversion(value, /* @__PURE__ */ new Set())) {
4569
+ return JSON.stringify(value);
4570
+ }
4525
4571
  return JSON.stringify(convertValue(value, /* @__PURE__ */ new Set()));
4526
4572
  }
4573
+ function needsConversion(value, ancestors) {
4574
+ if (value === null) return false;
4575
+ const type = typeof value;
4576
+ if (type === "bigint") return true;
4577
+ if (type === "number") return !Number.isFinite(value);
4578
+ if (type !== "object") return false;
4579
+ const obj = value;
4580
+ if (ancestors.has(obj)) {
4581
+ throw new TypeError("[faapi] Converting circular structure to JSON");
4582
+ }
4583
+ if (obj instanceof Date || obj instanceof Map || obj instanceof Set || obj instanceof RegExp) {
4584
+ return true;
4585
+ }
4586
+ if (typeof obj.toJSON === "function") return true;
4587
+ if (Array.isArray(obj)) {
4588
+ ancestors.add(obj);
4589
+ for (let i = 0; i < obj.length; i++) {
4590
+ if (needsConversion(obj[i], ancestors)) {
4591
+ ancestors.delete(obj);
4592
+ return true;
4593
+ }
4594
+ }
4595
+ ancestors.delete(obj);
4596
+ return false;
4597
+ }
4598
+ if (isPlainObject(obj)) {
4599
+ ancestors.add(obj);
4600
+ for (const key in obj) {
4601
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
4602
+ if (needsConversion(obj[key], ancestors)) {
4603
+ ancestors.delete(obj);
4604
+ return true;
4605
+ }
4606
+ }
4607
+ ancestors.delete(obj);
4608
+ return false;
4609
+ }
4610
+ return false;
4611
+ }
4527
4612
  function convertValue(value, ancestors) {
4528
4613
  if (value === null) return value;
4529
4614
  const type = typeof value;
@@ -4618,16 +4703,30 @@ function createSseWriter() {
4618
4703
  let controller = null;
4619
4704
  let closed = false;
4620
4705
  let aborted = false;
4621
- const stream = new ReadableStream({
4622
- start(c) {
4623
- controller = c;
4706
+ let drainWaiters = [];
4707
+ const resolveDrainWaiters = () => {
4708
+ const waiters = drainWaiters;
4709
+ drainWaiters = [];
4710
+ for (const resolve of waiters) resolve();
4711
+ };
4712
+ const stream = new ReadableStream(
4713
+ {
4714
+ start(c) {
4715
+ controller = c;
4716
+ },
4717
+ // 消费者排空缓冲后再要数据 → 背压解除,唤醒 waitForDrain 等待方
4718
+ pull() {
4719
+ resolveDrainWaiters();
4720
+ },
4721
+ cancel() {
4722
+ aborted = true;
4723
+ closed = true;
4724
+ controller = null;
4725
+ resolveDrainWaiters();
4726
+ }
4624
4727
  },
4625
- cancel() {
4626
- aborted = true;
4627
- closed = true;
4628
- controller = null;
4629
- }
4630
- });
4728
+ { highWaterMark: 16 }
4729
+ );
4631
4730
  const response = new Response(stream, {
4632
4731
  status: 200,
4633
4732
  headers: {
@@ -4662,6 +4761,7 @@ function createSseWriter() {
4662
4761
  close() {
4663
4762
  if (closed) return;
4664
4763
  closed = true;
4764
+ resolveDrainWaiters();
4665
4765
  if (controller) {
4666
4766
  try {
4667
4767
  controller.close();
@@ -4673,8 +4773,18 @@ function createSseWriter() {
4673
4773
  get closed() {
4674
4774
  return closed;
4675
4775
  },
4676
- get aborted() {
4677
- return aborted;
4776
+ get desiredSize() {
4777
+ return controller ? controller.desiredSize : null;
4778
+ },
4779
+ waitForDrain() {
4780
+ if (closed || !controller) return Promise.resolve();
4781
+ if ((controller.desiredSize ?? 0) > 0) return Promise.resolve();
4782
+ return new Promise((resolve) => {
4783
+ drainWaiters.push(resolve);
4784
+ });
4785
+ },
4786
+ get aborted() {
4787
+ return aborted;
4678
4788
  },
4679
4789
  get response() {
4680
4790
  return response;
@@ -4710,7 +4820,9 @@ function jsonRaw(body, status, extraHeaders) {
4710
4820
  const extra = new Headers(extraHeaders);
4711
4821
  extra.forEach((value, key) => headers.set(key, value));
4712
4822
  }
4713
- return new Response(stringifyJson(body), { status, headers });
4823
+ const response = new Response(stringifyJson(body), { status, headers });
4824
+ markBufferedBody(response);
4825
+ return response;
4714
4826
  }
4715
4827
  function wrapOkResult(result, config) {
4716
4828
  if (result instanceof Response) return result;
@@ -4727,12 +4839,9 @@ function formatFailResponse(options, config) {
4727
4839
  }
4728
4840
  function formatErrorResponse(error, config) {
4729
4841
  const failFn = resolveFailFn(config);
4842
+ const buildFailBody = (e) => failFn({ status: e.statusCode, code: e.code, message: e.message });
4730
4843
  if (error instanceof ValidationError) {
4731
- const body2 = failFn({
4732
- status: error.statusCode,
4733
- code: error.code,
4734
- message: error.message
4735
- });
4844
+ const body2 = buildFailBody(error);
4736
4845
  const bodyObj = typeof body2 === "object" && body2 !== null ? { ...body2 } : { error: body2 };
4737
4846
  const existingError = bodyObj.error;
4738
4847
  if (existingError && typeof existingError === "object") {
@@ -4745,28 +4854,15 @@ function formatErrorResponse(error, config) {
4745
4854
  return jsonOk(bodyObj, error.statusCode);
4746
4855
  }
4747
4856
  if (error instanceof MethodNotAllowedError) {
4748
- const body2 = failFn({
4749
- status: error.statusCode,
4750
- code: error.code,
4751
- message: error.message
4857
+ return jsonOk(buildFailBody(error), error.statusCode, {
4858
+ Allow: error.allowedMethods.join(", ")
4752
4859
  });
4753
- return jsonOk(body2, error.statusCode, { Allow: error.allowedMethods.join(", ") });
4754
4860
  }
4755
4861
  if (error instanceof PayloadTooLargeError) {
4756
- const body2 = failFn({
4757
- status: error.statusCode,
4758
- code: error.code,
4759
- message: error.message
4760
- });
4761
- return jsonOk(body2, error.statusCode);
4862
+ return jsonOk(buildFailBody(error), error.statusCode);
4762
4863
  }
4763
4864
  if (error instanceof FaapiError) {
4764
- const body2 = failFn({
4765
- status: error.statusCode,
4766
- code: error.code,
4767
- message: error.message
4768
- });
4769
- return jsonOk(body2, error.statusCode);
4865
+ return jsonOk(buildFailBody(error), error.statusCode);
4770
4866
  }
4771
4867
  const message = error instanceof Error ? error.message : "An unknown error occurred";
4772
4868
  const body = failFn({ status: 500, code: "INTERNAL_ERROR", message });
@@ -4851,10 +4947,12 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
4851
4947
  },
4852
4948
  html(html, status) {
4853
4949
  const headers = { "Content-Type": "text/html; charset=utf-8" };
4854
- return new Response(html, {
4950
+ const response = new Response(html, {
4855
4951
  status: status ?? 200,
4856
4952
  headers
4857
4953
  });
4954
+ markBufferedBody(response);
4955
+ return response;
4858
4956
  },
4859
4957
  getCookie(name) {
4860
4958
  return parsedCookies.get(name);
@@ -4921,7 +5019,10 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
4921
5019
  }
4922
5020
 
4923
5021
  // src/utils/queryToObject.ts
5022
+ var queryCache = /* @__PURE__ */ new WeakMap();
4924
5023
  function queryToObject(params) {
5024
+ const cached = queryCache.get(params);
5025
+ if (cached) return cached;
4925
5026
  const result = {};
4926
5027
  for (const [key, value] of params) {
4927
5028
  const existing = result[key];
@@ -4933,6 +5034,7 @@ function queryToObject(params) {
4933
5034
  result[key] = [existing, value];
4934
5035
  }
4935
5036
  }
5037
+ queryCache.set(params, result);
4936
5038
  return result;
4937
5039
  }
4938
5040
 
@@ -5101,51 +5203,67 @@ async function toResponse(value, meta) {
5101
5203
  if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
5102
5204
  const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
5103
5205
  applyMeta(headers2);
5104
- return new Response(value, {
5105
- status: meta?.status ?? 200,
5106
- headers: headers2
5107
- });
5206
+ return markBuffered(
5207
+ new Response(value, {
5208
+ status: meta?.status ?? 200,
5209
+ headers: headers2
5210
+ })
5211
+ );
5108
5212
  }
5109
5213
  if (value instanceof Uint8Array) {
5110
5214
  const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
5111
5215
  applyMeta(headers2);
5112
- return new Response(value, {
5113
- status: meta?.status ?? 200,
5114
- headers: headers2
5115
- });
5216
+ return markBuffered(
5217
+ new Response(value, {
5218
+ status: meta?.status ?? 200,
5219
+ headers: headers2
5220
+ })
5221
+ );
5116
5222
  }
5117
5223
  if (isPlainObject(value) || Array.isArray(value)) {
5118
5224
  const body2 = stringifyJson(value);
5119
5225
  const headers2 = new Headers({ "Content-Type": "application/json" });
5120
5226
  applyMeta(headers2);
5121
- return new Response(body2, {
5122
- status: meta?.status ?? 200,
5123
- headers: headers2
5124
- });
5227
+ return markBuffered(
5228
+ new Response(body2, {
5229
+ status: meta?.status ?? 200,
5230
+ headers: headers2
5231
+ })
5232
+ );
5125
5233
  }
5126
5234
  if (typeof value === "string") {
5127
5235
  const headers2 = new Headers({ "Content-Type": "text/plain" });
5128
5236
  applyMeta(headers2);
5129
- return new Response(value, {
5130
- status: meta?.status ?? 200,
5131
- headers: headers2
5132
- });
5237
+ return markBuffered(
5238
+ new Response(value, {
5239
+ status: meta?.status ?? 200,
5240
+ headers: headers2
5241
+ })
5242
+ );
5133
5243
  }
5134
5244
  if (typeof value === "number" || typeof value === "boolean") {
5135
5245
  const headers2 = new Headers({ "Content-Type": "text/plain" });
5136
5246
  applyMeta(headers2);
5137
- return new Response(String(value), {
5138
- status: meta?.status ?? 200,
5139
- headers: headers2
5140
- });
5247
+ return markBuffered(
5248
+ new Response(String(value), {
5249
+ status: meta?.status ?? 200,
5250
+ headers: headers2
5251
+ })
5252
+ );
5141
5253
  }
5142
5254
  const body = stringifyJson(value);
5143
5255
  const headers = new Headers({ "Content-Type": "application/json" });
5144
5256
  applyMeta(headers);
5145
- return new Response(body, {
5146
- status: meta?.status ?? 200,
5147
- headers
5148
- });
5257
+ return markBuffered(
5258
+ new Response(body, {
5259
+ status: meta?.status ?? 200,
5260
+ headers
5261
+ })
5262
+ );
5263
+ }
5264
+ function markBuffered(response) {
5265
+ markBufferedBody(response);
5266
+ return response;
5149
5267
  }
5150
5268
 
5151
5269
  // src/injection/injectParams.ts
@@ -5285,18 +5403,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
5285
5403
  sseWriter.close();
5286
5404
  }
5287
5405
  };
5288
- if (!middlewares || middlewares.length === 0) {
5289
- try {
5290
- const result = await injectParamsAsync(handler, ctx, body, injectors);
5291
- const sseResponse = pickSseAndAutoClose();
5292
- if (sseResponse) return sseResponse;
5293
- return toResponse(wrapResult(result, ctx), meta);
5294
- } catch (err) {
5295
- autoCloseSseOnError();
5296
- throw err;
5297
- }
5298
- }
5299
- const finalHandler = async () => {
5406
+ const executeAndFormat = async () => {
5300
5407
  try {
5301
5408
  const result = await injectParamsAsync(handler, ctx, body, injectors);
5302
5409
  const sseResponse = pickSseAndAutoClose();
@@ -5307,6 +5414,10 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
5307
5414
  throw err;
5308
5415
  }
5309
5416
  };
5417
+ if (!middlewares || middlewares.length === 0) {
5418
+ return executeAndFormat();
5419
+ }
5420
+ const finalHandler = executeAndFormat;
5310
5421
  return await compose(middlewares, ctx, finalHandler);
5311
5422
  }
5312
5423
 
@@ -5328,6 +5439,20 @@ async function sendNodeResponse(response, res) {
5328
5439
  }
5329
5440
  }
5330
5441
  if (response.body) {
5442
+ if (hasBufferedBody(response)) {
5443
+ const buffer = await readBufferedBody(response.body);
5444
+ await new Promise((resolve) => {
5445
+ res.on("close", () => {
5446
+ if (!res.writableEnded) res.destroy();
5447
+ });
5448
+ if (res.destroyed) {
5449
+ resolve();
5450
+ return;
5451
+ }
5452
+ res.end(buffer, () => resolve());
5453
+ });
5454
+ return;
5455
+ }
5331
5456
  const nodeStream = Readable.fromWeb(response.body);
5332
5457
  await new Promise((resolve, reject) => {
5333
5458
  nodeStream.on("error", reject);
@@ -5346,6 +5471,25 @@ async function sendNodeResponse(response, res) {
5346
5471
  }
5347
5472
  res.end();
5348
5473
  }
5474
+ async function readBufferedBody(body) {
5475
+ const reader = body.getReader();
5476
+ const chunks = [];
5477
+ try {
5478
+ for (; ; ) {
5479
+ const { done, value } = await reader.read();
5480
+ if (done) break;
5481
+ chunks.push(value);
5482
+ }
5483
+ } finally {
5484
+ try {
5485
+ reader.releaseLock();
5486
+ } catch {
5487
+ }
5488
+ }
5489
+ if (chunks.length === 0) return new Uint8Array(0);
5490
+ if (chunks.length === 1) return chunks[0];
5491
+ return Buffer.concat(chunks);
5492
+ }
5349
5493
 
5350
5494
  // src/validator/validateInput.ts
5351
5495
  init_schemaName();
@@ -5389,9 +5533,9 @@ async function validateInput(schemaPath, method, inputType, input) {
5389
5533
  function mapZodIssues(error) {
5390
5534
  return error.issues.map((issue) => {
5391
5535
  const code = mapZodCode(issue);
5392
- const path29 = issue.path.map(String).join(".") || "";
5536
+ const path31 = issue.path.map(String).join(".") || "";
5393
5537
  return {
5394
- path: path29,
5538
+ path: path31,
5395
5539
  code,
5396
5540
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
5397
5541
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -5513,17 +5657,6 @@ async function compressBody(encoding, body) {
5513
5657
  return deflateAsync(buf);
5514
5658
  }
5515
5659
  }
5516
- function mergeVary(meta, value) {
5517
- const existing = meta.headers["Vary"] ?? meta.headers["vary"];
5518
- if (!existing) {
5519
- meta.headers["Vary"] = value;
5520
- return;
5521
- }
5522
- if (!existing.toLowerCase().includes(value.toLowerCase())) {
5523
- const key = meta.headers["Vary"] !== void 0 ? "Vary" : "vary";
5524
- meta.headers[key] = `${existing}, ${value}`;
5525
- }
5526
- }
5527
5660
  function compression(options = {}) {
5528
5661
  const threshold = options.threshold ?? DEFAULT_THRESHOLD;
5529
5662
  return async (ctx, next) => {
@@ -5623,9 +5756,9 @@ function etag(options = {}) {
5623
5756
  }
5624
5757
 
5625
5758
  // src/server/handleWsUpgrade.ts
5626
- import fs15 from "fs";
5759
+ import fs16 from "fs";
5627
5760
  import { WebSocketServer, WebSocket } from "ws";
5628
- import path17 from "path";
5761
+ import path18 from "path";
5629
5762
 
5630
5763
  // src/server/serverUtils.ts
5631
5764
  function nodeHttpToWebHeaders(req) {
@@ -5644,13 +5777,15 @@ function buildErrorResponse(err, config) {
5644
5777
  try {
5645
5778
  return formatErrorResponse(err, config);
5646
5779
  } catch {
5647
- return new Response(
5780
+ const fallback = new Response(
5648
5781
  JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
5649
5782
  {
5650
5783
  status: 500,
5651
5784
  headers: { "Content-Type": "application/json" }
5652
5785
  }
5653
5786
  );
5787
+ markBufferedBody(fallback);
5788
+ return fallback;
5654
5789
  }
5655
5790
  }
5656
5791
 
@@ -5760,7 +5895,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
5760
5895
  const dist = getDevDist();
5761
5896
  if (dist) {
5762
5897
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
5763
- if (sourcePath && fs15.existsSync(sourcePath)) {
5898
+ if (sourcePath && fs16.existsSync(sourcePath)) {
5764
5899
  await ensureCompiled(sourcePath, rootDir, dist);
5765
5900
  }
5766
5901
  }
@@ -5775,27 +5910,47 @@ async function loadWsHandler(filePath, ctx, rootDir) {
5775
5910
  function bindEvents(rawSocket, handlers) {
5776
5911
  if (!handlers) return;
5777
5912
  const ws = wrapWsSocket(rawSocket);
5913
+ const guard = (event, fn) => {
5914
+ try {
5915
+ fn();
5916
+ } catch (err) {
5917
+ const error = err instanceof Error ? err : new Error(String(err));
5918
+ if (handlers.onError) {
5919
+ try {
5920
+ handlers.onError(ws, error);
5921
+ } catch (hookErr) {
5922
+ console.error(`[faapi] WS onError \u56DE\u8C03\u629B\u9519 (after ${event}):`, hookErr);
5923
+ }
5924
+ } else {
5925
+ console.error(`[faapi] WS ${event} \u56DE\u8C03\u629B\u9519:`, error);
5926
+ }
5927
+ }
5928
+ };
5778
5929
  if (handlers.onOpen) {
5779
5930
  if (rawSocket.readyState === WebSocket.OPEN) {
5780
- handlers.onOpen(ws);
5931
+ guard("onOpen", () => handlers.onOpen(ws));
5781
5932
  } else {
5782
- rawSocket.once("open", () => handlers.onOpen(ws));
5933
+ rawSocket.once("open", () => guard("onOpen", () => handlers.onOpen(ws)));
5783
5934
  }
5784
5935
  }
5785
5936
  if (handlers.onMessage) {
5786
5937
  rawSocket.on("message", (data, isBinary) => {
5787
5938
  const buf = toWsBuffer(data);
5788
- handlers.onMessage(ws, isBinary ? buf : buf.toString("utf8"));
5939
+ guard("onMessage", () => handlers.onMessage(ws, isBinary ? buf : buf.toString("utf8")));
5789
5940
  });
5790
5941
  }
5791
5942
  if (handlers.onClose) {
5792
5943
  rawSocket.on("close", (code, reason) => {
5793
- handlers.onClose(ws, code, reason.toString("utf8"));
5944
+ guard("onClose", () => handlers.onClose(ws, code, reason.toString("utf8")));
5794
5945
  });
5795
5946
  }
5796
5947
  if (handlers.onError) {
5797
5948
  rawSocket.on("error", (err) => {
5798
- handlers.onError(ws, err);
5949
+ try {
5950
+ handlers.onError(ws, err);
5951
+ } catch (hookErr) {
5952
+ console.error("[faapi] WS onError \u56DE\u8C03\u629B\u9519 (after onError):", hookErr);
5953
+ }
5799
5954
  });
5800
5955
  }
5801
5956
  }
@@ -5874,7 +6029,7 @@ function attachWebSocket(options) {
5874
6029
  const finalHandler = async () => {
5875
6030
  let handlers;
5876
6031
  try {
5877
- const absoluteFilePath = path17.resolve(rootDir, route.filePath);
6032
+ const absoluteFilePath = path18.resolve(rootDir, route.filePath);
5878
6033
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
5879
6034
  } catch (err) {
5880
6035
  const reason = err instanceof Error ? err.message : String(err);
@@ -6080,9 +6235,17 @@ function createServer(options) {
6080
6235
  bodyLimit,
6081
6236
  trustedProxy,
6082
6237
  registries
6083
- ).catch(() => {
6084
- res.statusCode = 500;
6085
- res.end();
6238
+ ).catch((err) => {
6239
+ console.error("[faapi] Request pipeline failed after error response attempt:", err);
6240
+ try {
6241
+ if (!res.headersSent) {
6242
+ res.statusCode = 500;
6243
+ res.end();
6244
+ } else if (!res.writableEnded) {
6245
+ res.end();
6246
+ }
6247
+ } catch {
6248
+ }
6086
6249
  });
6087
6250
  });
6088
6251
  attachWebSocket({
@@ -6125,13 +6288,14 @@ function getRoutePaths(route, rootDir, dist) {
6125
6288
  let cached = routePathCache.get(route);
6126
6289
  if (!cached) {
6127
6290
  cached = {
6128
- absFilePath: path18.resolve(rootDir, route.filePath),
6291
+ absFilePath: path19.resolve(rootDir, route.filePath),
6129
6292
  schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
6130
6293
  };
6131
6294
  routePathCache.set(route, cached);
6132
6295
  }
6133
6296
  return cached;
6134
6297
  }
6298
+ var mergedInjectorsCache = /* @__PURE__ */ new WeakMap();
6135
6299
  function createRoutePipeline(opts) {
6136
6300
  const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;
6137
6301
  return async () => {
@@ -6164,7 +6328,15 @@ function createRoutePipeline(opts) {
6164
6328
  route.injectors = {};
6165
6329
  }
6166
6330
  }
6167
- const mergedInjectors = globalInjectors ? { ...globalInjectors, ...route.injectors } : route.injectors;
6331
+ let mergedInjectors = route.injectors;
6332
+ if (globalInjectors) {
6333
+ let merged = mergedInjectorsCache.get(route);
6334
+ if (!merged) {
6335
+ merged = { ...globalInjectors, ...route.injectors };
6336
+ mergedInjectorsCache.set(route, merged);
6337
+ }
6338
+ mergedInjectors = merged;
6339
+ }
6168
6340
  return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);
6169
6341
  };
6170
6342
  }
@@ -6214,6 +6386,10 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
6214
6386
  await sendSuccessResponse(response, res);
6215
6387
  } catch (err) {
6216
6388
  if (res.destroyed || res.writableEnded) return;
6389
+ if (err instanceof PayloadTooLargeError) {
6390
+ res.setHeader("Connection", "close");
6391
+ res.once("finish", () => req.destroy());
6392
+ }
6217
6393
  await sendErrorResponse(err, meta, res, onError, ctx);
6218
6394
  }
6219
6395
  }
@@ -6247,8 +6423,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
6247
6423
  }
6248
6424
 
6249
6425
  // src/cli/generateRoutes.ts
6250
- import fs16 from "fs";
6251
- import path19 from "path";
6426
+ import fs17 from "fs";
6427
+ import path20 from "path";
6252
6428
  async function hydrateRoutes(manifest) {
6253
6429
  const hydrateRoute = (serialized) => ({
6254
6430
  method: serialized.method,
@@ -6272,102 +6448,306 @@ async function hydrateRoutes(manifest) {
6272
6448
  return { routes, wsRoutes };
6273
6449
  }
6274
6450
 
6275
- // src/cli/generateAgentArtifacts.ts
6276
- import path20 from "path";
6277
-
6278
- // src/ast/extractAgentMetadata.ts
6279
- import ts10 from "typescript";
6280
- init_resolveTypeNode();
6281
- function extractAgentMetadata(program, filePath, pathMeta) {
6282
- const sourceFile = program.getSourceFile(filePath);
6283
- if (!sourceFile) return null;
6284
- const configFound = findConfigExport(sourceFile);
6285
- if (!configFound) {
6286
- throw SchemaExtractionError.at(
6287
- sourceFile,
6288
- "config.systemPrompt",
6289
- "agent \u5FC5\u586B\u2014\u2014\u8BF7\u5728 config \u5757\u58F0\u660E\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF08\u5B57\u7B26\u4E32\u5B57\u9762\u91CF\u3001\u65E0\u63D2\u503C\u6A21\u677F\u5B57\u7B26\u4E32\u6216\u5176 + \u62FC\u63A5\uFF09",
6290
- sourceFile
6291
- );
6292
- }
6293
- if (!configFound.objectLiteral) {
6294
- throw SchemaExtractionError.at(
6295
- configFound.jsDocOwner,
6296
- "config",
6297
- "\u4EC5\u652F\u6301\u5BF9\u8C61\u5B57\u9762\u91CF\u3001\u7BAD\u5934\u51FD\u6570\u8FD4\u56DE\u5BF9\u8C61\u5B57\u9762\u91CF\u6216\u51FD\u6570\u8FD4\u56DE\u5BF9\u8C61\u5B57\u9762\u91CF",
6298
- sourceFile
6299
- );
6300
- }
6301
- const objectLiteral = configFound.objectLiteral;
6302
- const jsDoc = getJSDocFromNode(configFound.jsDocOwner);
6303
- const description = extractDescription(jsDoc);
6304
- const agentNameOverride = extractJSDocTagValue(jsDoc, "agent");
6305
- const { systemPrompt, tools, agents, model, maxTurns, inputDescription } = extractConfigFields(
6306
- objectLiteral,
6307
- sourceFile
6308
- );
6451
+ // src/task/idleTaskDriver.ts
6452
+ function createIdleTaskDriver() {
6309
6453
  return {
6310
- name: agentNameOverride ?? pathMeta.name,
6311
- description,
6312
- filePath: pathMeta.filePath,
6313
- hasRun: pathMeta.hasRun,
6314
- systemPrompt,
6315
- tools,
6316
- agents,
6317
- model,
6318
- maxTurns,
6319
- inputDescription
6454
+ enqueue() {
6455
+ return Promise.reject(
6456
+ new Error(
6457
+ "[faapi] New task(s) were registered without a queue driver. Set config.task.driver to 'pgboss' or 'bullmq' (install the matching @faapi/task-* package) and restart the server."
6458
+ )
6459
+ );
6460
+ },
6461
+ startWorker() {
6462
+ },
6463
+ async stop() {
6464
+ },
6465
+ async stopWorkers() {
6466
+ }
6320
6467
  };
6321
6468
  }
6322
- function findConfigExport(sourceFile) {
6323
- let result = null;
6324
- ts10.forEachChild(sourceFile, (node) => {
6325
- if (result) return;
6326
- if (ts10.isVariableStatement(node) && hasExportModifier(node)) {
6327
- for (const decl of node.declarationList.declarations) {
6328
- if (result) break;
6329
- const nameText = ts10.isIdentifier(decl.name) ? decl.name.text : "";
6330
- if (nameText !== "config" || !decl.initializer) continue;
6331
- if (ts10.isObjectLiteralExpression(decl.initializer)) {
6332
- result = { jsDocOwner: node, objectLiteral: decl.initializer };
6333
- } else if (ts10.isArrowFunction(decl.initializer)) {
6334
- result = { jsDocOwner: node, objectLiteral: getReturnObjectLiteral(decl.initializer) };
6335
- } else {
6336
- result = { jsDocOwner: node, objectLiteral: null };
6337
- }
6338
- }
6339
- }
6340
- if (ts10.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === "config") {
6341
- const returnObj = getReturnObjectLiteral(node);
6342
- result = { jsDocOwner: node, objectLiteral: returnObj };
6343
- }
6469
+
6470
+ // src/cli/loadPlugins.ts
6471
+ import path22 from "path";
6472
+ import fs19 from "fs";
6473
+ import { pathToFileURL as pathToFileURL3 } from "url";
6474
+
6475
+ // src/cli/compileConfig.ts
6476
+ import path21 from "path";
6477
+ import fs18 from "fs";
6478
+ async function compileProjectModules(entryPoints, rootDir, dist) {
6479
+ const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);
6480
+ const esbuild = await import("esbuild");
6481
+ const aliasPlugins = buildAliasPlugins(rootDir);
6482
+ const absDist = path21.resolve(rootDir, dist);
6483
+ await esbuild.build({
6484
+ entryPoints: [...entryPoints, ...outsideFiles],
6485
+ outdir: absDist,
6486
+ outbase: rootDir,
6487
+ bundle: false,
6488
+ platform: "node",
6489
+ format: "esm",
6490
+ sourcemap: true,
6491
+ packages: "external",
6492
+ plugins: aliasPlugins,
6493
+ logLevel: "silent"
6344
6494
  });
6345
- return result;
6346
- }
6347
- function getReturnObjectLiteral(fn) {
6348
- const body = fn.body;
6349
- if (!body) return null;
6350
- if (ts10.isObjectLiteralExpression(body)) {
6351
- return body;
6352
- }
6353
- if (ts10.isBlock(body)) {
6354
- for (const stmt of body.statements) {
6355
- if (ts10.isReturnStatement(stmt) && stmt.expression && ts10.isObjectLiteralExpression(stmt.expression)) {
6356
- return stmt.expression;
6357
- }
6358
- }
6495
+ if (insideFiles.length > 0) {
6496
+ const appOutbase = path21.resolve(rootDir, "src");
6497
+ await esbuild.build({
6498
+ entryPoints: insideFiles,
6499
+ outdir: absDist,
6500
+ outbase: appOutbase,
6501
+ bundle: false,
6502
+ platform: "node",
6503
+ format: "esm",
6504
+ sourcemap: true,
6505
+ packages: "external",
6506
+ plugins: aliasPlugins,
6507
+ logLevel: "silent"
6508
+ });
6359
6509
  }
6360
- return null;
6510
+ return { insideFiles, outsideFiles };
6361
6511
  }
6362
- function extractConfigFields(objLit, sourceFile) {
6363
- let systemPrompt;
6364
- let tools;
6365
- let agents;
6366
- let model;
6367
- let maxTurns;
6368
- let inputDescription;
6369
- for (const prop of objLit.properties) {
6370
- if (ts10.isSpreadAssignment(prop)) continue;
6512
+
6513
+ // src/cli/loadPlugins.ts
6514
+ async function loadPlugins(declarations, ctx, rootDir, dist) {
6515
+ const handlerWrappers = [];
6516
+ const upgradeWrappers = [];
6517
+ const failures = [];
6518
+ if (!declarations || declarations.length === 0) {
6519
+ return { handlerWrappers, upgradeWrappers, failures };
6520
+ }
6521
+ const fullCtx = {
6522
+ ...ctx,
6523
+ wrapHandler: (fn) => {
6524
+ handlerWrappers.push(fn);
6525
+ },
6526
+ wrapUpgradeHandler: (fn) => {
6527
+ upgradeWrappers.push(fn);
6528
+ }
6529
+ };
6530
+ const loaded = /* @__PURE__ */ new Set();
6531
+ for (const decl of declarations) {
6532
+ let specifier;
6533
+ let options;
6534
+ let enable;
6535
+ try {
6536
+ ({ specifier, options, enable } = resolveDeclaration(decl));
6537
+ } catch (err) {
6538
+ failures.push({
6539
+ specifier: JSON.stringify(decl),
6540
+ reason: err instanceof Error ? err.message : String(err)
6541
+ });
6542
+ continue;
6543
+ }
6544
+ if (enable === false) continue;
6545
+ if (loaded.has(specifier)) {
6546
+ console.warn(`! Plugin already loaded: ${specifier}, skipping`);
6547
+ continue;
6548
+ }
6549
+ loaded.add(specifier);
6550
+ try {
6551
+ const mod = await importPluginModule(specifier, rootDir ?? process.cwd(), dist);
6552
+ const plugin = mod.default ?? mod;
6553
+ if (typeof plugin.setup !== "function") {
6554
+ failures.push({ specifier, reason: "plugin has no setup function" });
6555
+ continue;
6556
+ }
6557
+ await plugin.setup({ ...fullCtx, options });
6558
+ console.log(`- Plugin loaded: ${plugin.name ?? specifier}`);
6559
+ } catch (err) {
6560
+ failures.push({
6561
+ specifier,
6562
+ reason: err instanceof Error ? err.message : String(err)
6563
+ });
6564
+ }
6565
+ }
6566
+ if (failures.length > 0) {
6567
+ console.error(
6568
+ `[faapi] ${failures.length} plugin(s) failed to load:
6569
+ ` + failures.map((f) => ` - ${f.specifier}: ${f.reason}`).join("\n")
6570
+ );
6571
+ }
6572
+ return { handlerWrappers, upgradeWrappers, failures };
6573
+ }
6574
+ var TS_EXTS = /\.(ts|tsx|mts|cts)$/;
6575
+ var JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
6576
+ function resolveLocalPluginSource(specifier, baseDir) {
6577
+ const base = path22.isAbsolute(specifier) ? specifier : path22.resolve(baseDir, specifier);
6578
+ if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs19.existsSync(base)) return base;
6579
+ for (const ext of [".ts", ".js"]) {
6580
+ if (fs19.existsSync(base + ext)) return base + ext;
6581
+ }
6582
+ for (const indexExt of ["/index.ts", "/index.js"]) {
6583
+ if (fs19.existsSync(base + indexExt)) return base + indexExt;
6584
+ }
6585
+ return null;
6586
+ }
6587
+ function mirrorProductPath(sourcePath, baseDir, distDir) {
6588
+ let rel = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
6589
+ if (rel.startsWith("src/")) rel = rel.slice(4);
6590
+ return path22.resolve(distDir, rel.replace(TS_EXTS, ".js"));
6591
+ }
6592
+ async function importPluginModule(specifier, baseDir, dist) {
6593
+ const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
6594
+ if (!isRelative && !path22.isAbsolute(specifier)) {
6595
+ return import(specifier);
6596
+ }
6597
+ const distDir = dist ? path22.resolve(baseDir, dist) : null;
6598
+ const sourcePath = resolveLocalPluginSource(specifier, baseDir);
6599
+ const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
6600
+ if (productPath !== null && fs19.existsSync(productPath) && (sourcePath === null || isProductFresh(sourcePath, productPath))) {
6601
+ return import(pathToFileURL3(productPath).href);
6602
+ }
6603
+ if (sourcePath && TS_EXTS.test(sourcePath)) {
6604
+ if (!isDevOnDemandEnabled() || !distDir) {
6605
+ throw new Error(
6606
+ `Local plugin "${specifier}" has no up-to-date build artifact (dist is stale or missing). Run "faapi build" first, or use "faapi dev" for on-demand compilation.`
6607
+ );
6608
+ }
6609
+ const relFromRoot = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
6610
+ if (relFromRoot.startsWith("src/")) {
6611
+ await ensureCompiled(sourcePath, baseDir, dist);
6612
+ } else {
6613
+ await compileProjectModules([sourcePath], baseDir, dist);
6614
+ }
6615
+ if (productPath && fs19.existsSync(productPath)) {
6616
+ return import(pathToFileURL3(productPath).href);
6617
+ }
6618
+ }
6619
+ if (sourcePath && JS_EXTS.test(sourcePath)) {
6620
+ return import(pathToFileURL3(sourcePath).href);
6621
+ }
6622
+ const candidates = [
6623
+ `${specifier}.ts`,
6624
+ `${specifier}/index.ts`,
6625
+ `${specifier}.js`,
6626
+ `${specifier}/index.js`
6627
+ ].map((c) => path22.isAbsolute(c) ? c : path22.join(baseDir, c));
6628
+ throw new Error(
6629
+ `Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
6630
+ ${candidates.join("\n ")}
6631
+ Create the plugin file (export default { name, setup(ctx) {...} }) or fix the path.`
6632
+ );
6633
+ }
6634
+ function resolveDeclaration(decl) {
6635
+ if (typeof decl === "string") {
6636
+ return { specifier: decl };
6637
+ }
6638
+ if (Array.isArray(decl)) {
6639
+ const [specifier, options] = decl;
6640
+ return { specifier, options };
6641
+ }
6642
+ if ("package" in decl) {
6643
+ return { specifier: decl.package, options: decl.options, enable: decl.enable };
6644
+ }
6645
+ if ("path" in decl) {
6646
+ return { specifier: decl.path, options: decl.options, enable: decl.enable };
6647
+ }
6648
+ throw new Error(`Invalid plugin declaration: ${JSON.stringify(decl)}`);
6649
+ }
6650
+
6651
+ // src/cli/manifestLoader.ts
6652
+ import fs20 from "fs";
6653
+ import path25 from "path";
6654
+
6655
+ // src/cli/generateAgentArtifacts.ts
6656
+ import path23 from "path";
6657
+
6658
+ // src/ast/extractAgentMetadata.ts
6659
+ import ts10 from "typescript";
6660
+ init_resolveTypeNode();
6661
+ function extractAgentMetadata(program, filePath, pathMeta) {
6662
+ const sourceFile = program.getSourceFile(filePath);
6663
+ if (!sourceFile) return null;
6664
+ const configFound = findConfigExport(sourceFile);
6665
+ if (!configFound) {
6666
+ throw SchemaExtractionError.at(
6667
+ sourceFile,
6668
+ "config.systemPrompt",
6669
+ "agent \u5FC5\u586B\u2014\u2014\u8BF7\u5728 config \u5757\u58F0\u660E\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF08\u5B57\u7B26\u4E32\u5B57\u9762\u91CF\u3001\u65E0\u63D2\u503C\u6A21\u677F\u5B57\u7B26\u4E32\u6216\u5176 + \u62FC\u63A5\uFF09",
6670
+ sourceFile
6671
+ );
6672
+ }
6673
+ if (!configFound.objectLiteral) {
6674
+ throw SchemaExtractionError.at(
6675
+ configFound.jsDocOwner,
6676
+ "config",
6677
+ "\u4EC5\u652F\u6301\u5BF9\u8C61\u5B57\u9762\u91CF\u3001\u7BAD\u5934\u51FD\u6570\u8FD4\u56DE\u5BF9\u8C61\u5B57\u9762\u91CF\u6216\u51FD\u6570\u8FD4\u56DE\u5BF9\u8C61\u5B57\u9762\u91CF",
6678
+ sourceFile
6679
+ );
6680
+ }
6681
+ const objectLiteral = configFound.objectLiteral;
6682
+ const jsDoc = getJSDocFromNode(configFound.jsDocOwner);
6683
+ const description = extractDescription(jsDoc);
6684
+ const agentNameOverride = extractJSDocTagValue(jsDoc, "agent");
6685
+ const { systemPrompt, tools, agents, model, maxTurns, inputDescription } = extractConfigFields(
6686
+ objectLiteral,
6687
+ sourceFile
6688
+ );
6689
+ return {
6690
+ name: agentNameOverride ?? pathMeta.name,
6691
+ description,
6692
+ filePath: pathMeta.filePath,
6693
+ hasRun: pathMeta.hasRun,
6694
+ systemPrompt,
6695
+ tools,
6696
+ agents,
6697
+ model,
6698
+ maxTurns,
6699
+ inputDescription
6700
+ };
6701
+ }
6702
+ function findConfigExport(sourceFile) {
6703
+ let result = null;
6704
+ ts10.forEachChild(sourceFile, (node) => {
6705
+ if (result) return;
6706
+ if (ts10.isVariableStatement(node) && hasExportModifier(node)) {
6707
+ for (const decl of node.declarationList.declarations) {
6708
+ if (result) break;
6709
+ const nameText = ts10.isIdentifier(decl.name) ? decl.name.text : "";
6710
+ if (nameText !== "config" || !decl.initializer) continue;
6711
+ if (ts10.isObjectLiteralExpression(decl.initializer)) {
6712
+ result = { jsDocOwner: node, objectLiteral: decl.initializer };
6713
+ } else if (ts10.isArrowFunction(decl.initializer)) {
6714
+ result = { jsDocOwner: node, objectLiteral: getReturnObjectLiteral(decl.initializer) };
6715
+ } else {
6716
+ result = { jsDocOwner: node, objectLiteral: null };
6717
+ }
6718
+ }
6719
+ }
6720
+ if (ts10.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === "config") {
6721
+ const returnObj = getReturnObjectLiteral(node);
6722
+ result = { jsDocOwner: node, objectLiteral: returnObj };
6723
+ }
6724
+ });
6725
+ return result;
6726
+ }
6727
+ function getReturnObjectLiteral(fn) {
6728
+ const body = fn.body;
6729
+ if (!body) return null;
6730
+ if (ts10.isObjectLiteralExpression(body)) {
6731
+ return body;
6732
+ }
6733
+ if (ts10.isBlock(body)) {
6734
+ for (const stmt of body.statements) {
6735
+ if (ts10.isReturnStatement(stmt) && stmt.expression && ts10.isObjectLiteralExpression(stmt.expression)) {
6736
+ return stmt.expression;
6737
+ }
6738
+ }
6739
+ }
6740
+ return null;
6741
+ }
6742
+ function extractConfigFields(objLit, sourceFile) {
6743
+ let systemPrompt;
6744
+ let tools;
6745
+ let agents;
6746
+ let model;
6747
+ let maxTurns;
6748
+ let inputDescription;
6749
+ for (const prop of objLit.properties) {
6750
+ if (ts10.isSpreadAssignment(prop)) continue;
6371
6751
  if (!ts10.isPropertyAssignment(prop)) {
6372
6752
  throw SchemaExtractionError.at(
6373
6753
  prop,
@@ -6553,9 +6933,9 @@ function validateAgentList(metadata) {
6553
6933
  }
6554
6934
  async function generateAgentArtifacts(agents, rootDir, dist) {
6555
6935
  const metadata = [];
6556
- const programByFile = createPrograms(agents.map((m) => path20.resolve(rootDir, m.filePath)));
6936
+ const programByFile = createPrograms(agents.map((m) => path23.resolve(rootDir, m.filePath)));
6557
6937
  for (const manifest of agents) {
6558
- const absPath = path20.resolve(rootDir, manifest.filePath);
6938
+ const absPath = path23.resolve(rootDir, manifest.filePath);
6559
6939
  const program = programByFile.get(absPath);
6560
6940
  const result = extractAgentMetadata(program, absPath, {
6561
6941
  name: manifest.name,
@@ -6565,394 +6945,151 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
6565
6945
  if (!result) {
6566
6946
  throw new Error(
6567
6947
  `agent "${manifest.name}" \u6E90\u6587\u4EF6\u4E0D\u5728 Program \u4E2D: ${manifest.filePath}\u2014\u2014\u65E0\u6CD5\u751F\u6210\u6E05\u5355`
6568
- );
6569
- }
6570
- metadata.push(result);
6571
- }
6572
- validateAgentList(metadata);
6573
- const serialized = serializeAgents(metadata, dist);
6574
- const agentsPath = path20.resolve(rootDir, dist, AGENTS_FILE);
6575
- await writeAgentsModule(serialized, agentsPath);
6576
- return metadata;
6577
- }
6578
-
6579
- // src/cli/generateTaskArtifacts.ts
6580
- import path21 from "path";
6581
- init_createProgram();
6582
- init_atomicWrite();
6583
- init_generateSchemaFiles();
6584
- init_extractHandlerTypes();
6585
- init_generateZodSchema();
6586
- var TASKS_FILE = "faapi-tasks.js";
6587
- function serializeTasks(tasks, dist = "dist") {
6588
- return tasks.map((t) => ({
6589
- name: t.name,
6590
- filePath: toProdFilePath(t.filePath, dist),
6591
- ...t.cron !== void 0 ? { cron: t.cron } : {},
6592
- ...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
6593
- ...t.retries !== void 0 ? { retries: t.retries } : {},
6594
- ...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {},
6595
- ...t.graceMs !== void 0 ? { graceMs: t.graceMs } : {}
6596
- }));
6597
- }
6598
- async function writeTasksModule(manifest, outputPath) {
6599
- const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
6600
- export const tasks = ${JSON.stringify(manifest, null, 2)};
6601
- `;
6602
- await atomicWriteFile(outputPath, content);
6603
- }
6604
- function hydrateTasks(manifest) {
6605
- return manifest.map((t) => ({
6606
- name: t.name,
6607
- filePath: t.filePath,
6608
- cron: t.cron ?? void 0,
6609
- concurrency: t.concurrency ?? void 0,
6610
- retries: t.retries ?? void 0,
6611
- timeoutMs: t.timeoutMs ?? void 0,
6612
- graceMs: t.graceMs ?? void 0
6613
- }));
6614
- }
6615
- function collectTaskSchemaSources(tasks, rootDir) {
6616
- const tasksByFile = /* @__PURE__ */ new Map();
6617
- for (const task of tasks) {
6618
- if (!task.inputTypeName) continue;
6619
- const absPath = path21.resolve(rootDir, task.filePath);
6620
- let list = tasksByFile.get(absPath);
6621
- if (!list) {
6622
- list = [];
6623
- tasksByFile.set(absPath, list);
6624
- }
6625
- list.push(task);
6626
- }
6627
- const programByFile = createPrograms([...tasksByFile.keys()]);
6628
- const resolversByFile = /* @__PURE__ */ new Map();
6629
- for (const filePath of tasksByFile.keys()) {
6630
- resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
6631
- }
6632
- const sources = [];
6633
- for (const [filePath, fileTasks] of tasksByFile) {
6634
- const program = programByFile.get(filePath);
6635
- for (const task of fileTasks) {
6636
- const typeInfo = extractTypeInfo(program, filePath, task.inputTypeName);
6637
- sources.push({
6638
- name: task.taskName,
6639
- filePath,
6640
- schemaName: task.inputTypeName,
6641
- typeInfo
6642
- });
6643
- }
6644
- }
6645
- return { sources, resolversByFile };
6646
- }
6647
- function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
6648
- const lines = ["import { z } from 'zod';"];
6649
- const schemaBlocks = [];
6650
- for (const source of sources) {
6651
- if (!source.typeInfo) continue;
6652
- const block = [`// task ${source.name} \u2192 ${source.schemaName}`];
6653
- const schemaCode = generateZodSchemaSource(
6654
- source.typeInfo,
6655
- resolveType,
6656
- source.schemaName,
6657
- false
6658
- ).replace(/^import \{ z \} from 'zod';\s*\n\s*\n/, "");
6659
- block.push(schemaCode);
6660
- block.push("");
6661
- schemaBlocks.push(block.join("\n"));
6662
- }
6663
- const allSchemaCode = schemaBlocks.join("\n");
6664
- if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
6665
- lines.push(
6666
- `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
6667
- );
6668
- }
6669
- lines.push("");
6670
- lines.push(...schemaBlocks);
6671
- return lines.join("\n").replace(/\n+$/, "\n");
6672
- }
6673
- async function generateTaskArtifacts(manifests, rootDir, dist) {
6674
- const metadata = [];
6675
- if (manifests.length > 0) {
6676
- const programByFile = createPrograms(manifests.map((m) => path21.resolve(rootDir, m.filePath)));
6677
- for (const manifest of manifests) {
6678
- const absPath = path21.resolve(rootDir, manifest.filePath);
6679
- const result = extractToolMetadata(programByFile.get(absPath), absPath, "run", {
6680
- name: manifest.name,
6681
- filePath: manifest.filePath
6682
- });
6683
- if (result) {
6684
- metadata.push({ ...result, taskName: manifest.name });
6685
- }
6686
- }
6687
- }
6688
- const serialized = serializeTasks(manifests, dist);
6689
- const tasksPath = path21.resolve(rootDir, dist, TASKS_FILE);
6690
- await writeTasksModule(serialized, tasksPath);
6691
- if (metadata.length === 0) {
6692
- return hydrateTasks(serialized);
6693
- }
6694
- const { sources, resolversByFile } = collectTaskSchemaSources(metadata, rootDir);
6695
- if (sources.length === 0) {
6696
- return hydrateTasks(serialized);
6697
- }
6698
- const sourcesByFile = /* @__PURE__ */ new Map();
6699
- for (const source of sources) {
6700
- let list = sourcesByFile.get(source.filePath);
6701
- if (!list) {
6702
- list = [];
6703
- sourcesByFile.set(source.filePath, list);
6704
- }
6705
- list.push(source);
6706
- }
6707
- const fileEntries = [];
6708
- for (const [filePath, fileSources] of sourcesByFile) {
6709
- const relFile = path21.relative(rootDir, filePath).replace(/\\/g, "/");
6710
- const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
6711
- const resolver = resolversByFile.get(filePath);
6712
- let relForDir = relFile;
6713
- if (relForDir.startsWith("src/")) {
6714
- relForDir = relForDir.slice(4);
6715
- }
6716
- const dirIdx = relForDir.lastIndexOf("/");
6717
- const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
6718
- const helpersImportPath = getHelpersImportPath(zodRelDir);
6719
- const source = generateTaskSchemaFileSource(
6720
- fileSources,
6721
- (name) => resolver?.resolve(name)?.runtimeType,
6722
- helpersImportPath
6723
- );
6724
- fileEntries.push({ outputPath, source });
6725
- }
6726
- const allSourceCode = fileEntries.map((e) => e.source).join("\n");
6727
- if (usesCoerceHelpers(allSourceCode)) {
6728
- const helpersPath = path21.resolve(rootDir, dist, HELPERS_FILENAME);
6729
- const { existsSync: existsSync3 } = await import("fs");
6730
- if (!existsSync3(helpersPath)) {
6731
- await atomicWriteFile(helpersPath, generateHelpersFileSource());
6732
- }
6733
- }
6734
- await Promise.all(
6735
- fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source))
6736
- );
6737
- return hydrateTasks(serialized);
6738
- }
6739
-
6740
- // src/task/idleTaskDriver.ts
6741
- function createIdleTaskDriver() {
6742
- return {
6743
- enqueue() {
6744
- return Promise.reject(
6745
- new Error(
6746
- "[faapi] New task(s) were registered without a queue driver. Set config.task.driver to 'pgboss' or 'bullmq' (install the matching @faapi/task-* package) and restart the server."
6747
- )
6748
- );
6749
- },
6750
- startWorker() {
6751
- },
6752
- async stop() {
6753
- },
6754
- async stopWorkers() {
6755
- }
6756
- };
6757
- }
6758
-
6759
- // src/cli/loadPlugins.ts
6760
- import path23 from "path";
6761
- import fs18 from "fs";
6762
- import { pathToFileURL as pathToFileURL3 } from "url";
6763
-
6764
- // src/cli/compileConfig.ts
6765
- import path22 from "path";
6766
- import fs17 from "fs";
6767
- async function compileProjectModules(entryPoints, rootDir, dist) {
6768
- const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);
6769
- const esbuild = await import("esbuild");
6770
- const aliasPlugins = buildAliasPlugins(rootDir);
6771
- const absDist = path22.resolve(rootDir, dist);
6772
- await esbuild.build({
6773
- entryPoints: [...entryPoints, ...outsideFiles],
6774
- outdir: absDist,
6775
- outbase: rootDir,
6776
- bundle: false,
6777
- platform: "node",
6778
- format: "esm",
6779
- sourcemap: true,
6780
- packages: "external",
6781
- plugins: aliasPlugins,
6782
- logLevel: "silent"
6783
- });
6784
- if (insideFiles.length > 0) {
6785
- const appOutbase = path22.resolve(rootDir, "src");
6786
- await esbuild.build({
6787
- entryPoints: insideFiles,
6788
- outdir: absDist,
6789
- outbase: appOutbase,
6790
- bundle: false,
6791
- platform: "node",
6792
- format: "esm",
6793
- sourcemap: true,
6794
- packages: "external",
6795
- plugins: aliasPlugins,
6796
- logLevel: "silent"
6797
- });
6798
- }
6799
- return { insideFiles, outsideFiles };
6800
- }
6801
-
6802
- // src/cli/loadPlugins.ts
6803
- async function loadPlugins(declarations, ctx, rootDir, dist) {
6804
- const handlerWrappers = [];
6805
- const upgradeWrappers = [];
6806
- const failures = [];
6807
- if (!declarations || declarations.length === 0) {
6808
- return { handlerWrappers, upgradeWrappers, failures };
6809
- }
6810
- const fullCtx = {
6811
- ...ctx,
6812
- wrapHandler: (fn) => {
6813
- handlerWrappers.push(fn);
6814
- },
6815
- wrapUpgradeHandler: (fn) => {
6816
- upgradeWrappers.push(fn);
6817
- }
6818
- };
6819
- const loaded = /* @__PURE__ */ new Set();
6820
- for (const decl of declarations) {
6821
- let specifier;
6822
- let options;
6823
- let enable;
6824
- try {
6825
- ({ specifier, options, enable } = resolveDeclaration(decl));
6826
- } catch (err) {
6827
- failures.push({
6828
- specifier: JSON.stringify(decl),
6829
- reason: err instanceof Error ? err.message : String(err)
6830
- });
6831
- continue;
6832
- }
6833
- if (enable === false) continue;
6834
- if (loaded.has(specifier)) {
6835
- console.warn(`! Plugin already loaded: ${specifier}, skipping`);
6836
- continue;
6837
- }
6838
- loaded.add(specifier);
6839
- try {
6840
- const mod = await importPluginModule(specifier, rootDir ?? process.cwd(), dist);
6841
- const plugin = mod.default ?? mod;
6842
- if (typeof plugin.setup !== "function") {
6843
- failures.push({ specifier, reason: "plugin has no setup function" });
6844
- continue;
6845
- }
6846
- await plugin.setup({ ...fullCtx, options });
6847
- console.log(`- Plugin loaded: ${plugin.name ?? specifier}`);
6848
- } catch (err) {
6849
- failures.push({
6850
- specifier,
6851
- reason: err instanceof Error ? err.message : String(err)
6852
- });
6853
- }
6854
- }
6855
- if (failures.length > 0) {
6856
- console.error(
6857
- `[faapi] ${failures.length} plugin(s) failed to load:
6858
- ` + failures.map((f) => ` - ${f.specifier}: ${f.reason}`).join("\n")
6859
- );
6860
- }
6861
- return { handlerWrappers, upgradeWrappers, failures };
6862
- }
6863
- var TS_EXTS = /\.(ts|tsx|mts|cts)$/;
6864
- var JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
6865
- function resolveLocalPluginSource(specifier, baseDir) {
6866
- const base = path23.isAbsolute(specifier) ? specifier : path23.resolve(baseDir, specifier);
6867
- if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs18.existsSync(base)) return base;
6868
- for (const ext of [".ts", ".js"]) {
6869
- if (fs18.existsSync(base + ext)) return base + ext;
6870
- }
6871
- for (const indexExt of ["/index.ts", "/index.js"]) {
6872
- if (fs18.existsSync(base + indexExt)) return base + indexExt;
6948
+ );
6949
+ }
6950
+ metadata.push(result);
6873
6951
  }
6874
- return null;
6952
+ validateAgentList(metadata);
6953
+ const serialized = serializeAgents(metadata, dist);
6954
+ const agentsPath = path23.resolve(rootDir, dist, AGENTS_FILE);
6955
+ await writeAgentsModule(serialized, agentsPath);
6956
+ return metadata;
6875
6957
  }
6876
- function mirrorProductPath(sourcePath, baseDir, distDir) {
6877
- let rel = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
6878
- if (rel.startsWith("src/")) rel = rel.slice(4);
6879
- return path23.resolve(distDir, rel.replace(TS_EXTS, ".js"));
6958
+
6959
+ // src/cli/generateTaskArtifacts.ts
6960
+ init_generateZodArtifacts();
6961
+ import path24 from "path";
6962
+ init_createProgram();
6963
+ init_atomicWrite();
6964
+ init_extractHandlerTypes();
6965
+ init_generateZodSchema();
6966
+ var TASKS_FILE = "faapi-tasks.js";
6967
+ function serializeTasks(tasks, dist = "dist") {
6968
+ return tasks.map((t) => ({
6969
+ name: t.name,
6970
+ filePath: toProdFilePath(t.filePath, dist),
6971
+ ...t.cron !== void 0 ? { cron: t.cron } : {},
6972
+ ...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
6973
+ ...t.retries !== void 0 ? { retries: t.retries } : {},
6974
+ ...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {},
6975
+ ...t.graceMs !== void 0 ? { graceMs: t.graceMs } : {}
6976
+ }));
6880
6977
  }
6881
- function isSourceNewer(sourcePath, productPath) {
6882
- try {
6883
- return fs18.statSync(sourcePath).mtimeMs > fs18.statSync(productPath).mtimeMs;
6884
- } catch {
6885
- return true;
6886
- }
6978
+ async function writeTasksModule(manifest, outputPath) {
6979
+ const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
6980
+ export const tasks = ${JSON.stringify(manifest, null, 2)};
6981
+ `;
6982
+ await atomicWriteFile(outputPath, content);
6887
6983
  }
6888
- async function importPluginModule(specifier, baseDir, dist) {
6889
- const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
6890
- if (!isRelative && !path23.isAbsolute(specifier)) {
6891
- return import(specifier);
6984
+ function hydrateTasks(manifest) {
6985
+ return manifest.map((t) => ({
6986
+ name: t.name,
6987
+ filePath: t.filePath,
6988
+ cron: t.cron ?? void 0,
6989
+ concurrency: t.concurrency ?? void 0,
6990
+ retries: t.retries ?? void 0,
6991
+ timeoutMs: t.timeoutMs ?? void 0,
6992
+ graceMs: t.graceMs ?? void 0
6993
+ }));
6994
+ }
6995
+ function collectTaskSchemaSources(tasks, rootDir) {
6996
+ const tasksByFile = /* @__PURE__ */ new Map();
6997
+ for (const task of tasks) {
6998
+ if (!task.inputTypeName) continue;
6999
+ const absPath = path24.resolve(rootDir, task.filePath);
7000
+ let list = tasksByFile.get(absPath);
7001
+ if (!list) {
7002
+ list = [];
7003
+ tasksByFile.set(absPath, list);
7004
+ }
7005
+ list.push(task);
6892
7006
  }
6893
- const distDir = dist ? path23.resolve(baseDir, dist) : null;
6894
- const sourcePath = resolveLocalPluginSource(specifier, baseDir);
6895
- const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
6896
- if (productPath !== null && fs18.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
6897
- return import(pathToFileURL3(productPath).href);
7007
+ const programByFile = createPrograms([...tasksByFile.keys()]);
7008
+ const resolversByFile = /* @__PURE__ */ new Map();
7009
+ for (const filePath of tasksByFile.keys()) {
7010
+ resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
6898
7011
  }
6899
- if (sourcePath && TS_EXTS.test(sourcePath)) {
6900
- if (!isDevOnDemandEnabled() || !distDir) {
6901
- throw new Error(
6902
- `Local plugin "${specifier}" has no up-to-date build artifact (dist is stale or missing). Run "faapi build" first, or use "faapi dev" for on-demand compilation.`
6903
- );
6904
- }
6905
- const relFromRoot = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
6906
- if (relFromRoot.startsWith("src/")) {
6907
- await ensureCompiled(sourcePath, baseDir, dist);
6908
- } else {
6909
- await compileProjectModules([sourcePath], baseDir, dist);
6910
- }
6911
- if (productPath && fs18.existsSync(productPath)) {
6912
- return import(pathToFileURL3(productPath).href);
7012
+ const sources = [];
7013
+ for (const [filePath, fileTasks] of tasksByFile) {
7014
+ const program = programByFile.get(filePath);
7015
+ for (const task of fileTasks) {
7016
+ const typeInfo = extractTypeInfo(program, filePath, task.inputTypeName);
7017
+ sources.push({
7018
+ name: task.taskName,
7019
+ filePath,
7020
+ schemaName: task.inputTypeName,
7021
+ typeInfo
7022
+ });
6913
7023
  }
6914
7024
  }
6915
- if (sourcePath && JS_EXTS.test(sourcePath)) {
6916
- return import(pathToFileURL3(sourcePath).href);
6917
- }
6918
- const candidates = [
6919
- `${specifier}.ts`,
6920
- `${specifier}/index.ts`,
6921
- `${specifier}.js`,
6922
- `${specifier}/index.js`
6923
- ].map((c) => path23.isAbsolute(c) ? c : path23.join(baseDir, c));
6924
- throw new Error(
6925
- `Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
6926
- ${candidates.join("\n ")}
6927
- Create the plugin file (export default { name, setup(ctx) {...} }) or fix the path.`
6928
- );
7025
+ return { sources, resolversByFile };
6929
7026
  }
6930
- function resolveDeclaration(decl) {
6931
- if (typeof decl === "string") {
6932
- return { specifier: decl };
7027
+ function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
7028
+ const lines = ["import { z } from 'zod';"];
7029
+ const schemaBlocks = [];
7030
+ for (const source of sources) {
7031
+ if (!source.typeInfo) continue;
7032
+ const block = [`// task ${source.name} \u2192 ${source.schemaName}`];
7033
+ const schemaCode = generateZodSchemaSource(
7034
+ source.typeInfo,
7035
+ resolveType,
7036
+ source.schemaName,
7037
+ false
7038
+ ).replace(/^import \{ z \} from 'zod';\s*\n\s*\n/, "");
7039
+ block.push(schemaCode);
7040
+ block.push("");
7041
+ schemaBlocks.push(block.join("\n"));
6933
7042
  }
6934
- if (Array.isArray(decl)) {
6935
- const [specifier, options] = decl;
6936
- return { specifier, options };
7043
+ const allSchemaCode = schemaBlocks.join("\n");
7044
+ if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
7045
+ lines.push(
7046
+ `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
7047
+ );
6937
7048
  }
6938
- if ("package" in decl) {
6939
- return { specifier: decl.package, options: decl.options, enable: decl.enable };
7049
+ lines.push("");
7050
+ lines.push(...schemaBlocks);
7051
+ return lines.join("\n").replace(/\n+$/, "\n");
7052
+ }
7053
+ async function generateTaskArtifacts(manifests, rootDir, dist) {
7054
+ const metadata = [];
7055
+ if (manifests.length > 0) {
7056
+ const programByFile = createPrograms(manifests.map((m) => path24.resolve(rootDir, m.filePath)));
7057
+ for (const manifest of manifests) {
7058
+ const absPath = path24.resolve(rootDir, manifest.filePath);
7059
+ const result = extractToolMetadata(programByFile.get(absPath), absPath, "run", {
7060
+ name: manifest.name,
7061
+ filePath: manifest.filePath
7062
+ });
7063
+ if (result) {
7064
+ metadata.push({ ...result, taskName: manifest.name });
7065
+ }
7066
+ }
6940
7067
  }
6941
- if ("path" in decl) {
6942
- return { specifier: decl.path, options: decl.options, enable: decl.enable };
7068
+ const serialized = serializeTasks(manifests, dist);
7069
+ const tasksPath = path24.resolve(rootDir, dist, TASKS_FILE);
7070
+ await writeTasksModule(serialized, tasksPath);
7071
+ if (metadata.length === 0) {
7072
+ return hydrateTasks(serialized);
6943
7073
  }
6944
- throw new Error(`Invalid plugin declaration: ${JSON.stringify(decl)}`);
7074
+ const { sources, resolversByFile } = collectTaskSchemaSources(metadata, rootDir);
7075
+ if (sources.length === 0) {
7076
+ return hydrateTasks(serialized);
7077
+ }
7078
+ await generateZodArtifacts(sources, {
7079
+ generateFileSource: generateTaskSchemaFileSource,
7080
+ resolversByFile,
7081
+ rootDir,
7082
+ dist
7083
+ });
7084
+ return hydrateTasks(serialized);
6945
7085
  }
6946
7086
 
6947
- // src/cli/createAppCore.ts
6948
- var DEFAULT_DIST = "dist";
6949
- var DEFAULT_PORT = 3e3;
6950
- var ROUTES_FILE = "faapi-routes.js";
7087
+ // src/cli/manifestLoader.ts
6951
7088
  var TOOLS_FILE2 = "faapi-tools.js";
6952
7089
  var AGENTS_FILE2 = "faapi-agents.js";
6953
7090
  async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
6954
- const toolsPath = path24.resolve(rootDir, dist, TOOLS_FILE2);
6955
- if (!fs19.existsSync(toolsPath)) {
7091
+ const toolsPath = path25.resolve(rootDir, dist, TOOLS_FILE2);
7092
+ if (!fs20.existsSync(toolsPath)) {
6956
7093
  return [];
6957
7094
  }
6958
7095
  const serialized = await importWithCacheBust(toolsPath);
@@ -6961,8 +7098,8 @@ async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries
6961
7098
  return hydrated;
6962
7099
  }
6963
7100
  async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
6964
- const agentsPath = path24.resolve(rootDir, dist, AGENTS_FILE2);
6965
- if (!fs19.existsSync(agentsPath)) {
7101
+ const agentsPath = path25.resolve(rootDir, dist, AGENTS_FILE2);
7102
+ if (!fs20.existsSync(agentsPath)) {
6966
7103
  return [];
6967
7104
  }
6968
7105
  const serialized = await importWithCacheBust(agentsPath);
@@ -6977,8 +7114,8 @@ function getTaskDriverOptions(config) {
6977
7114
  return void 0;
6978
7115
  }
6979
7116
  async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries) {
6980
- const tasksPath = path24.resolve(rootDir, dist, TASKS_FILE);
6981
- if (!fs19.existsSync(tasksPath)) {
7117
+ const tasksPath = path25.resolve(rootDir, dist, TASKS_FILE);
7118
+ if (!fs20.existsSync(tasksPath)) {
6982
7119
  return [];
6983
7120
  }
6984
7121
  const serialized = await importWithCacheBust(tasksPath);
@@ -6986,6 +7123,8 @@ async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries
6986
7123
  registries.task.hydrate(hydrated);
6987
7124
  return hydrated;
6988
7125
  }
7126
+
7127
+ // src/cli/appSingleton.ts
6989
7128
  var APP_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("faapi.app.instance");
6990
7129
  function getCurrentApp() {
6991
7130
  return globalThis[APP_INSTANCE_KEY] ?? null;
@@ -7023,6 +7162,94 @@ function getApp() {
7023
7162
  }
7024
7163
  return app;
7025
7164
  }
7165
+
7166
+ // src/cli/injectMock.ts
7167
+ import { PassThrough, Readable as Readable3 } from "stream";
7168
+ function performInject(server, injectOpts) {
7169
+ const {
7170
+ method = "GET",
7171
+ path: reqPath = "/",
7172
+ headers: reqHeaders = {},
7173
+ query,
7174
+ body
7175
+ } = injectOpts ?? {};
7176
+ const queryStr = query ? "?" + new URLSearchParams(Object.entries(query).map(([k, v]) => [k, String(v)])).toString() : "";
7177
+ return new Promise((resolve, reject) => {
7178
+ const chunks = [];
7179
+ const mockRes = new PassThrough();
7180
+ mockRes.statusCode = 200;
7181
+ mockRes._headers = {};
7182
+ mockRes.setHeader = function(name, value) {
7183
+ this._headers[name.toLowerCase()] = value;
7184
+ };
7185
+ mockRes.appendHeader = function(name, value) {
7186
+ const key = name.toLowerCase();
7187
+ const existing = this._headers[key];
7188
+ this._headers[key] = existing ? `${existing}, ${value}` : value;
7189
+ };
7190
+ mockRes.writeHead = function(status, headers) {
7191
+ this.statusCode = status;
7192
+ if (headers) {
7193
+ Object.assign(this._headers, headers);
7194
+ }
7195
+ };
7196
+ mockRes.on("data", (chunk) => chunks.push(chunk));
7197
+ mockRes.on("error", reject);
7198
+ mockRes.on("finish", () => {
7199
+ const bodyBuf = Buffer.concat(chunks);
7200
+ let parsed;
7201
+ try {
7202
+ parsed = JSON.parse(bodyBuf.toString());
7203
+ } catch {
7204
+ parsed = bodyBuf.toString();
7205
+ }
7206
+ resolve({
7207
+ status: mockRes.statusCode,
7208
+ headers: new Headers(mockRes._headers),
7209
+ body: parsed
7210
+ });
7211
+ });
7212
+ const listeners = server.listeners("request");
7213
+ const handler = listeners[listeners.length - 1];
7214
+ if (typeof handler !== "function") {
7215
+ reject(new Error("No request handler found"));
7216
+ return;
7217
+ }
7218
+ let payload;
7219
+ let defaultContentType;
7220
+ if (body !== void 0) {
7221
+ if (typeof body === "string") {
7222
+ payload = Buffer.from(body, "utf-8");
7223
+ defaultContentType = "text/plain";
7224
+ } else if (body instanceof Uint8Array) {
7225
+ payload = Buffer.from(body);
7226
+ defaultContentType = "application/octet-stream";
7227
+ } else {
7228
+ payload = Buffer.from(JSON.stringify(body));
7229
+ defaultContentType = "application/json";
7230
+ }
7231
+ }
7232
+ const mockReq = Readable3.from(payload !== void 0 ? [payload] : []);
7233
+ mockReq.method = method;
7234
+ mockReq.url = `${reqPath}${queryStr}`;
7235
+ const hasCallerContentType = "content-type" in reqHeaders || "Content-Type" in reqHeaders;
7236
+ mockReq.headers = {
7237
+ ...reqHeaders,
7238
+ host: "localhost",
7239
+ ...payload !== void 0 && !hasCallerContentType ? { "content-type": defaultContentType } : {}
7240
+ };
7241
+ mockReq.socket = { remoteAddress: "127.0.0.1" };
7242
+ handler(
7243
+ mockReq,
7244
+ mockRes
7245
+ );
7246
+ });
7247
+ }
7248
+
7249
+ // src/cli/createAppCore.ts
7250
+ var DEFAULT_DIST = "dist";
7251
+ var DEFAULT_PORT = 3e3;
7252
+ var ROUTES_FILE = "faapi-routes.js";
7026
7253
  var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
7027
7254
  "cors",
7028
7255
  "lifecycle",
@@ -7043,11 +7270,45 @@ var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
7043
7270
  function isFaapiConfigKey(key) {
7044
7271
  return FAAPI_CONFIG_KEYS.has(key);
7045
7272
  }
7273
+ function logStartupSummary(routes, wsRoutes, tools, agents, taskMetas, p) {
7274
+ console.log("faapi server started");
7275
+ console.log(`- Local: http://localhost:${p}`);
7276
+ console.log("- Loaded routes:");
7277
+ for (const route of routes) {
7278
+ console.log(` ${route.method.padEnd(6)}${route.urlPath} ${route.filePath}`);
7279
+ }
7280
+ if (wsRoutes.length > 0) {
7281
+ console.log("- WebSocket routes:");
7282
+ for (const route of wsRoutes) {
7283
+ console.log(` WS ${route.urlPath} ${route.filePath}`);
7284
+ }
7285
+ }
7286
+ if (tools.length > 0) {
7287
+ console.log(`- Loaded ${tools.length} tool(s):`);
7288
+ for (const tool of tools) {
7289
+ console.log(` ${tool.name} ${tool.filePath}`);
7290
+ }
7291
+ }
7292
+ if (agents.length > 0) {
7293
+ console.log(`- Loaded ${agents.length} agent(s):`);
7294
+ for (const agent of agents) {
7295
+ const exports = agent.hasRun ? "run" : "-";
7296
+ console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
7297
+ }
7298
+ }
7299
+ if (taskMetas.length > 0) {
7300
+ console.log(`- Loaded ${taskMetas.length} task(s):`);
7301
+ for (const taskMeta of taskMetas) {
7302
+ const schedule = taskMeta.cron ? ` cron=${taskMeta.cron}` : "";
7303
+ console.log(` ${taskMeta.name}${schedule} ${taskMeta.filePath}`);
7304
+ }
7305
+ }
7306
+ }
7046
7307
  async function createAppBase(options) {
7047
7308
  const rootDir = options?.rootDir ?? process.cwd();
7048
7309
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
7049
- const routesPath = path24.resolve(rootDir, dist, ROUTES_FILE);
7050
- if (!fs19.existsSync(routesPath)) {
7310
+ const routesPath = path26.resolve(rootDir, dist, ROUTES_FILE);
7311
+ if (!fs21.existsSync(routesPath)) {
7051
7312
  throw new Error(
7052
7313
  `[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
7053
7314
  );
@@ -7058,15 +7319,7 @@ async function createAppBase(options) {
7058
7319
  const hydrated = await hydrateRoutes(serialized);
7059
7320
  let sorted = sortRoutes(hydrated.routes);
7060
7321
  let wsRoutes = hydrated.wsRoutes;
7061
- const conflicts = detectRouteConflicts(sorted);
7062
- if (conflicts.length > 0) {
7063
- for (const conflict of conflicts) {
7064
- console.warn(`! \u8DEF\u7531\u51B2\u7A81: ${conflict.method} ${conflict.urlPath}`);
7065
- for (const file of conflict.files) {
7066
- console.warn(` - ${file}`);
7067
- }
7068
- }
7069
- }
7322
+ reportRouteConflicts(sorted);
7070
7323
  const registries = createAppRegistries();
7071
7324
  const tools = await loadAndHydrateTools(rootDir, dist, registries);
7072
7325
  const agents = await loadAndHydrateAgents(rootDir, dist, registries);
@@ -7172,38 +7425,7 @@ async function createAppBase(options) {
7172
7425
  server.off("error", onListenError);
7173
7426
  const address = server.address();
7174
7427
  const p = typeof address === "object" && address !== null ? address.port : actualPort;
7175
- console.log("faapi server started");
7176
- console.log(`- Local: http://localhost:${p}`);
7177
- console.log("- Loaded routes:");
7178
- for (const route of sorted) {
7179
- console.log(` ${route.method.padEnd(6)}${route.urlPath} ${route.filePath}`);
7180
- }
7181
- if (wsRoutes.length > 0) {
7182
- console.log("- WebSocket routes:");
7183
- for (const route of wsRoutes) {
7184
- console.log(` WS ${route.urlPath} ${route.filePath}`);
7185
- }
7186
- }
7187
- if (tools.length > 0) {
7188
- console.log(`- Loaded ${tools.length} tool(s):`);
7189
- for (const tool of tools) {
7190
- console.log(` ${tool.name} ${tool.filePath}`);
7191
- }
7192
- }
7193
- if (agents.length > 0) {
7194
- console.log(`- Loaded ${agents.length} agent(s):`);
7195
- for (const agent of agents) {
7196
- const exports = agent.hasRun ? "run" : "-";
7197
- console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
7198
- }
7199
- }
7200
- if (taskMetas.length > 0) {
7201
- console.log(`- Loaded ${taskMetas.length} task(s):`);
7202
- for (const taskMeta of taskMetas) {
7203
- const schedule = taskMeta.cron ? ` cron=${taskMeta.cron}` : "";
7204
- console.log(` ${taskMeta.name}${schedule} ${taskMeta.filePath}`);
7205
- }
7206
- }
7428
+ logStartupSummary(sorted, wsRoutes, tools, agents, taskMetas, p);
7207
7429
  registerDefaultShutdownHandlers();
7208
7430
  if (config?.lifecycle?.onReady) {
7209
7431
  await config.lifecycle.onReady({
@@ -7220,85 +7442,8 @@ async function createAppBase(options) {
7220
7442
  });
7221
7443
  });
7222
7444
  },
7223
- async inject(injectOpts) {
7224
- const {
7225
- method = "GET",
7226
- path: reqPath = "/",
7227
- headers: reqHeaders = {},
7228
- query,
7229
- body
7230
- } = injectOpts ?? {};
7231
- const queryStr = query ? "?" + new URLSearchParams(Object.entries(query).map(([k, v]) => [k, String(v)])).toString() : "";
7232
- return new Promise((resolve, reject) => {
7233
- const chunks = [];
7234
- const mockRes = new PassThrough();
7235
- mockRes.statusCode = 200;
7236
- mockRes._headers = {};
7237
- mockRes.setHeader = function(name, value) {
7238
- this._headers[name.toLowerCase()] = value;
7239
- };
7240
- mockRes.appendHeader = function(name, value) {
7241
- const key = name.toLowerCase();
7242
- const existing = this._headers[key];
7243
- this._headers[key] = existing ? `${existing}, ${value}` : value;
7244
- };
7245
- mockRes.writeHead = function(status, headers) {
7246
- this.statusCode = status;
7247
- if (headers) {
7248
- Object.assign(this._headers, headers);
7249
- }
7250
- };
7251
- mockRes.on("data", (chunk) => chunks.push(chunk));
7252
- mockRes.on("error", reject);
7253
- mockRes.on("finish", () => {
7254
- const body2 = Buffer.concat(chunks);
7255
- let parsed;
7256
- try {
7257
- parsed = JSON.parse(body2.toString());
7258
- } catch {
7259
- parsed = body2.toString();
7260
- }
7261
- resolve({
7262
- status: mockRes.statusCode,
7263
- headers: new Headers(mockRes._headers),
7264
- body: parsed
7265
- });
7266
- });
7267
- const listeners = server.listeners("request");
7268
- const handler = listeners[listeners.length - 1];
7269
- if (typeof handler !== "function") {
7270
- reject(new Error("No request handler found"));
7271
- return;
7272
- }
7273
- let payload;
7274
- let defaultContentType;
7275
- if (body !== void 0) {
7276
- if (typeof body === "string") {
7277
- payload = Buffer.from(body, "utf-8");
7278
- defaultContentType = "text/plain";
7279
- } else if (body instanceof Uint8Array) {
7280
- payload = Buffer.from(body);
7281
- defaultContentType = "application/octet-stream";
7282
- } else {
7283
- payload = Buffer.from(JSON.stringify(body));
7284
- defaultContentType = "application/json";
7285
- }
7286
- }
7287
- const mockReq = Readable3.from(payload !== void 0 ? [payload] : []);
7288
- mockReq.method = method;
7289
- mockReq.url = `${reqPath}${queryStr}`;
7290
- const hasCallerContentType = "content-type" in reqHeaders || "Content-Type" in reqHeaders;
7291
- mockReq.headers = {
7292
- ...reqHeaders,
7293
- host: "localhost",
7294
- ...payload !== void 0 && !hasCallerContentType ? { "content-type": defaultContentType } : {}
7295
- };
7296
- mockReq.socket = { remoteAddress: "127.0.0.1" };
7297
- handler(
7298
- mockReq,
7299
- mockRes
7300
- );
7301
- });
7445
+ inject(injectOpts) {
7446
+ return performInject(server, injectOpts);
7302
7447
  },
7303
7448
  async close() {
7304
7449
  if (closed) return;
@@ -7374,21 +7519,21 @@ async function createAppBase(options) {
7374
7519
  }
7375
7520
 
7376
7521
  // src/cli/createDevApp.ts
7377
- import path28 from "path";
7522
+ import path30 from "path";
7378
7523
 
7379
7524
  // src/router/scanRoutes.ts
7380
7525
  import fg3 from "fast-glob";
7381
- import path25 from "path";
7382
- import fs20 from "fs";
7526
+ import path27 from "path";
7527
+ import fs22 from "fs";
7383
7528
 
7384
7529
  // src/router/constants.ts
7385
7530
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
7386
7531
  var HTTP_METHOD_SET = new Set(HTTP_METHODS);
7387
7532
 
7388
7533
  // src/utils/normalizePath.ts
7389
- function normalizePath(path29) {
7390
- if (!path29) return "";
7391
- let result = path29.replace(/\\/g, "/");
7534
+ function normalizePath(path31) {
7535
+ if (!path31) return "";
7536
+ let result = path31.replace(/\\/g, "/");
7392
7537
  result = result.replace(/\/+/g, "/");
7393
7538
  result = result.replace(/\/+$/, "");
7394
7539
  if (result && !result.startsWith("/")) {
@@ -7449,34 +7594,34 @@ function extractExportsFromSource(source) {
7449
7594
  return names;
7450
7595
  }
7451
7596
  function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
7452
- const routeDir = path25.dirname(routeFilePath);
7453
- const resolvedRoot = path25.resolve(rootDir);
7597
+ const routeDir = path27.dirname(routeFilePath);
7598
+ const resolvedRoot = path27.resolve(rootDir);
7454
7599
  const paths = [];
7455
- let currentDir = path25.resolve(rootDir, routeDir);
7600
+ let currentDir = path27.resolve(rootDir, routeDir);
7456
7601
  while (true) {
7457
7602
  if (dist) {
7458
- const mwTsPath = path25.join(currentDir, "middlewares.ts");
7459
- const mwJsPath = path25.join(currentDir, "middlewares.js");
7460
- const absTsPath = path25.resolve(rootDir, mwTsPath);
7461
- const absJsPath = path25.resolve(rootDir, mwJsPath);
7462
- const absMwPath = fs20.existsSync(absTsPath) ? absTsPath : fs20.existsSync(absJsPath) ? absJsPath : null;
7603
+ const mwTsPath = path27.join(currentDir, "middlewares.ts");
7604
+ const mwJsPath = path27.join(currentDir, "middlewares.js");
7605
+ const absTsPath = path27.resolve(rootDir, mwTsPath);
7606
+ const absJsPath = path27.resolve(rootDir, mwJsPath);
7607
+ const absMwPath = fs22.existsSync(absTsPath) ? absTsPath : fs22.existsSync(absJsPath) ? absJsPath : null;
7463
7608
  if (absMwPath) {
7464
- const relMwPath = path25.relative(rootDir, absMwPath);
7465
- const prodAbsPath = path25.resolve(rootDir, toProdFilePath(relMwPath, dist));
7609
+ const relMwPath = path27.relative(rootDir, absMwPath);
7610
+ const prodAbsPath = path27.resolve(rootDir, toProdFilePath(relMwPath, dist));
7466
7611
  paths.push(prodAbsPath);
7467
7612
  }
7468
7613
  } else {
7469
7614
  for (const ext of [".ts", ".js"]) {
7470
- const mwPath = path25.join(currentDir, `middlewares${ext}`);
7471
- const absMwPath = path25.resolve(rootDir, mwPath);
7472
- if (fs20.existsSync(absMwPath)) {
7615
+ const mwPath = path27.join(currentDir, `middlewares${ext}`);
7616
+ const absMwPath = path27.resolve(rootDir, mwPath);
7617
+ if (fs22.existsSync(absMwPath)) {
7473
7618
  paths.push(absMwPath);
7474
7619
  break;
7475
7620
  }
7476
7621
  }
7477
7622
  }
7478
7623
  if (currentDir === resolvedRoot) break;
7479
- const parentDir = path25.dirname(currentDir);
7624
+ const parentDir = path27.dirname(currentDir);
7480
7625
  if (parentDir === currentDir) break;
7481
7626
  currentDir = parentDir;
7482
7627
  }
@@ -7495,7 +7640,7 @@ async function scanRoutes(rootDir, patterns, dist) {
7495
7640
  const normalizedFile = file.replace(/\\/g, "/");
7496
7641
  const fileName = normalizedFile.split("/").pop();
7497
7642
  if (fileName === "handler.ts" || fileName === "handler.js") {
7498
- const absPath = path25.resolve(rootDir, normalizedFile);
7643
+ const absPath = path27.resolve(rootDir, normalizedFile);
7499
7644
  const urlPath = filePathToUrlPath(normalizedFile);
7500
7645
  const paramNames = extractParamNames(urlPath);
7501
7646
  const isDynamic = paramNames.length > 0;
@@ -7508,7 +7653,7 @@ async function scanRoutes(rootDir, patterns, dist) {
7508
7653
  const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
7509
7654
  middlewareBundle = await loadMergedMiddlewares(mwPaths);
7510
7655
  }
7511
- const source = await fs20.promises.readFile(absPath, "utf8").catch(() => "");
7656
+ const source = await fs22.promises.readFile(absPath, "utf8").catch(() => "");
7512
7657
  const exportNames = extractExportsFromSource(source);
7513
7658
  const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
7514
7659
  for (const method of methods) {
@@ -7544,8 +7689,8 @@ async function scanRoutes(rootDir, patterns, dist) {
7544
7689
 
7545
7690
  // src/tools/scanTools.ts
7546
7691
  import fg4 from "fast-glob";
7547
- import path26 from "path";
7548
- import fs21 from "fs";
7692
+ import path28 from "path";
7693
+ import fs23 from "fs";
7549
7694
  var TOOL_PATTERNS = ["src/tools/**/*.ts"];
7550
7695
  var TOOL_EXPORT_RE = new RegExp(
7551
7696
  String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
@@ -7595,8 +7740,8 @@ async function scanTools(rootDir, patterns) {
7595
7740
  if (fileName !== "handler.ts" && fileName !== "handler.js") {
7596
7741
  continue;
7597
7742
  }
7598
- const absPath = path26.resolve(rootDir, normalizedFile);
7599
- const source = await fs21.promises.readFile(absPath, "utf8").catch(() => "");
7743
+ const absPath = path28.resolve(rootDir, normalizedFile);
7744
+ const source = await fs23.promises.readFile(absPath, "utf8").catch(() => "");
7600
7745
  const exportNames = extractToolExportsFromSource(source);
7601
7746
  const namespace = filePathToToolNamespace(normalizedFile);
7602
7747
  for (const fnName of exportNames) {
@@ -7620,8 +7765,8 @@ async function scanTools(rootDir, patterns) {
7620
7765
 
7621
7766
  // src/agents/scanAgents.ts
7622
7767
  import fg5 from "fast-glob";
7623
- import path27 from "path";
7624
- import fs22 from "fs";
7768
+ import path29 from "path";
7769
+ import fs24 from "fs";
7625
7770
  var DEFAULT_AGENT_PATTERNS = ["src/agents/**/handler.ts"];
7626
7771
  var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
7627
7772
  function extractAgentNameFromPath(filePath) {
@@ -7653,8 +7798,8 @@ async function scanAgents(rootDir, patterns) {
7653
7798
  if (fileName !== "handler.ts" && fileName !== "handler.js") {
7654
7799
  continue;
7655
7800
  }
7656
- const absPath = path27.resolve(rootDir, normalizedFile);
7657
- const source = await fs22.promises.readFile(absPath, "utf8").catch(() => "");
7801
+ const absPath = path29.resolve(rootDir, normalizedFile);
7802
+ const source = await fs24.promises.readFile(absPath, "utf8").catch(() => "");
7658
7803
  const { hasRun } = detectAgentExports(source);
7659
7804
  const name = extractAgentNameFromPath(normalizedFile);
7660
7805
  const prevFile = seen.get(name);
@@ -7675,13 +7820,14 @@ async function scanAgents(rootDir, patterns) {
7675
7820
 
7676
7821
  // src/cli/createDevApp.ts
7677
7822
  init_createProgram();
7823
+ var suppressProgramInvalidation = false;
7678
7824
  async function createDevApp(options) {
7679
7825
  const { app, ctx } = await createAppBase(options);
7680
7826
  const devApp = app;
7681
7827
  devApp.reloadRoutes = async () => {
7682
7828
  setLoadTimestamp(Date.now());
7683
7829
  invalidateMiddlewareCache();
7684
- invalidateProgramCache();
7830
+ if (!suppressProgramInvalidation) invalidateProgramCache();
7685
7831
  invalidateSchemaCache();
7686
7832
  clearCompiledFiles();
7687
7833
  const reScanned = await scanRoutes(ctx.rootDir, ctx.patterns, ctx.dist);
@@ -7705,7 +7851,7 @@ async function createDevApp(options) {
7705
7851
  };
7706
7852
  devApp.reloadTools = async () => {
7707
7853
  setLoadTimestamp(Date.now());
7708
- invalidateProgramCache();
7854
+ if (!suppressProgramInvalidation) invalidateProgramCache();
7709
7855
  const tools = await scanTools(ctx.rootDir, TOOL_PATTERNS);
7710
7856
  await generateToolArtifacts(tools, ctx.rootDir, ctx.dist, {
7711
7857
  skipSchema: isDevOnDemandEnabled()
@@ -7714,17 +7860,17 @@ async function createDevApp(options) {
7714
7860
  };
7715
7861
  devApp.reloadAgents = async () => {
7716
7862
  setLoadTimestamp(Date.now());
7717
- invalidateProgramCache();
7863
+ if (!suppressProgramInvalidation) invalidateProgramCache();
7718
7864
  const agents = await scanAgents(ctx.rootDir, DEFAULT_AGENT_PATTERNS);
7719
7865
  await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);
7720
7866
  await loadAndHydrateAgents(ctx.rootDir, ctx.dist, ctx.registries);
7721
7867
  };
7722
7868
  devApp.reloadTasks = async () => {
7723
7869
  setLoadTimestamp(Date.now());
7724
- invalidateProgramCache();
7870
+ if (!suppressProgramInvalidation) invalidateProgramCache();
7725
7871
  const tasks = await scanTasks(ctx.rootDir, TASK_PATTERNS);
7726
7872
  for (const task of tasks) {
7727
- await ensureCompiled(path28.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
7873
+ await ensureCompiled(path30.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
7728
7874
  }
7729
7875
  await generateTaskArtifacts(tasks, ctx.rootDir, ctx.dist);
7730
7876
  ctx.taskQueue.invalidateModules();
@@ -7732,6 +7878,19 @@ async function createDevApp(options) {
7732
7878
  await ctx.taskQueue.reload();
7733
7879
  await loadAndHydrateTasks(ctx.rootDir, ctx.dist, ctx.registries);
7734
7880
  };
7881
+ devApp.reloadAll = async () => {
7882
+ invalidateProgramCache();
7883
+ const prev = suppressProgramInvalidation;
7884
+ suppressProgramInvalidation = true;
7885
+ try {
7886
+ await devApp.reloadRoutes();
7887
+ await devApp.reloadTools();
7888
+ await devApp.reloadAgents();
7889
+ await devApp.reloadTasks();
7890
+ } finally {
7891
+ suppressProgramInvalidation = prev;
7892
+ }
7893
+ };
7735
7894
  return devApp;
7736
7895
  }
7737
7896