@faapi/faapi 6.21.1 → 6.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2046,14 +2046,14 @@ var ValidationError = class extends FaapiError {
2046
2046
  issues;
2047
2047
  };
2048
2048
  var RouteNotFoundError = class extends FaapiError {
2049
- constructor(path31) {
2050
- super("ROUTE_NOT_FOUND", `Route not found: ${path31}`, 404);
2049
+ constructor(path32) {
2050
+ super("ROUTE_NOT_FOUND", `Route not found: ${path32}`, 404);
2051
2051
  this.name = "RouteNotFoundError";
2052
2052
  }
2053
2053
  };
2054
2054
  var MethodNotAllowedError = class extends FaapiError {
2055
- constructor(method, path31, allowedMethods) {
2056
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path31}`, 405);
2055
+ constructor(method, path32, allowedMethods) {
2056
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path32}`, 405);
2057
2057
  this.allowedMethods = allowedMethods;
2058
2058
  this.name = "MethodNotAllowedError";
2059
2059
  }
@@ -3442,13 +3442,14 @@ function buildAliasPlugins(rootDir) {
3442
3442
  }
3443
3443
 
3444
3444
  // src/cli/compileSourceFiles.ts
3445
+ var SOURCE_IGNORE = ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts", `${APP_DIR}/resources/**`];
3445
3446
  async function compileSourceFiles(options) {
3446
3447
  const { rootDir, dist, files, logLevel = "silent", production, atomicWrite } = options;
3447
3448
  const entryPoints = files ?? await fg2([`${APP_DIR}/**/*.ts`], {
3448
3449
  cwd: rootDir,
3449
3450
  onlyFiles: true,
3450
3451
  absolute: true,
3451
- ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
3452
+ ignore: SOURCE_IGNORE
3452
3453
  });
3453
3454
  if (entryPoints.length === 0) {
3454
3455
  return { compiledFiles: [] };
@@ -4353,8 +4354,8 @@ function loadEnv(rootDir) {
4353
4354
  }
4354
4355
 
4355
4356
  // src/cli/createAppCore.ts
4356
- import fs21 from "fs";
4357
- import path26 from "path";
4357
+ import fs22 from "fs";
4358
+ import path27 from "path";
4358
4359
 
4359
4360
  // src/router/sortRoutes.ts
4360
4361
  function sortRoutes(routes) {
@@ -4418,7 +4419,7 @@ import {
4418
4419
  import { createSecureServer as createHttp2SecureServer } from "http2";
4419
4420
  import { readFileSync } from "fs";
4420
4421
  import { Readable as Readable2 } from "stream";
4421
- import path19 from "path";
4422
+ import path20 from "path";
4422
4423
 
4423
4424
  // src/router/matchRoute.ts
4424
4425
  var httpIndexCache = /* @__PURE__ */ new WeakMap();
@@ -4460,25 +4461,25 @@ function getWsIndex(routes) {
4460
4461
  wsIndexCache.set(routes, index);
4461
4462
  return index;
4462
4463
  }
4463
- function matchRoute(routes, method, path31) {
4464
+ function matchRoute(routes, method, path32) {
4464
4465
  const index = getHttpIndex(routes);
4465
4466
  const upper = method.toUpperCase();
4466
- const hit = matchByMethod(index, upper, path31);
4467
+ const hit = matchByMethod(index, upper, path32);
4467
4468
  if (hit) return hit;
4468
4469
  if (upper === "HEAD") {
4469
- return matchByMethod(index, "GET", path31);
4470
+ return matchByMethod(index, "GET", path32);
4470
4471
  }
4471
4472
  return null;
4472
4473
  }
4473
- function matchByMethod(index, method, path31) {
4474
- const staticHit = index.static.get(`${method}|${path31}`);
4474
+ function matchByMethod(index, method, path32) {
4475
+ const staticHit = index.static.get(`${method}|${path32}`);
4475
4476
  if (staticHit) {
4476
4477
  return { route: staticHit, params: {} };
4477
4478
  }
4478
4479
  if (index.dynamics.length === 0) {
4479
4480
  return null;
4480
4481
  }
4481
- const pathSegments = path31.split("/").filter(Boolean);
4482
+ const pathSegments = path32.split("/").filter(Boolean);
4482
4483
  for (const entry of index.dynamics) {
4483
4484
  const route = entry.route;
4484
4485
  if (route.method !== method) {
@@ -4491,31 +4492,31 @@ function matchByMethod(index, method, path31) {
4491
4492
  }
4492
4493
  return null;
4493
4494
  }
4494
- function matchWsRoute(wsRoutes, path31) {
4495
+ function matchWsRoute(wsRoutes, path32) {
4495
4496
  const index = getWsIndex(wsRoutes);
4496
- const staticHit = index.static.get(path31);
4497
+ const staticHit = index.static.get(path32);
4497
4498
  if (staticHit) {
4498
4499
  return { route: staticHit, params: {} };
4499
4500
  }
4500
4501
  for (const route of index.dynamics) {
4501
- const params = matchDynamicPath(route.urlPath, path31, route.paramNames, route.isCatchAll);
4502
+ const params = matchDynamicPath(route.urlPath, path32, route.paramNames, route.isCatchAll);
4502
4503
  if (params !== null) {
4503
4504
  return { route, params };
4504
4505
  }
4505
4506
  }
4506
4507
  return null;
4507
4508
  }
4508
- function findAllowedMethods(routes, path31) {
4509
+ function findAllowedMethods(routes, path32) {
4509
4510
  const index = getHttpIndex(routes);
4510
4511
  const methods = /* @__PURE__ */ new Set();
4511
- const staticMethods = index.methodsByStaticPath.get(path31);
4512
+ const staticMethods = index.methodsByStaticPath.get(path32);
4512
4513
  if (staticMethods) {
4513
4514
  for (const method of staticMethods) {
4514
4515
  methods.add(method);
4515
4516
  }
4516
4517
  }
4517
4518
  if (index.dynamics.length > 0) {
4518
- const pathSegments = path31.split("/").filter(Boolean);
4519
+ const pathSegments = path32.split("/").filter(Boolean);
4519
4520
  for (const entry of index.dynamics) {
4520
4521
  const params = matchSegments(
4521
4522
  entry.segments,
@@ -4533,10 +4534,10 @@ function findAllowedMethods(routes, path31) {
4533
4534
  }
4534
4535
  return Array.from(methods);
4535
4536
  }
4536
- function matchDynamicPath(pattern, path31, paramNames, isCatchAll) {
4537
+ function matchDynamicPath(pattern, path32, paramNames, isCatchAll) {
4537
4538
  return matchSegments(
4538
4539
  pattern.split("/").filter(Boolean),
4539
- path31.split("/").filter(Boolean),
4540
+ path32.split("/").filter(Boolean),
4540
4541
  paramNames,
4541
4542
  isCatchAll
4542
4543
  );
@@ -4782,13 +4783,28 @@ function encodeSseEvent(event) {
4782
4783
  return out;
4783
4784
  }
4784
4785
  function createSseWriter(options = {}) {
4785
- const { onFirstWrite } = options;
4786
+ const { onFirstWrite, keepOpen = false } = options;
4786
4787
  const encoder = new TextEncoder();
4787
4788
  let controller = null;
4788
4789
  let closed = false;
4789
4790
  let aborted = false;
4790
4791
  let firstWriteTriggered = false;
4791
4792
  let drainWaiters = [];
4793
+ let closeCallbacks = [];
4794
+ let closeCallbacksFired = false;
4795
+ const safeRunCallback = (callback) => {
4796
+ try {
4797
+ callback();
4798
+ } catch {
4799
+ }
4800
+ };
4801
+ const runCloseCallbacks = () => {
4802
+ if (closeCallbacksFired) return;
4803
+ closeCallbacksFired = true;
4804
+ const callbacks = closeCallbacks;
4805
+ closeCallbacks = [];
4806
+ for (const callback of callbacks) safeRunCallback(callback);
4807
+ };
4792
4808
  const resolveDrainWaiters = () => {
4793
4809
  const waiters = drainWaiters;
4794
4810
  drainWaiters = [];
@@ -4812,6 +4828,7 @@ function createSseWriter(options = {}) {
4812
4828
  aborted = true;
4813
4829
  closed = true;
4814
4830
  controller = null;
4831
+ runCloseCallbacks();
4815
4832
  resolveDrainWaiters();
4816
4833
  }
4817
4834
  },
@@ -4854,6 +4871,7 @@ function createSseWriter(options = {}) {
4854
4871
  close() {
4855
4872
  if (closed) return;
4856
4873
  closed = true;
4874
+ runCloseCallbacks();
4857
4875
  resolveDrainWaiters();
4858
4876
  if (controller) {
4859
4877
  try {
@@ -4863,6 +4881,13 @@ function createSseWriter(options = {}) {
4863
4881
  controller = null;
4864
4882
  }
4865
4883
  },
4884
+ onClose(callback) {
4885
+ if (closeCallbacksFired) {
4886
+ safeRunCallback(callback);
4887
+ return;
4888
+ }
4889
+ closeCallbacks.push(callback);
4890
+ },
4866
4891
  get closed() {
4867
4892
  return closed;
4868
4893
  },
@@ -4879,6 +4904,9 @@ function createSseWriter(options = {}) {
4879
4904
  get aborted() {
4880
4905
  return aborted;
4881
4906
  },
4907
+ get keepOpen() {
4908
+ return keepOpen;
4909
+ },
4882
4910
  get response() {
4883
4911
  return response;
4884
4912
  }
@@ -5012,10 +5040,18 @@ function formatSetCookie(name, value, options) {
5012
5040
  if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;
5013
5041
  return cookie;
5014
5042
  }
5015
- function createContext(request, params, config = {}, ip = "", registries) {
5016
- return createContextFromUrl(request, new URL(request.url), params, config, ip, registries);
5043
+ function createContext(request, params, config = {}, ip = "", registries, resourcesDir) {
5044
+ return createContextFromUrl(
5045
+ request,
5046
+ new URL(request.url),
5047
+ params,
5048
+ config,
5049
+ ip,
5050
+ registries,
5051
+ resourcesDir
5052
+ );
5017
5053
  }
5018
- function createContextFromUrl(request, url, params, config = {}, ip = "", registries) {
5054
+ function createContextFromUrl(request, url, params, config = {}, ip = "", registries, resourcesDir) {
5019
5055
  const meta = { headers: {}, setCookies: [] };
5020
5056
  const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
5021
5057
  const cookiesObj = {};
@@ -5043,6 +5079,9 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
5043
5079
  ua: request.headers.get("user-agent") ?? "",
5044
5080
  cookies: cookiesObj,
5045
5081
  config,
5082
+ // 运行时资源根目录(HTTP/WS 链路由 createServer/handleWsUpgrade 传入;
5083
+ // testing 直调未传时为 undefined,见 contextTypes.resourcesDir 注释)
5084
+ resourcesDir,
5046
5085
  meta,
5047
5086
  setStatus(status) {
5048
5087
  meta.status = status;
@@ -5088,10 +5127,15 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
5088
5127
  * 接入底层连接——流式期间字节即产即达(见 createServer.md「SSE 提前接管」)。
5089
5128
  * 无钩子场景(app.inject / 测试直调 invokeHandler)回落为 handler 返回后交出。
5090
5129
  *
5130
+ * `{ keepOpen: true }` 声明流生命周期独立于 handler 返回:handler 返回后框架
5131
+ * 不自动 close(长连接订阅模式,清理推送源用 writer.onClose);handler 抛错
5132
+ * 的兜底 close 不受影响。详见 runtime/sse.md「长连接订阅模式」。
5133
+ *
5091
5134
  * 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。
5092
5135
  */
5093
- sse() {
5136
+ sse(options) {
5094
5137
  const writer = createSseWriter({
5138
+ keepOpen: options?.keepOpen,
5095
5139
  onFirstWrite: (response) => {
5096
5140
  ctx.__earlyRespond?.(response);
5097
5141
  }
@@ -5491,7 +5535,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
5491
5535
  const pickSseAndAutoClose = () => {
5492
5536
  const sseWriter = ctx.__sseWriter;
5493
5537
  if (!sseWriter) return null;
5494
- if (!sseWriter.closed && !sseWriter.aborted) {
5538
+ if (!sseWriter.keepOpen && !sseWriter.closed && !sseWriter.aborted) {
5495
5539
  sseWriter.close();
5496
5540
  }
5497
5541
  return mergeMeta(sseWriter.response, meta);
@@ -5632,9 +5676,9 @@ async function validateInput(schemaPath, method, inputType, input) {
5632
5676
  function mapZodIssues(error) {
5633
5677
  return error.issues.map((issue) => {
5634
5678
  const code = mapZodCode(issue);
5635
- const path31 = issue.path.map(String).join(".") || "";
5679
+ const path32 = issue.path.map(String).join(".") || "";
5636
5680
  return {
5637
- path: path31,
5681
+ path: path32,
5638
5682
  code,
5639
5683
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
5640
5684
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -5855,9 +5899,17 @@ function etag(options = {}) {
5855
5899
  }
5856
5900
 
5857
5901
  // src/server/handleWsUpgrade.ts
5858
- import fs16 from "fs";
5902
+ import fs17 from "fs";
5859
5903
  import { WebSocketServer, WebSocket } from "ws";
5904
+ import path19 from "path";
5905
+
5906
+ // src/cli/copyResources.ts
5860
5907
  import path18 from "path";
5908
+ import fs16 from "fs";
5909
+ var RESOURCES_OUTPUT_DIR = "resources";
5910
+ function resolveResourcesDir(rootDir, dist) {
5911
+ return path18.resolve(rootDir, dist, RESOURCES_OUTPUT_DIR);
5912
+ }
5861
5913
 
5862
5914
  // src/server/serverUtils.ts
5863
5915
  function nodeHttpToWebHeaders(req) {
@@ -5997,7 +6049,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
5997
6049
  const dist = getDevDist();
5998
6050
  if (dist) {
5999
6051
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
6000
- if (sourcePath && fs16.existsSync(sourcePath)) {
6052
+ if (sourcePath && fs17.existsSync(sourcePath)) {
6001
6053
  await ensureCompiled(sourcePath, rootDir, dist);
6002
6054
  }
6003
6055
  }
@@ -6095,6 +6147,7 @@ function attachWebSocket(options) {
6095
6147
  trustedProxy = false,
6096
6148
  registries
6097
6149
  } = options;
6150
+ const resourcesDir = resolveResourcesDir(rootDir, dist);
6098
6151
  const wss = new WebSocketServer({ noServer: true });
6099
6152
  server.on("upgrade", async (req, socket, head) => {
6100
6153
  try {
@@ -6126,12 +6179,19 @@ function attachWebSocket(options) {
6126
6179
  const host = req.headers.host ?? "localhost";
6127
6180
  const fullUrl = new URL(`http://${host}${req.url ?? "/"}`);
6128
6181
  const request = new Request(fullUrl.toString(), { method: "GET", headers });
6129
- const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);
6182
+ const ctx = createContext(
6183
+ request,
6184
+ params,
6185
+ config,
6186
+ getClientIp(req, trustedProxy),
6187
+ registries,
6188
+ resourcesDir
6189
+ );
6130
6190
  const meta = ctx.meta;
6131
6191
  let upgraded = false;
6132
6192
  const finalHandler = async () => {
6133
6193
  const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
6134
- if (fs16.existsSync(schemaPath)) {
6194
+ if (fs17.existsSync(schemaPath)) {
6135
6195
  if (isDevOnDemandEnabled()) {
6136
6196
  const devDist = getDevDist();
6137
6197
  if (devDist) {
@@ -6170,7 +6230,7 @@ function attachWebSocket(options) {
6170
6230
  }
6171
6231
  let handlers;
6172
6232
  try {
6173
- const absoluteFilePath = path18.resolve(rootDir, route.filePath);
6233
+ const absoluteFilePath = path19.resolve(rootDir, route.filePath);
6174
6234
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
6175
6235
  } catch (err) {
6176
6236
  const reason = err instanceof Error ? err.message : String(err);
@@ -6330,6 +6390,7 @@ function createServer(options) {
6330
6390
  trustedProxy = false
6331
6391
  } = options;
6332
6392
  const routesRef = { current: routes, wsCurrent: wsRoutes ?? [] };
6393
+ const resourcesDir = resolveResourcesDir(rootDir, dist);
6333
6394
  const configMiddlewares = [];
6334
6395
  if (compressionOption) {
6335
6396
  const compOpts = typeof compressionOption === "object" ? compressionOption : {};
@@ -6376,7 +6437,8 @@ function createServer(options) {
6376
6437
  bodyLimit,
6377
6438
  trustedProxy,
6378
6439
  registries,
6379
- routesRef.wsCurrent
6440
+ routesRef.wsCurrent,
6441
+ resourcesDir
6380
6442
  ).catch((err) => {
6381
6443
  console.error("[faapi] Request pipeline failed after error response attempt:", err);
6382
6444
  try {
@@ -6402,7 +6464,7 @@ function createServer(options) {
6402
6464
  });
6403
6465
  return { server, routesRef };
6404
6466
  }
6405
- function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched) {
6467
+ function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched, resourcesDir) {
6406
6468
  const method = request.method.toUpperCase();
6407
6469
  const urlPath = url.pathname;
6408
6470
  const ctx = createContextFromUrl(
@@ -6411,7 +6473,8 @@ function prepareRequest(req, request, url, config, trustedProxy, registries, pre
6411
6473
  preMatched?.params ?? {},
6412
6474
  config,
6413
6475
  getClientIp(req, trustedProxy),
6414
- registries
6476
+ registries,
6477
+ resourcesDir
6415
6478
  );
6416
6479
  const meta = ctx.meta;
6417
6480
  return { request, url, ctx, meta, method, urlPath };
@@ -6430,7 +6493,7 @@ function getRoutePaths(route, rootDir, dist) {
6430
6493
  let cached = routePathCache.get(route);
6431
6494
  if (!cached) {
6432
6495
  cached = {
6433
- absFilePath: path19.resolve(rootDir, route.filePath),
6496
+ absFilePath: path20.resolve(rootDir, route.filePath),
6434
6497
  schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
6435
6498
  };
6436
6499
  routePathCache.set(route, cached);
@@ -6553,7 +6616,7 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
6553
6616
  }
6554
6617
  }
6555
6618
  }
6556
- async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes) {
6619
+ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes, resourcesDir) {
6557
6620
  let meta = { headers: {}, setCookies: [] };
6558
6621
  let ctx;
6559
6622
  let sseEarlySent = false;
@@ -6573,7 +6636,8 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
6573
6636
  config,
6574
6637
  trustedProxy,
6575
6638
  registries,
6576
- preMatched
6639
+ preMatched,
6640
+ resourcesDir
6577
6641
  );
6578
6642
  ctx = prepared.ctx;
6579
6643
  meta = prepared.meta;
@@ -6649,8 +6713,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
6649
6713
  }
6650
6714
 
6651
6715
  // src/cli/generateRoutes.ts
6652
- import fs17 from "fs";
6653
- import path20 from "path";
6716
+ import fs18 from "fs";
6717
+ import path21 from "path";
6654
6718
  async function hydrateRoutes(manifest) {
6655
6719
  const hydrateRoute = (serialized) => ({
6656
6720
  method: serialized.method,
@@ -6694,18 +6758,18 @@ function createIdleTaskDriver() {
6694
6758
  }
6695
6759
 
6696
6760
  // src/cli/loadPlugins.ts
6697
- import path22 from "path";
6698
- import fs19 from "fs";
6761
+ import path23 from "path";
6762
+ import fs20 from "fs";
6699
6763
  import { pathToFileURL as pathToFileURL3 } from "url";
6700
6764
 
6701
6765
  // src/cli/compileConfig.ts
6702
- import path21 from "path";
6703
- import fs18 from "fs";
6766
+ import path22 from "path";
6767
+ import fs19 from "fs";
6704
6768
  async function compileProjectModules(entryPoints, rootDir, dist) {
6705
6769
  const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);
6706
6770
  const esbuild = await import("esbuild");
6707
6771
  const aliasPlugins = buildAliasPlugins(rootDir);
6708
- const absDist = path21.resolve(rootDir, dist);
6772
+ const absDist = path22.resolve(rootDir, dist);
6709
6773
  await esbuild.build({
6710
6774
  entryPoints: [...entryPoints, ...outsideFiles],
6711
6775
  outdir: absDist,
@@ -6719,7 +6783,7 @@ async function compileProjectModules(entryPoints, rootDir, dist) {
6719
6783
  logLevel: "silent"
6720
6784
  });
6721
6785
  if (insideFiles.length > 0) {
6722
- const appOutbase = path21.resolve(rootDir, "src");
6786
+ const appOutbase = path22.resolve(rootDir, "src");
6723
6787
  await esbuild.build({
6724
6788
  entryPoints: insideFiles,
6725
6789
  outdir: absDist,
@@ -6800,30 +6864,30 @@ async function loadPlugins(declarations, ctx, rootDir, dist) {
6800
6864
  var TS_EXTS = /\.(ts|tsx|mts|cts)$/;
6801
6865
  var JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
6802
6866
  function resolveLocalPluginSource(specifier, baseDir) {
6803
- const base = path22.isAbsolute(specifier) ? specifier : path22.resolve(baseDir, specifier);
6804
- if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs19.existsSync(base)) return base;
6867
+ const base = path23.isAbsolute(specifier) ? specifier : path23.resolve(baseDir, specifier);
6868
+ if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs20.existsSync(base)) return base;
6805
6869
  for (const ext of [".ts", ".js"]) {
6806
- if (fs19.existsSync(base + ext)) return base + ext;
6870
+ if (fs20.existsSync(base + ext)) return base + ext;
6807
6871
  }
6808
6872
  for (const indexExt of ["/index.ts", "/index.js"]) {
6809
- if (fs19.existsSync(base + indexExt)) return base + indexExt;
6873
+ if (fs20.existsSync(base + indexExt)) return base + indexExt;
6810
6874
  }
6811
6875
  return null;
6812
6876
  }
6813
6877
  function mirrorProductPath(sourcePath, baseDir, distDir) {
6814
- let rel = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
6878
+ let rel = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
6815
6879
  if (rel.startsWith("src/")) rel = rel.slice(4);
6816
- return path22.resolve(distDir, rel.replace(TS_EXTS, ".js"));
6880
+ return path23.resolve(distDir, rel.replace(TS_EXTS, ".js"));
6817
6881
  }
6818
6882
  async function importPluginModule(specifier, baseDir, dist) {
6819
6883
  const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
6820
- if (!isRelative && !path22.isAbsolute(specifier)) {
6884
+ if (!isRelative && !path23.isAbsolute(specifier)) {
6821
6885
  return import(specifier);
6822
6886
  }
6823
- const distDir = dist ? path22.resolve(baseDir, dist) : null;
6887
+ const distDir = dist ? path23.resolve(baseDir, dist) : null;
6824
6888
  const sourcePath = resolveLocalPluginSource(specifier, baseDir);
6825
6889
  const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
6826
- if (productPath !== null && fs19.existsSync(productPath) && (sourcePath === null || isProductFresh(sourcePath, productPath))) {
6890
+ if (productPath !== null && fs20.existsSync(productPath) && (sourcePath === null || isProductFresh(sourcePath, productPath))) {
6827
6891
  return import(pathToFileURL3(productPath).href);
6828
6892
  }
6829
6893
  if (sourcePath && TS_EXTS.test(sourcePath)) {
@@ -6832,13 +6896,13 @@ async function importPluginModule(specifier, baseDir, dist) {
6832
6896
  `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.`
6833
6897
  );
6834
6898
  }
6835
- const relFromRoot = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
6899
+ const relFromRoot = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
6836
6900
  if (relFromRoot.startsWith("src/")) {
6837
6901
  await ensureCompiled(sourcePath, baseDir, dist);
6838
6902
  } else {
6839
6903
  await compileProjectModules([sourcePath], baseDir, dist);
6840
6904
  }
6841
- if (productPath && fs19.existsSync(productPath)) {
6905
+ if (productPath && fs20.existsSync(productPath)) {
6842
6906
  return import(pathToFileURL3(productPath).href);
6843
6907
  }
6844
6908
  }
@@ -6850,7 +6914,7 @@ async function importPluginModule(specifier, baseDir, dist) {
6850
6914
  `${specifier}/index.ts`,
6851
6915
  `${specifier}.js`,
6852
6916
  `${specifier}/index.js`
6853
- ].map((c) => path22.isAbsolute(c) ? c : path22.join(baseDir, c));
6917
+ ].map((c) => path23.isAbsolute(c) ? c : path23.join(baseDir, c));
6854
6918
  throw new Error(
6855
6919
  `Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
6856
6920
  ${candidates.join("\n ")}
@@ -6875,11 +6939,11 @@ function resolveDeclaration(decl) {
6875
6939
  }
6876
6940
 
6877
6941
  // src/cli/manifestLoader.ts
6878
- import fs20 from "fs";
6879
- import path25 from "path";
6942
+ import fs21 from "fs";
6943
+ import path26 from "path";
6880
6944
 
6881
6945
  // src/cli/generateAgentArtifacts.ts
6882
- import path23 from "path";
6946
+ import path24 from "path";
6883
6947
 
6884
6948
  // src/ast/extractAgentMetadata.ts
6885
6949
  import ts11 from "typescript";
@@ -7159,9 +7223,9 @@ function validateAgentList(metadata) {
7159
7223
  }
7160
7224
  async function generateAgentArtifacts(agents, rootDir, dist) {
7161
7225
  const metadata = [];
7162
- const programByFile = createPrograms(agents.map((m) => path23.resolve(rootDir, m.filePath)));
7226
+ const programByFile = createPrograms(agents.map((m) => path24.resolve(rootDir, m.filePath)));
7163
7227
  for (const manifest of agents) {
7164
- const absPath = path23.resolve(rootDir, manifest.filePath);
7228
+ const absPath = path24.resolve(rootDir, manifest.filePath);
7165
7229
  const program = programByFile.get(absPath);
7166
7230
  const result = extractAgentMetadata(program, absPath, {
7167
7231
  name: manifest.name,
@@ -7177,14 +7241,14 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
7177
7241
  }
7178
7242
  validateAgentList(metadata);
7179
7243
  const serialized = serializeAgents(metadata, dist);
7180
- const agentsPath = path23.resolve(rootDir, dist, AGENTS_FILE);
7244
+ const agentsPath = path24.resolve(rootDir, dist, AGENTS_FILE);
7181
7245
  await writeAgentsModule(serialized, agentsPath);
7182
7246
  return metadata;
7183
7247
  }
7184
7248
 
7185
7249
  // src/cli/generateTaskArtifacts.ts
7186
7250
  init_generateZodArtifacts();
7187
- import path24 from "path";
7251
+ import path25 from "path";
7188
7252
  init_createProgram();
7189
7253
  init_atomicWrite();
7190
7254
  init_extractHandlerTypes();
@@ -7222,7 +7286,7 @@ function collectTaskSchemaSources(tasks, rootDir) {
7222
7286
  const tasksByFile = /* @__PURE__ */ new Map();
7223
7287
  for (const task of tasks) {
7224
7288
  if (!task.inputTypeName) continue;
7225
- const absPath = path24.resolve(rootDir, task.filePath);
7289
+ const absPath = path25.resolve(rootDir, task.filePath);
7226
7290
  let list = tasksByFile.get(absPath);
7227
7291
  if (!list) {
7228
7292
  list = [];
@@ -7279,9 +7343,9 @@ function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
7279
7343
  async function generateTaskArtifacts(manifests, rootDir, dist) {
7280
7344
  const metadata = [];
7281
7345
  if (manifests.length > 0) {
7282
- const programByFile = createPrograms(manifests.map((m) => path24.resolve(rootDir, m.filePath)));
7346
+ const programByFile = createPrograms(manifests.map((m) => path25.resolve(rootDir, m.filePath)));
7283
7347
  for (const manifest of manifests) {
7284
- const absPath = path24.resolve(rootDir, manifest.filePath);
7348
+ const absPath = path25.resolve(rootDir, manifest.filePath);
7285
7349
  const result = extractToolMetadata(programByFile.get(absPath), absPath, "run", {
7286
7350
  name: manifest.name,
7287
7351
  filePath: manifest.filePath
@@ -7292,7 +7356,7 @@ async function generateTaskArtifacts(manifests, rootDir, dist) {
7292
7356
  }
7293
7357
  }
7294
7358
  const serialized = serializeTasks(manifests, dist);
7295
- const tasksPath = path24.resolve(rootDir, dist, TASKS_FILE);
7359
+ const tasksPath = path25.resolve(rootDir, dist, TASKS_FILE);
7296
7360
  await writeTasksModule(serialized, tasksPath);
7297
7361
  if (metadata.length === 0) {
7298
7362
  return hydrateTasks(serialized);
@@ -7314,8 +7378,8 @@ async function generateTaskArtifacts(manifests, rootDir, dist) {
7314
7378
  var TOOLS_FILE2 = "faapi-tools.js";
7315
7379
  var AGENTS_FILE2 = "faapi-agents.js";
7316
7380
  async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
7317
- const toolsPath = path25.resolve(rootDir, dist, TOOLS_FILE2);
7318
- if (!fs20.existsSync(toolsPath)) {
7381
+ const toolsPath = path26.resolve(rootDir, dist, TOOLS_FILE2);
7382
+ if (!fs21.existsSync(toolsPath)) {
7319
7383
  return [];
7320
7384
  }
7321
7385
  const serialized = await importWithCacheBust(toolsPath);
@@ -7324,8 +7388,8 @@ async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries
7324
7388
  return hydrated;
7325
7389
  }
7326
7390
  async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
7327
- const agentsPath = path25.resolve(rootDir, dist, AGENTS_FILE2);
7328
- if (!fs20.existsSync(agentsPath)) {
7391
+ const agentsPath = path26.resolve(rootDir, dist, AGENTS_FILE2);
7392
+ if (!fs21.existsSync(agentsPath)) {
7329
7393
  return [];
7330
7394
  }
7331
7395
  const serialized = await importWithCacheBust(agentsPath);
@@ -7340,8 +7404,8 @@ function getTaskDriverOptions(config) {
7340
7404
  return void 0;
7341
7405
  }
7342
7406
  async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries) {
7343
- const tasksPath = path25.resolve(rootDir, dist, TASKS_FILE);
7344
- if (!fs20.existsSync(tasksPath)) {
7407
+ const tasksPath = path26.resolve(rootDir, dist, TASKS_FILE);
7408
+ if (!fs21.existsSync(tasksPath)) {
7345
7409
  return [];
7346
7410
  }
7347
7411
  const serialized = await importWithCacheBust(tasksPath);
@@ -7533,8 +7597,9 @@ function logStartupSummary(routes, wsRoutes, tools, agents, taskMetas, p) {
7533
7597
  async function createAppBase(options) {
7534
7598
  const rootDir = options?.rootDir ?? process.cwd();
7535
7599
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
7536
- const routesPath = path26.resolve(rootDir, dist, ROUTES_FILE);
7537
- if (!fs21.existsSync(routesPath)) {
7600
+ const resourcesDir = resolveResourcesDir(rootDir, dist);
7601
+ const routesPath = path27.resolve(rootDir, dist, ROUTES_FILE);
7602
+ if (!fs22.existsSync(routesPath)) {
7538
7603
  throw new Error(
7539
7604
  `[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
7540
7605
  );
@@ -7613,6 +7678,8 @@ async function createAppBase(options) {
7613
7678
  routes: sorted,
7614
7679
  wsRoutes,
7615
7680
  rootDir,
7681
+ dist,
7682
+ resourcesDir,
7616
7683
  tasks: taskQueue,
7617
7684
  async listen(listenPort) {
7618
7685
  const envPort = process.env.PORT ? Number(process.env.PORT) : void 0;
@@ -7624,7 +7691,8 @@ async function createAppBase(options) {
7624
7691
  routes: sorted,
7625
7692
  server,
7626
7693
  registries,
7627
- tasks: taskQueue
7694
+ tasks: taskQueue,
7695
+ resourcesDir
7628
7696
  });
7629
7697
  console.log("- onBoot hook executed");
7630
7698
  } catch (err) {
@@ -7659,7 +7727,8 @@ async function createAppBase(options) {
7659
7727
  routes: sorted,
7660
7728
  server,
7661
7729
  registries,
7662
- tasks: taskQueue
7730
+ tasks: taskQueue,
7731
+ resourcesDir
7663
7732
  });
7664
7733
  console.log("- onReady hook executed");
7665
7734
  }
@@ -7683,7 +7752,8 @@ async function createAppBase(options) {
7683
7752
  routes: sorted,
7684
7753
  server,
7685
7754
  registries,
7686
- tasks: taskQueue
7755
+ tasks: taskQueue,
7756
+ resourcesDir
7687
7757
  });
7688
7758
  }
7689
7759
  registries.tool.clear();
@@ -7745,21 +7815,21 @@ async function createAppBase(options) {
7745
7815
  }
7746
7816
 
7747
7817
  // src/cli/createDevApp.ts
7748
- import path30 from "path";
7818
+ import path31 from "path";
7749
7819
 
7750
7820
  // src/router/scanRoutes.ts
7751
7821
  import fg3 from "fast-glob";
7752
- import path27 from "path";
7753
- import fs22 from "fs";
7822
+ import path28 from "path";
7823
+ import fs23 from "fs";
7754
7824
 
7755
7825
  // src/router/constants.ts
7756
7826
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
7757
7827
  var HTTP_METHOD_SET = new Set(HTTP_METHODS);
7758
7828
 
7759
7829
  // src/utils/normalizePath.ts
7760
- function normalizePath(path31) {
7761
- if (!path31) return "";
7762
- let result = path31.replace(/\\/g, "/");
7830
+ function normalizePath(path32) {
7831
+ if (!path32) return "";
7832
+ let result = path32.replace(/\\/g, "/");
7763
7833
  result = result.replace(/\/+/g, "/");
7764
7834
  result = result.replace(/\/+$/, "");
7765
7835
  if (result && !result.startsWith("/")) {
@@ -7820,34 +7890,34 @@ function extractExportsFromSource(source) {
7820
7890
  return names;
7821
7891
  }
7822
7892
  function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
7823
- const routeDir = path27.dirname(routeFilePath);
7824
- const resolvedRoot = path27.resolve(rootDir);
7893
+ const routeDir = path28.dirname(routeFilePath);
7894
+ const resolvedRoot = path28.resolve(rootDir);
7825
7895
  const paths = [];
7826
- let currentDir = path27.resolve(rootDir, routeDir);
7896
+ let currentDir = path28.resolve(rootDir, routeDir);
7827
7897
  while (true) {
7828
7898
  if (dist) {
7829
- const mwTsPath = path27.join(currentDir, "middlewares.ts");
7830
- const mwJsPath = path27.join(currentDir, "middlewares.js");
7831
- const absTsPath = path27.resolve(rootDir, mwTsPath);
7832
- const absJsPath = path27.resolve(rootDir, mwJsPath);
7833
- const absMwPath = fs22.existsSync(absTsPath) ? absTsPath : fs22.existsSync(absJsPath) ? absJsPath : null;
7899
+ const mwTsPath = path28.join(currentDir, "middlewares.ts");
7900
+ const mwJsPath = path28.join(currentDir, "middlewares.js");
7901
+ const absTsPath = path28.resolve(rootDir, mwTsPath);
7902
+ const absJsPath = path28.resolve(rootDir, mwJsPath);
7903
+ const absMwPath = fs23.existsSync(absTsPath) ? absTsPath : fs23.existsSync(absJsPath) ? absJsPath : null;
7834
7904
  if (absMwPath) {
7835
- const relMwPath = path27.relative(rootDir, absMwPath);
7836
- const prodAbsPath = path27.resolve(rootDir, toProdFilePath(relMwPath, dist));
7905
+ const relMwPath = path28.relative(rootDir, absMwPath);
7906
+ const prodAbsPath = path28.resolve(rootDir, toProdFilePath(relMwPath, dist));
7837
7907
  paths.push(prodAbsPath);
7838
7908
  }
7839
7909
  } else {
7840
7910
  for (const ext of [".ts", ".js"]) {
7841
- const mwPath = path27.join(currentDir, `middlewares${ext}`);
7842
- const absMwPath = path27.resolve(rootDir, mwPath);
7843
- if (fs22.existsSync(absMwPath)) {
7911
+ const mwPath = path28.join(currentDir, `middlewares${ext}`);
7912
+ const absMwPath = path28.resolve(rootDir, mwPath);
7913
+ if (fs23.existsSync(absMwPath)) {
7844
7914
  paths.push(absMwPath);
7845
7915
  break;
7846
7916
  }
7847
7917
  }
7848
7918
  }
7849
7919
  if (currentDir === resolvedRoot) break;
7850
- const parentDir = path27.dirname(currentDir);
7920
+ const parentDir = path28.dirname(currentDir);
7851
7921
  if (parentDir === currentDir) break;
7852
7922
  currentDir = parentDir;
7853
7923
  }
@@ -7866,7 +7936,7 @@ async function scanRoutes(rootDir, patterns, dist) {
7866
7936
  const normalizedFile = file.replace(/\\/g, "/");
7867
7937
  const fileName = normalizedFile.split("/").pop();
7868
7938
  if (fileName === "handler.ts" || fileName === "handler.js") {
7869
- const absPath = path27.resolve(rootDir, normalizedFile);
7939
+ const absPath = path28.resolve(rootDir, normalizedFile);
7870
7940
  const urlPath = filePathToUrlPath(normalizedFile);
7871
7941
  const paramNames = extractParamNames(urlPath);
7872
7942
  const isDynamic = paramNames.length > 0;
@@ -7879,7 +7949,7 @@ async function scanRoutes(rootDir, patterns, dist) {
7879
7949
  const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
7880
7950
  middlewareBundle = await loadMergedMiddlewares(mwPaths);
7881
7951
  }
7882
- const source = await fs22.promises.readFile(absPath, "utf8").catch(() => "");
7952
+ const source = await fs23.promises.readFile(absPath, "utf8").catch(() => "");
7883
7953
  const exportNames = extractExportsFromSource(source);
7884
7954
  const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
7885
7955
  for (const method of methods) {
@@ -7915,8 +7985,8 @@ async function scanRoutes(rootDir, patterns, dist) {
7915
7985
 
7916
7986
  // src/tools/scanTools.ts
7917
7987
  import fg4 from "fast-glob";
7918
- import path28 from "path";
7919
- import fs23 from "fs";
7988
+ import path29 from "path";
7989
+ import fs24 from "fs";
7920
7990
  var TOOL_PATTERNS = ["src/tools/**/*.ts"];
7921
7991
  var TOOL_EXPORT_RE = new RegExp(
7922
7992
  String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
@@ -7966,8 +8036,8 @@ async function scanTools(rootDir, patterns) {
7966
8036
  if (fileName !== "handler.ts" && fileName !== "handler.js") {
7967
8037
  continue;
7968
8038
  }
7969
- const absPath = path28.resolve(rootDir, normalizedFile);
7970
- const source = await fs23.promises.readFile(absPath, "utf8").catch(() => "");
8039
+ const absPath = path29.resolve(rootDir, normalizedFile);
8040
+ const source = await fs24.promises.readFile(absPath, "utf8").catch(() => "");
7971
8041
  const exportNames = extractToolExportsFromSource(source);
7972
8042
  const namespace = filePathToToolNamespace(normalizedFile);
7973
8043
  for (const fnName of exportNames) {
@@ -7991,8 +8061,8 @@ async function scanTools(rootDir, patterns) {
7991
8061
 
7992
8062
  // src/agents/scanAgents.ts
7993
8063
  import fg5 from "fast-glob";
7994
- import path29 from "path";
7995
- import fs24 from "fs";
8064
+ import path30 from "path";
8065
+ import fs25 from "fs";
7996
8066
  var DEFAULT_AGENT_PATTERNS = ["src/agents/**/handler.ts"];
7997
8067
  var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
7998
8068
  function extractAgentNameFromPath(filePath) {
@@ -8024,8 +8094,8 @@ async function scanAgents(rootDir, patterns) {
8024
8094
  if (fileName !== "handler.ts" && fileName !== "handler.js") {
8025
8095
  continue;
8026
8096
  }
8027
- const absPath = path29.resolve(rootDir, normalizedFile);
8028
- const source = await fs24.promises.readFile(absPath, "utf8").catch(() => "");
8097
+ const absPath = path30.resolve(rootDir, normalizedFile);
8098
+ const source = await fs25.promises.readFile(absPath, "utf8").catch(() => "");
8029
8099
  const { hasRun } = detectAgentExports(source);
8030
8100
  const name = extractAgentNameFromPath(normalizedFile);
8031
8101
  const prevFile = seen.get(name);
@@ -8096,7 +8166,7 @@ async function createDevApp(options) {
8096
8166
  if (!suppressProgramInvalidation) invalidateProgramCache();
8097
8167
  const tasks = await scanTasks(ctx.rootDir, TASK_PATTERNS);
8098
8168
  for (const task of tasks) {
8099
- await ensureCompiled(path30.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
8169
+ await ensureCompiled(path31.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
8100
8170
  }
8101
8171
  await generateTaskArtifacts(tasks, ctx.rootDir, ctx.dist);
8102
8172
  ctx.taskQueue.invalidateModules();