@faapi/faapi 6.22.0 → 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.
@@ -1025,6 +1025,14 @@ interface FaapiContext {
1025
1025
  cookies: Record<string, string>;
1026
1026
  /** 配置文件中的自定义业务配置(类型可通过 declare module '@faapi/faapi' 增强 FaapiContextConfig) */
1027
1027
  config: FaapiContextConfig;
1028
+ /**
1029
+ * 运行时资源根目录绝对路径(<rootDir>/<dist>/resources)
1030
+ *
1031
+ * `src/resources/` 经 dev/build 复制进产物后的位置,handler 读静态文件用:
1032
+ * `await fs.readFile(path.join(ctx.resourcesDir, 'prompts/foo.md'))`。
1033
+ * HTTP/WS 请求链路恒有值;testing 直调(createTestContext)未传时为 undefined。
1034
+ */
1035
+ resourcesDir?: string;
1028
1036
  /**
1029
1037
  * 设置响应状态码
1030
1038
  */
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as FaapiContext, F as FaapiMiddleware, I as InjectorMap, R as RouteManifest, W as WsRouteManifest, C as CorsOptions, H as HelmetOptions } from './routeTypes-CXWkRx4W.js';
1
+ import { a as FaapiContext, F as FaapiMiddleware, I as InjectorMap, R as RouteManifest, W as WsRouteManifest, C as CorsOptions, H as HelmetOptions } from './routeTypes-CJV5M90s.js';
2
2
  import { Server } from 'node:http';
3
3
  import { WebSocket } from 'ws';
4
4
 
