@faapi/faapi 6.10.0 → 6.11.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.d.ts CHANGED
@@ -1622,7 +1622,18 @@ interface InjectOptions {
1622
1622
  interface InjectResponse {
1623
1623
  status: number;
1624
1624
  headers: Headers;
1625
+ /** 响应字节按 JSON 反序列化的结果(非 JSON 回退字符串)——HTTP 语义,与真实客户端一致 */
1625
1626
  body: unknown;
1627
+ /**
1628
+ * handler 原始返回值(自动包裹与 JSON 序列化之前,Date 等富类型不丢)
1629
+ *
1630
+ * 仅当响应体由 handler 数据返回值自动包裹产生时捕获(同进程取数类型保真,
1631
+ * 快路径判断用 `res.raw !== undefined`);handler 返回 Response 形态
1632
+ * (ctx.ok/ctx.fail/ctx.json)、抛错、中间件拦截、SSE、404/405/校验失败等
1633
+ * handler 未产生数据返回值的场景为 undefined。语义详见 createAppCore.md
1634
+ * 「inject 的 raw 字段」节。
1635
+ */
1636
+ raw?: unknown;
1626
1637
  }
1627
1638
  /**
1628
1639
  * 获取当前 faapi app 单例
@@ -1682,7 +1693,9 @@ interface AppBase {
1682
1693
  * 无服务器测试注入
1683
1694
  *
1684
1695
  * 构建一个模拟请求直接走完整请求链路(CORS / helmet / logger / 全局中间件 / 路由匹配 /
1685
- * schema 校验 / 目录中间件 / handler),不绑定端口,返回已解析的 `{ status, headers, body }`。
1696
+ * schema 校验 / 目录中间件 / handler),不绑定端口,返回已解析的
1697
+ * `{ status, headers, body, raw }`——body 为 JSON 反序列化结果(HTTP 语义),
1698
+ * raw 为 handler 原始返回值(同进程取数类型保真,语义见 InjectResponse.raw)。
1686
1699
  *
1687
1700
  * `listen()` 前后均可调用——`listen()` 后调用常用于 Next.js Server Component 等同进程场景
1688
1701
  * (配合 `getApp()` 拿到 app 实例)。
package/dist/index.js CHANGED
@@ -2374,51 +2374,64 @@ function assertLogLevel(value, source) {
2374
2374
  );
2375
2375
  }
2376
2376
  }
2377
- var globalState = {
2378
- level: null,
2379
- consoleLevel: null,
2380
- sink: null,
2381
- fileSink: null,
2382
- stdout: true,
2383
- accessLog: true,
2384
- disabled: false
2385
- };
2377
+ var LOGGING_STATE_KEY = /* @__PURE__ */ Symbol.for("faapi.logger.state");
2378
+ function createDefaultLoggingState() {
2379
+ return {
2380
+ level: null,
2381
+ consoleLevel: null,
2382
+ sink: null,
2383
+ fileSink: null,
2384
+ stdout: true,
2385
+ accessLog: true,
2386
+ disabled: false
2387
+ };
2388
+ }
2389
+ function getLoggingState() {
2390
+ const g = globalThis;
2391
+ let state2 = g[LOGGING_STATE_KEY];
2392
+ if (!state2) {
2393
+ state2 = createDefaultLoggingState();
2394
+ g[LOGGING_STATE_KEY] = state2;
2395
+ }
2396
+ return state2;
2397
+ }
2386
2398
  function configureLogging(config) {
2399
+ const state2 = getLoggingState();
2387
2400
  const objectConfig = typeof config === "object" ? config : {};
2388
2401
  if (objectConfig.sink && objectConfig.dir) {
2389
2402
  throw new Error(
2390
2403
  "[faapi] config.log.sink and config.log.dir are mutually exclusive: pick one output pipeline (custom sink OR built-in file logs)."
2391
2404
  );
2392
2405
  }
2393
- void globalState.fileSink?.close();
2394
- globalState.disabled = config === false;
2395
- globalState.sink = objectConfig.sink ?? null;
2396
- globalState.accessLog = objectConfig.accessLog ?? true;
2397
- globalState.stdout = objectConfig.stdout ?? true;
2398
- globalState.fileSink = objectConfig.dir ? createFileLogSink({ dir: objectConfig.dir, splitByLevel: objectConfig.splitByLevel }) : null;
2406
+ void state2.fileSink?.close();
2407
+ state2.disabled = config === false;
2408
+ state2.sink = objectConfig.sink ?? null;
2409
+ state2.accessLog = objectConfig.accessLog ?? true;
2410
+ state2.stdout = objectConfig.stdout ?? true;
2411
+ state2.fileSink = objectConfig.dir ? createFileLogSink({ dir: objectConfig.dir, splitByLevel: objectConfig.splitByLevel }) : null;
2399
2412
  if (objectConfig.consoleLevel !== void 0) {
2400
2413
  if (objectConfig.consoleLevel !== false) {
2401
2414
  assertLogLevel(objectConfig.consoleLevel, "config.log.consoleLevel");
2402
2415
  }
2403
- globalState.consoleLevel = objectConfig.consoleLevel;
2416
+ state2.consoleLevel = objectConfig.consoleLevel;
2404
2417
  } else {
2405
- globalState.consoleLevel = null;
2418
+ state2.consoleLevel = null;
2406
2419
  }
2407
2420
  if (objectConfig.level !== void 0) {
2408
2421
  assertLogLevel(objectConfig.level, "config.log.level");
2409
- globalState.level = objectConfig.level;
2422
+ state2.level = objectConfig.level;
2410
2423
  } else if (process.env.LOG_LEVEL !== void 0) {
2411
2424
  assertLogLevel(process.env.LOG_LEVEL, "LOG_LEVEL env");
2412
- globalState.level = process.env.LOG_LEVEL;
2425
+ state2.level = process.env.LOG_LEVEL;
2413
2426
  } else {
2414
- globalState.level = null;
2427
+ state2.level = null;
2415
2428
  }
2416
2429
  }
2417
2430
  function pipelineThreshold() {
2418
- return globalState.level ?? void 0;
2431
+ return getLoggingState().level ?? void 0;
2419
2432
  }
2420
2433
  function consoleThreshold() {
2421
- return globalState.consoleLevel ?? "info";
2434
+ return getLoggingState().consoleLevel ?? "info";
2422
2435
  }
2423
2436
  function belowThreshold(level, threshold) {
2424
2437
  return threshold !== void 0 && LEVEL_RANK[level] < LEVEL_RANK[threshold];
@@ -2438,15 +2451,16 @@ function consoleDispatch(entry) {
2438
2451
  console[CONSOLE_METHOD[entry.level]](formatEntry(entry));
2439
2452
  }
2440
2453
  function globalDispatch(entry) {
2441
- if (globalState.disabled) return;
2454
+ const state2 = getLoggingState();
2455
+ if (state2.disabled) return;
2442
2456
  const pipeThreshold = pipelineThreshold();
2443
- if (globalState.sink) {
2444
- if (!belowThreshold(entry.level, pipeThreshold)) globalState.sink(entry);
2457
+ if (state2.sink) {
2458
+ if (!belowThreshold(entry.level, pipeThreshold)) state2.sink(entry);
2445
2459
  return;
2446
2460
  }
2447
- if (globalState.fileSink) {
2448
- if (!belowThreshold(entry.level, pipeThreshold)) globalState.fileSink.write(entry);
2449
- if (globalState.stdout) consoleDispatch(entry);
2461
+ if (state2.fileSink) {
2462
+ if (!belowThreshold(entry.level, pipeThreshold)) state2.fileSink.write(entry);
2463
+ if (state2.stdout) consoleDispatch(entry);
2450
2464
  return;
2451
2465
  }
2452
2466
  consoleDispatch(entry);
@@ -2455,10 +2469,12 @@ function writeLogEntry(entry) {
2455
2469
  globalDispatch(entry);
2456
2470
  }
2457
2471
  function flushLogging() {
2458
- return globalState.fileSink ? globalState.fileSink.flush() : Promise.resolve();
2472
+ const state2 = getLoggingState();
2473
+ return state2.fileSink ? state2.fileSink.flush() : Promise.resolve();
2459
2474
  }
2460
2475
  function isAccessLogEnabled() {
2461
- return !globalState.disabled && globalState.accessLog;
2476
+ const state2 = getLoggingState();
2477
+ return !state2.disabled && state2.accessLog;
2462
2478
  }
2463
2479
  function createLogger(scope, options) {
2464
2480
  const baseScope = scope;
@@ -5148,6 +5164,9 @@ async function injectParamsAsync(handler, ctx, body, injectors) {
5148
5164
  function wrapResult(result, ctx) {
5149
5165
  return wrapOkResult(result, ctx.config);
5150
5166
  }
5167
+ function stashHandlerResult(ctx, result) {
5168
+ ctx.__faapiHandlerResult = result;
5169
+ }
5151
5170
  function mergeMeta(response, meta) {
5152
5171
  const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
5153
5172
  if (!hasMeta) return response;
@@ -5216,7 +5235,9 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
5216
5235
  const result = await injectParamsAsync(handler, ctx, body, injectors);
5217
5236
  const sseResponse = pickSseAndAutoClose();
5218
5237
  if (sseResponse) return sseResponse;
5219
- return toResponse(wrapResult(result, ctx), meta);
5238
+ const response = await toResponse(wrapResult(result, ctx), meta);
5239
+ if (!(result instanceof Response)) stashHandlerResult(ctx, result);
5240
+ return response;
5220
5241
  } catch (err) {
5221
5242
  autoCloseSseOnError();
5222
5243
  throw err;
@@ -5227,7 +5248,9 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
5227
5248
  const result = await injectParamsAsync(handler, ctx, body, injectors);
5228
5249
  const sseResponse = pickSseAndAutoClose();
5229
5250
  if (sseResponse) return sseResponse;
5230
- return toResponse(wrapResult(result, ctx), meta);
5251
+ const response = await toResponse(wrapResult(result, ctx), meta);
5252
+ if (!(result instanceof Response)) stashHandlerResult(ctx, result);
5253
+ return response;
5231
5254
  } catch (err) {
5232
5255
  autoCloseSseOnError();
5233
5256
  throw err;
@@ -6137,6 +6160,7 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
6137
6160
  globalInjectors
6138
6161
  });
6139
6162
  const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
6163
+ res.__faapiHandlerResult = ctx.__faapiHandlerResult;
6140
6164
  await sendSuccessResponse(response, res);
6141
6165
  } catch (err) {
6142
6166
  if (res.destroyed || res.writableEnded) return;
@@ -7180,7 +7204,10 @@ async function createAppBase(options) {
7180
7204
  resolve({
7181
7205
  status: mockRes.statusCode,
7182
7206
  headers: new Headers(mockRes._headers),
7183
- body: parsed
7207
+ body: parsed,
7208
+ // inject raw 捕获桥(第三跳):handler 原始返回值(handleRequest 从 ctx
7209
+ // 桥接到 res;未捕获时 undefined——语义见 InjectResponse.raw 注释)
7210
+ raw: mockRes.__faapiHandlerResult
7184
7211
  });
7185
7212
  });
7186
7213
  const listeners = server.listeners("request");