@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/cli/index.js +59 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +60 -33
- package/dist/index.js.map +1 -1
- package/dist/testing.js +42 -20
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
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),不绑定端口,返回已解析的
|
|
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
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
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
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
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
|
-
|
|
2416
|
+
state2.consoleLevel = objectConfig.consoleLevel;
|
|
2404
2417
|
} else {
|
|
2405
|
-
|
|
2418
|
+
state2.consoleLevel = null;
|
|
2406
2419
|
}
|
|
2407
2420
|
if (objectConfig.level !== void 0) {
|
|
2408
2421
|
assertLogLevel(objectConfig.level, "config.log.level");
|
|
2409
|
-
|
|
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
|
-
|
|
2425
|
+
state2.level = process.env.LOG_LEVEL;
|
|
2413
2426
|
} else {
|
|
2414
|
-
|
|
2427
|
+
state2.level = null;
|
|
2415
2428
|
}
|
|
2416
2429
|
}
|
|
2417
2430
|
function pipelineThreshold() {
|
|
2418
|
-
return
|
|
2431
|
+
return getLoggingState().level ?? void 0;
|
|
2419
2432
|
}
|
|
2420
2433
|
function consoleThreshold() {
|
|
2421
|
-
return
|
|
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
|
-
|
|
2454
|
+
const state2 = getLoggingState();
|
|
2455
|
+
if (state2.disabled) return;
|
|
2442
2456
|
const pipeThreshold = pipelineThreshold();
|
|
2443
|
-
if (
|
|
2444
|
-
if (!belowThreshold(entry.level, pipeThreshold))
|
|
2457
|
+
if (state2.sink) {
|
|
2458
|
+
if (!belowThreshold(entry.level, pipeThreshold)) state2.sink(entry);
|
|
2445
2459
|
return;
|
|
2446
2460
|
}
|
|
2447
|
-
if (
|
|
2448
|
-
if (!belowThreshold(entry.level, pipeThreshold))
|
|
2449
|
-
if (
|
|
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
|
-
|
|
2472
|
+
const state2 = getLoggingState();
|
|
2473
|
+
return state2.fileSink ? state2.fileSink.flush() : Promise.resolve();
|
|
2459
2474
|
}
|
|
2460
2475
|
function isAccessLogEnabled() {
|
|
2461
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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");
|