@@ -25,6 +25,8 @@ declare function createTestContext(options: CreateTestContextOptions): FaapiCont
25
25
  interface CreateTestContextOptions {
26
26
  /** app 级注册表(测试 handler 声明 agent/agents 参数时注入用,可选) */
27
27
  registries?: FaapiContext['registries'];
28
+ /** 运行时资源根目录(测试 handler 读 ctx.resourcesDir 时传入,可选) */
29
+ resourcesDir?: string;
28
30
  /** 请求方法,默认 'GET' */
29
31
  method?: string;
30
32
  /** 请求路径,必填,如 '/api/user'(无需写 host) */
package/dist/testing.js CHANGED
@@ -312,14 +312,14 @@ var ValidationError = class extends FaapiError {
312
312
  issues;
313
313
  };
314
314
  var RouteNotFoundError = class extends FaapiError {
315
- constructor(path16) {
316
- super("ROUTE_NOT_FOUND", `Route not found: ${path16}`, 404);
315
+ constructor(path17) {
316
+ super("ROUTE_NOT_FOUND", `Route not found: ${path17}`, 404);
317
317
  this.name = "RouteNotFoundError";
318
318
  }
319
319
  };
320
320
  var MethodNotAllowedError = class extends FaapiError {
321
- constructor(method, path16, allowedMethods) {
322
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path16}`, 405);
321
+ constructor(method, path17, allowedMethods) {
322
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path17}`, 405);
323
323
  this.allowedMethods = allowedMethods;
324
324
  this.name = "MethodNotAllowedError";
325
325
  }
@@ -584,10 +584,18 @@ function formatSetCookie(name, value, options) {
584
584
  if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;
585
585
  return cookie;
586
586
  }
587
- function createContext(request, params, config = {}, ip = "", registries) {
588
- return createContextFromUrl(request, new URL(request.url), params, config, ip, registries);
587
+ function createContext(request, params, config = {}, ip = "", registries, resourcesDir) {
588
+ return createContextFromUrl(
589
+ request,
590
+ new URL(request.url),
591
+ params,
592
+ config,
593
+ ip,
594
+ registries,
595
+ resourcesDir
596
+ );
589
597
  }
590
- function createContextFromUrl(request, url, params, config = {}, ip = "", registries) {
598
+ function createContextFromUrl(request, url, params, config = {}, ip = "", registries, resourcesDir) {
591
599
  const meta = { headers: {}, setCookies: [] };
592
600
  const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
593
601
  const cookiesObj = {};
@@ -615,6 +623,9 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
615
623
  ua: request.headers.get("user-agent") ?? "",
616
624
  cookies: cookiesObj,
617
625
  config,
626
+ // 运行时资源根目录(HTTP/WS 链路由 createServer/handleWsUpgrade 传入;
627
+ // testing 直调未传时为 undefined,见 contextTypes.resourcesDir 注释)
628
+ resourcesDir,
618
629
  meta,
619
630
  setStatus(status) {
620
631
  meta.status = status;
@@ -720,15 +731,16 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
720
731
  function createTestContext(options) {
721
732
  const {
722
733
  method = "GET",
723
- path: path16,
734
+ path: path17,
724
735
  query,
725
736
  headers,
726
737
  params = {},
727
738
  config = {},
728
739
  ip = "",
729
- registries
740
+ registries,
741
+ resourcesDir
730
742
  } = options;
731
- const url = new URL(`http://localhost${path16}`);
743
+ const url = new URL(`http://localhost${path17}`);
732
744
  if (query) {
733
745
  for (const [key, value] of Object.entries(query)) {
734
746
  if (Array.isArray(value)) {
@@ -744,7 +756,7 @@ function createTestContext(options) {
744
756
  method,
745
757
  headers
746
758
  });
747
- return createContext(request, params, config, ip, registries);
759
+ return createContext(request, params, config, ip, registries, resourcesDir);
748
760
  }
749
761
 
750
762
  // src/response/pendingMeta.ts
@@ -1327,9 +1339,9 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
1327
1339
  }
1328
1340
 
1329
1341
  // src/testServer.ts
1330
- import path15 from "path";
1342
+ import path16 from "path";
1331
1343
  import os from "os";
1332
- import fs12 from "fs/promises";
1344
+ import fs13 from "fs/promises";
1333
1345
 
1334
1346
  // src/router/scanRoutes.ts
1335
1347
  import fg from "fast-glob";
@@ -1341,9 +1353,9 @@ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
1341
1353
  var HTTP_METHOD_SET = new Set(HTTP_METHODS);
1342
1354
 
1343
1355
  // src/utils/normalizePath.ts
1344
- function normalizePath(path16) {
1345
- if (!path16) return "";
1346
- let result = path16.replace(/\\/g, "/");
1356
+ function normalizePath(path17) {
1357
+ if (!path17) return "";
1358
+ let result = path17.replace(/\\/g, "/");
1347
1359
  result = result.replace(/\/+/g, "/");
1348
1360
  result = result.replace(/\/+$/, "");
1349
1361
  if (result && !result.startsWith("/")) {
@@ -3511,13 +3523,14 @@ function buildAliasPlugins(rootDir) {
3511
3523
  }
3512
3524
 
3513
3525
  // src/cli/compileSourceFiles.ts
3526
+ var SOURCE_IGNORE = ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts", `${APP_DIR}/resources/**`];
3514
3527
  async function compileSourceFiles(options) {
3515
3528
  const { rootDir, dist, files, logLevel = "silent", production, atomicWrite } = options;
3516
3529
  const entryPoints = files ?? await fg2([`${APP_DIR}/**/*.ts`], {
3517
3530
  cwd: rootDir,
3518
3531
  onlyFiles: true,
3519
3532
  absolute: true,
3520
- ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
3533
+ ignore: SOURCE_IGNORE
3521
3534
  });
3522
3535
  if (entryPoints.length === 0) {
3523
3536
  return { compiledFiles: [] };
@@ -3821,9 +3834,9 @@ async function validateInput(schemaPath, method, inputType, input) {
3821
3834
  function mapZodIssues(error) {
3822
3835
  return error.issues.map((issue) => {
3823
3836
  const code = mapZodCode(issue);
3824
- const path16 = issue.path.map(String).join(".") || "";
3837
+ const path17 = issue.path.map(String).join(".") || "";
3825
3838
  return {
3826
- path: path16,
3839
+ path: path17,
3827
3840
  code,
3828
3841
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
3829
3842
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -3873,7 +3886,7 @@ import {
3873
3886
  import { createSecureServer as createHttp2SecureServer } from "http2";
3874
3887
  import { readFileSync } from "fs";
3875
3888
  import { Readable as Readable2 } from "stream";
3876
- import path14 from "path";
3889
+ import path15 from "path";
3877
3890
 
3878
3891
  // src/router/matchRoute.ts
3879
3892
  var httpIndexCache = /* @__PURE__ */ new WeakMap();
@@ -3915,25 +3928,25 @@ function getWsIndex(routes) {
3915
3928
  wsIndexCache.set(routes, index);
3916
3929
  return index;
3917
3930
  }
3918
- function matchRoute(routes, method, path16) {
3931
+ function matchRoute(routes, method, path17) {
3919
3932
  const index = getHttpIndex(routes);
3920
3933
  const upper = method.toUpperCase();
3921
- const hit = matchByMethod(index, upper, path16);
3934
+ const hit = matchByMethod(index, upper, path17);
3922
3935
  if (hit) return hit;
3923
3936
  if (upper === "HEAD") {
3924
- return matchByMethod(index, "GET", path16);
3937
+ return matchByMethod(index, "GET", path17);
3925
3938
  }
3926
3939
  return null;
3927
3940
  }
3928
- function matchByMethod(index, method, path16) {
3929
- const staticHit = index.static.get(`${method}|${path16}`);
3941
+ function matchByMethod(index, method, path17) {
3942
+ const staticHit = index.static.get(`${method}|${path17}`);
3930
3943
  if (staticHit) {
3931
3944
  return { route: staticHit, params: {} };
3932
3945
  }
3933
3946
  if (index.dynamics.length === 0) {
3934
3947
  return null;
3935
3948
  }
3936
- const pathSegments = path16.split("/").filter(Boolean);
3949
+ const pathSegments = path17.split("/").filter(Boolean);
3937
3950
  for (const entry of index.dynamics) {
3938
3951
  const route = entry.route;
3939
3952
  if (route.method !== method) {
@@ -3946,31 +3959,31 @@ function matchByMethod(index, method, path16) {
3946
3959
  }
3947
3960
  return null;
3948
3961
  }
3949
- function matchWsRoute(wsRoutes, path16) {
3962
+ function matchWsRoute(wsRoutes, path17) {
3950
3963
  const index = getWsIndex(wsRoutes);
3951
- const staticHit = index.static.get(path16);
3964
+ const staticHit = index.static.get(path17);
3952
3965
  if (staticHit) {
3953
3966
  return { route: staticHit, params: {} };
3954
3967
  }
3955
3968
  for (const route of index.dynamics) {
3956
- const params = matchDynamicPath(route.urlPath, path16, route.paramNames, route.isCatchAll);
3969
+ const params = matchDynamicPath(route.urlPath, path17, route.paramNames, route.isCatchAll);
3957
3970
  if (params !== null) {
3958
3971
  return { route, params };
3959
3972
  }
3960
3973
  }
3961
3974
  return null;
3962
3975
  }
3963
- function findAllowedMethods(routes, path16) {
3976
+ function findAllowedMethods(routes, path17) {
3964
3977
  const index = getHttpIndex(routes);
3965
3978
  const methods = /* @__PURE__ */ new Set();
3966
- const staticMethods = index.methodsByStaticPath.get(path16);
3979
+ const staticMethods = index.methodsByStaticPath.get(path17);
3967
3980
  if (staticMethods) {
3968
3981
  for (const method of staticMethods) {
3969
3982
  methods.add(method);
3970
3983
  }
3971
3984
  }
3972
3985
  if (index.dynamics.length > 0) {
3973
- const pathSegments = path16.split("/").filter(Boolean);
3986
+ const pathSegments = path17.split("/").filter(Boolean);
3974
3987
  for (const entry of index.dynamics) {
3975
3988
  const params = matchSegments(
3976
3989
  entry.segments,
@@ -3988,10 +4001,10 @@ function findAllowedMethods(routes, path16) {
3988
4001
  }
3989
4002
  return Array.from(methods);
3990
4003
  }
3991
- function matchDynamicPath(pattern, path16, paramNames, isCatchAll) {
4004
+ function matchDynamicPath(pattern, path17, paramNames, isCatchAll) {
3992
4005
  return matchSegments(
3993
4006
  pattern.split("/").filter(Boolean),
3994
- path16.split("/").filter(Boolean),
4007
+ path17.split("/").filter(Boolean),
3995
4008
  paramNames,
3996
4009
  isCatchAll
3997
4010
  );
@@ -4613,9 +4626,17 @@ function logger(options = {}) {
4613
4626
  }
4614
4627
 
4615
4628
  // src/server/handleWsUpgrade.ts
4616
- import fs11 from "fs";
4629
+ import fs12 from "fs";
4617
4630
  import { WebSocketServer, WebSocket } from "ws";
4631
+ import path14 from "path";
4632
+
4633
+ // src/cli/copyResources.ts
4618
4634
  import path13 from "path";
4635
+ import fs11 from "fs";
4636
+ var RESOURCES_OUTPUT_DIR = "resources";
4637
+ function resolveResourcesDir(rootDir, dist) {
4638
+ return path13.resolve(rootDir, dist, RESOURCES_OUTPUT_DIR);
4639
+ }
4619
4640
 
4620
4641
  // src/server/serverUtils.ts
4621
4642
  function nodeHttpToWebHeaders(req) {
@@ -4673,7 +4694,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
4673
4694
  const dist = getDevDist();
4674
4695
  if (dist) {
4675
4696
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
4676
- if (sourcePath && fs11.existsSync(sourcePath)) {
4697
+ if (sourcePath && fs12.existsSync(sourcePath)) {
4677
4698
  await ensureCompiled(sourcePath, rootDir, dist);
4678
4699
  }
4679
4700
  }
@@ -4771,6 +4792,7 @@ function attachWebSocket(options) {
4771
4792
  trustedProxy = false,
4772
4793
  registries
4773
4794
  } = options;
4795
+ const resourcesDir = resolveResourcesDir(rootDir, dist);
4774
4796
  const wss = new WebSocketServer({ noServer: true });
4775
4797
  server.on("upgrade", async (req, socket, head) => {
4776
4798
  try {
@@ -4802,12 +4824,19 @@ function attachWebSocket(options) {
4802
4824
  const host = req.headers.host ?? "localhost";
4803
4825
  const fullUrl = new URL(`http://${host}${req.url ?? "/"}`);
4804
4826
  const request = new Request(fullUrl.toString(), { method: "GET", headers });
4805
- const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);
4827
+ const ctx = createContext(
4828
+ request,
4829
+ params,
4830
+ config,
4831
+ getClientIp(req, trustedProxy),
4832
+ registries,
4833
+ resourcesDir
4834
+ );
4806
4835
  const meta = ctx.meta;
4807
4836
  let upgraded = false;
4808
4837
  const finalHandler = async () => {
4809
4838
  const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
4810
- if (fs11.existsSync(schemaPath)) {
4839
+ if (fs12.existsSync(schemaPath)) {
4811
4840
  if (isDevOnDemandEnabled()) {
4812
4841
  const devDist = getDevDist();
4813
4842
  if (devDist) {
@@ -4846,7 +4875,7 @@ function attachWebSocket(options) {
4846
4875
  }
4847
4876
  let handlers;
4848
4877
  try {
4849
- const absoluteFilePath = path13.resolve(rootDir, route.filePath);
4878
+ const absoluteFilePath = path14.resolve(rootDir, route.filePath);
4850
4879
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
4851
4880
  } catch (err) {
4852
4881
  const reason = err instanceof Error ? err.message : String(err);
@@ -5005,6 +5034,7 @@ function createServer(options) {
5005
5034
  trustedProxy = false
5006
5035
  } = options;
5007
5036
  const routesRef = { current: routes, wsCurrent: wsRoutes ?? [] };
5037
+ const resourcesDir = resolveResourcesDir(rootDir, dist);
5008
5038
  const configMiddlewares = [];
5009
5039
  if (compressionOption) {
5010
5040
  const compOpts = typeof compressionOption === "object" ? compressionOption : {};
@@ -5051,7 +5081,8 @@ function createServer(options) {
5051
5081
  bodyLimit,
5052
5082
  trustedProxy,
5053
5083
  registries,
5054
- routesRef.wsCurrent
5084
+ routesRef.wsCurrent,
5085
+ resourcesDir
5055
5086
  ).catch((err) => {
5056
5087
  console.error("[faapi] Request pipeline failed after error response attempt:", err);
5057
5088
  try {
@@ -5077,7 +5108,7 @@ function createServer(options) {
5077
5108
  });
5078
5109
  return { server, routesRef };
5079
5110
  }
5080
- function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched) {
5111
+ function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched, resourcesDir) {
5081
5112
  const method = request.method.toUpperCase();
5082
5113
  const urlPath = url.pathname;
5083
5114
  const ctx = createContextFromUrl(
@@ -5086,7 +5117,8 @@ function prepareRequest(req, request, url, config, trustedProxy, registries, pre
5086
5117
  preMatched?.params ?? {},
5087
5118
  config,
5088
5119
  getClientIp(req, trustedProxy),
5089
- registries
5120
+ registries,
5121
+ resourcesDir
5090
5122
  );
5091
5123
  const meta = ctx.meta;
5092
5124
  return { request, url, ctx, meta, method, urlPath };
@@ -5105,7 +5137,7 @@ function getRoutePaths(route, rootDir, dist) {
5105
5137
  let cached = routePathCache.get(route);
5106
5138
  if (!cached) {
5107
5139
  cached = {
5108
- absFilePath: path14.resolve(rootDir, route.filePath),
5140
+ absFilePath: path15.resolve(rootDir, route.filePath),
5109
5141
  schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
5110
5142
  };
5111
5143
  routePathCache.set(route, cached);
@@ -5228,7 +5260,7 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
5228
5260
  }
5229
5261
  }
5230
5262
  }
5231
- async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes) {
5263
+ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes, resourcesDir) {
5232
5264
  let meta = { headers: {}, setCookies: [] };
5233
5265
  let ctx;
5234
5266
  let sseEarlySent = false;
@@ -5248,7 +5280,8 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
5248
5280
  config,
5249
5281
  trustedProxy,
5250
5282
  registries,
5251
- preMatched
5283
+ preMatched,
5284
+ resourcesDir
5252
5285
  );
5253
5286
  ctx = prepared.ctx;
5254
5287
  meta = prepared.meta;
@@ -5313,7 +5346,7 @@ async function createTestServer(options) {
5313
5346
  } = options;
5314
5347
  const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);
5315
5348
  const sorted = sortRoutes(routes);
5316
- const schemaDist = dist ? path15.isAbsolute(dist) ? dist : path15.resolve(rootDir, dist) : await fs12.mkdtemp(path15.join(os.tmpdir(), "faapi-test-schema-"));
5349
+ const schemaDist = dist ? path16.isAbsolute(dist) ? dist : path16.resolve(rootDir, dist) : await fs13.mkdtemp(path16.join(os.tmpdir(), "faapi-test-schema-"));
5317
5350
  await generateSchemaFiles(sorted, rootDir, schemaDist);
5318
5351
  const { server } = createServer({
5319
5352
  routes: sorted,
@@ -5345,7 +5378,7 @@ async function createTestServer(options) {
5345
5378
  await new Promise((resolve) => {
5346
5379
  server.close(() => resolve());
5347
5380
  });
5348
- await fs12.rm(schemaDist, { recursive: true, force: true }).catch(() => {
5381
+ await fs13.rm(schemaDist, { recursive: true, force: true }).catch(() => {
5349
5382
  });
5350
5383
  invalidateSchemaCache();
5351
5384
  }