@faapi/faapi 4.0.0 → 4.1.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/README.md CHANGED
@@ -48,6 +48,11 @@ node dist/main # 启动生产服务器
48
48
  - 洋葱模型中间件:`(ctx, next) => {}`
49
49
  - 依赖注入:注入器按参数名匹配 handler 参数
50
50
  - WebSocket 路由:导出 `WS` 函数声明
51
+ - SSE 流式响应:`ctx.sse()` 流式推送(LLM 输出、通知推送)
52
+ - 响应压缩:`compression: true`,gzip/deflate/br 协商,SSE 自动跳过
53
+ - ETag/304 协商:`etag: true`,GET/HEAD 弱 ETag 自动生成与协商
54
+ - 优雅停机:SIGTERM/SIGINT 默认注册,drain 在途请求(`FAAPI_SHUTDOWN_TIMEOUT_MS` 可调)
55
+ - app 级注册表:tool/agent/skill 注册表随 app 实例创建与销毁,多 app 同进程隔离
51
56
  - SSE 流式响应:`ctx.sse()` 推送
52
57
  - 动态路由:`[id]` / `[...slug]` / `(group)`
53
58
  - MCP 集成:LLM 可查询路由 schema
package/dist/cli/index.js CHANGED
@@ -7519,7 +7519,7 @@ import { createSecureServer as createHttp2SecureServer } from "http2";
7519
7519
  import { readFileSync } from "fs";
7520
7520
  import { Readable as Readable3 } from "stream";
7521
7521
  import path22 from "path";
7522
- function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
7522
+ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT, requestSignal) {
7523
7523
  const forwardedProto = req.headers["x-forwarded-proto"];
7524
7524
  const protocol = Array.isArray(forwardedProto) ? forwardedProto[0]?.split(",")[0]?.trim() ?? "http" : forwardedProto?.split(",")[0]?.trim() ?? "http";
7525
7525
  const host = req.headers.host ?? "localhost";
@@ -7527,7 +7527,10 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
7527
7527
  const headers = nodeHttpToWebHeaders(req);
7528
7528
  const method = req.method ?? "GET";
7529
7529
  if (method === "GET" || method === "HEAD") {
7530
- return { request: new Request(url.toString(), { method, headers }), url };
7530
+ return {
7531
+ request: new Request(url.toString(), { method, headers, signal: requestSignal }),
7532
+ url
7533
+ };
7531
7534
  }
7532
7535
  const contentLength = req.headers["content-length"];
7533
7536
  if (contentLength !== void 0) {
@@ -7543,7 +7546,8 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
7543
7546
  method,
7544
7547
  headers,
7545
7548
  body: limitedStream,
7546
- duplex: "half"
7549
+ duplex: "half",
7550
+ signal: requestSignal
7547
7551
  }),
7548
7552
  url
7549
7553
  };
@@ -7682,8 +7686,8 @@ function createServer(options) {
7682
7686
  });
7683
7687
  return { server, routesRef };
7684
7688
  }
7685
- function prepareRequest(req, config, bodyLimit, trustedProxy, registries) {
7686
- const { request, url } = toWebRequest(req, bodyLimit);
7689
+ function prepareRequest(req, config, bodyLimit, trustedProxy, registries, requestSignal) {
7690
+ const { request, url } = toWebRequest(req, bodyLimit, requestSignal);
7687
7691
  const method = request.method.toUpperCase();
7688
7692
  const urlPath = url.pathname;
7689
7693
  const ctx = createContextFromUrl(
@@ -7768,8 +7772,19 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
7768
7772
  async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries) {
7769
7773
  let meta = { headers: {}, setCookies: [] };
7770
7774
  let ctx;
7775
+ const abortController = new AbortController();
7776
+ res.on("close", () => {
7777
+ if (!res.writableEnded) abortController.abort();
7778
+ });
7771
7779
  try {
7772
- const prepared = prepareRequest(req, config, bodyLimit, trustedProxy, registries);
7780
+ const prepared = prepareRequest(
7781
+ req,
7782
+ config,
7783
+ bodyLimit,
7784
+ trustedProxy,
7785
+ registries,
7786
+ abortController.signal
7787
+ );
7773
7788
  ctx = prepared.ctx;
7774
7789
  meta = prepared.meta;
7775
7790
  const { request, url, method, urlPath } = prepared;