@faapi/faapi 6.11.0 → 6.13.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.
@@ -752,7 +752,9 @@ type TaskFailedHandler = (info: TaskFailedInfo) => Promise<void> | void;
752
752
  *
753
753
  * 设计要点:
754
754
  * - writer 内部用 ReadableStream + TextEncoder,send 时 enqueue,close 时 close controller
755
- * - response 预设 text/event-stream、no-cache、keep-alive 头,状态码默认 200
755
+ * - response 预设 text/event-stream、no-cache、keep-alive、X-Accel-Buffering: no 头,状态码默认 200
756
+ * (X-Accel-Buffering: no 让 nginx 按响应跳过 proxy_buffering,SSE 小包不被攒到流结束才下发;
757
+ * 非 nginx 反代不识别该头,当普通自定义头透传)
756
758
  * - close 后再 send 静默忽略,避免 handler 异步流程中误写已关闭的流
757
759
  * - sendError 向流写入 event: error 后关闭,用于流式输出中报错的优雅终止
758
760
  *
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-m9wZHWyo.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-CVPV8Yd3.js';
2
2
  import { Server } from 'node:http';
3
3
  import { WebSocket } from 'ws';
4
4
 
package/dist/testing.js CHANGED
@@ -1,3 +1,71 @@
1
+ // src/utils/isPlainObject.ts
2
+ function isPlainObject(value) {
3
+ if (value === null || typeof value !== "object") {
4
+ return false;
5
+ }
6
+ if (Array.isArray(value)) {
7
+ return false;
8
+ }
9
+ const proto = Object.getPrototypeOf(value);
10
+ return proto === null || proto === Object.prototype;
11
+ }
12
+
13
+ // src/utils/stringifyJson.ts
14
+ function stringifyJson(value) {
15
+ return JSON.stringify(convertValue(value, /* @__PURE__ */ new Set()));
16
+ }
17
+ function convertValue(value, ancestors) {
18
+ if (value === null) return value;
19
+ const type = typeof value;
20
+ if (type === "bigint") return value.toString();
21
+ if (type === "number") return Number.isFinite(value) ? value : String(value);
22
+ if (type !== "object") return value;
23
+ const obj = value;
24
+ if (ancestors.has(obj)) {
25
+ throw new TypeError("[faapi] Converting circular structure to JSON");
26
+ }
27
+ if (obj instanceof Date) return obj.getTime();
28
+ if (obj instanceof Map) {
29
+ ancestors.add(obj);
30
+ const entries = Array.from(obj, ([k, v]) => [
31
+ convertValue(k, ancestors),
32
+ convertValue(v, ancestors)
33
+ ]);
34
+ ancestors.delete(obj);
35
+ return entries;
36
+ }
37
+ if (obj instanceof Set) {
38
+ ancestors.add(obj);
39
+ const values = Array.from(obj, (v) => convertValue(v, ancestors));
40
+ ancestors.delete(obj);
41
+ return values;
42
+ }
43
+ if (obj instanceof RegExp) return obj.toString();
44
+ const toJSON = obj.toJSON;
45
+ if (typeof toJSON === "function") {
46
+ ancestors.add(obj);
47
+ const result = convertValue(toJSON.call(obj), ancestors);
48
+ ancestors.delete(obj);
49
+ return result;
50
+ }
51
+ if (Array.isArray(obj)) {
52
+ ancestors.add(obj);
53
+ const arr = obj.map((item) => convertValue(item, ancestors));
54
+ ancestors.delete(obj);
55
+ return arr;
56
+ }
57
+ if (isPlainObject(obj)) {
58
+ ancestors.add(obj);
59
+ const out = {};
60
+ for (const [key, val] of Object.entries(obj)) {
61
+ out[key] = convertValue(val, ancestors);
62
+ }
63
+ ancestors.delete(obj);
64
+ return out;
65
+ }
66
+ return obj;
67
+ }
68
+
1
69
  // src/runtime/sse.ts
2
70
  function encodeSseEvent(event) {
3
71
  let out = "";
@@ -24,7 +92,7 @@ function encodeSseEvent(event) {
24
92
  } else if (event.data === null) {
25
93
  dataStr = "null";
26
94
  } else {
27
- dataStr = JSON.stringify(event.data);
95
+ dataStr = stringifyJson(event.data);
28
96
  }
29
97
  const lines = dataStr.split("\n");
30
98
  for (const line of lines) {
@@ -55,7 +123,9 @@ function createSseWriter() {
55
123
  headers: {
56
124
  "Content-Type": "text/event-stream",
57
125
  "Cache-Control": "no-cache",
58
- Connection: "keep-alive"
126
+ Connection: "keep-alive",
127
+ // nginx 认到该头后按响应跳过 proxy_buffering;其他反代/客户端忽略,无副作用
128
+ "X-Accel-Buffering": "no"
59
129
  }
60
130
  });
61
131
  const writer = {
@@ -182,7 +252,7 @@ function jsonRaw(body, status, extraHeaders) {
182
252
  const extra = new Headers(extraHeaders);
183
253
  extra.forEach((value, key) => headers.set(key, value));
184
254
  }
185
- return new Response(JSON.stringify(body), { status, headers });
255
+ return new Response(stringifyJson(body), { status, headers });
186
256
  }
187
257
  function wrapOkResult(result, config) {
188
258
  if (result instanceof Response) return result;
@@ -541,18 +611,6 @@ function createTestContext(options) {
541
611
  return createContext(request, params, config, ip, registries);
542
612
  }
543
613
 
544
- // src/utils/isPlainObject.ts
545
- function isPlainObject(value) {
546
- if (value === null || typeof value !== "object") {
547
- return false;
548
- }
549
- if (Array.isArray(value)) {
550
- return false;
551
- }
552
- const proto = Object.getPrototypeOf(value);
553
- return proto === null || proto === Object.prototype;
554
- }
555
-
556
614
  // src/response/pendingMeta.ts
557
615
  var pending = /* @__PURE__ */ new WeakMap();
558
616
  function deferMetaHeaders(response, headers) {
@@ -632,7 +690,7 @@ async function toResponse(value, meta) {
632
690
  });
633
691
  }
634
692
  if (isPlainObject(value) || Array.isArray(value)) {
635
- const body2 = JSON.stringify(value);
693
+ const body2 = stringifyJson(value);
636
694
  const headers2 = new Headers({ "Content-Type": "application/json" });
637
695
  applyMeta(headers2);
638
696
  return new Response(body2, {
@@ -656,7 +714,7 @@ async function toResponse(value, meta) {
656
714
  headers: headers2
657
715
  });
658
716
  }
659
- const body = JSON.stringify(value);
717
+ const body = stringifyJson(value);
660
718
  const headers = new Headers({ "Content-Type": "application/json" });
661
719
  applyMeta(headers);
662
720
  return new Response(body, {
@@ -1036,9 +1094,6 @@ async function injectParamsAsync(handler, ctx, body, injectors) {
1036
1094
  function wrapResult(result, ctx) {
1037
1095
  return wrapOkResult(result, ctx.config);
1038
1096
  }
1039
- function stashHandlerResult(ctx, result) {
1040
- ctx.__faapiHandlerResult = result;
1041
- }
1042
1097
  function mergeMeta(response, meta) {
1043
1098
  const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
1044
1099
  if (!hasMeta) return response;
@@ -1107,9 +1162,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
1107
1162
  const result = await injectParamsAsync(handler, ctx, body, injectors);
1108
1163
  const sseResponse = pickSseAndAutoClose();
1109
1164
  if (sseResponse) return sseResponse;
1110
- const response = await toResponse(wrapResult(result, ctx), meta);
1111
- if (!(result instanceof Response)) stashHandlerResult(ctx, result);
1112
- return response;
1165
+ return toResponse(wrapResult(result, ctx), meta);
1113
1166
  } catch (err) {
1114
1167
  autoCloseSseOnError();
1115
1168
  throw err;
@@ -1120,9 +1173,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
1120
1173
  const result = await injectParamsAsync(handler, ctx, body, injectors);
1121
1174
  const sseResponse = pickSseAndAutoClose();
1122
1175
  if (sseResponse) return sseResponse;
1123
- const response = await toResponse(wrapResult(result, ctx), meta);
1124
- if (!(result instanceof Response)) stashHandlerResult(ctx, result);
1125
- return response;
1176
+ return toResponse(wrapResult(result, ctx), meta);
1126
1177
  } catch (err) {
1127
1178
  autoCloseSseOnError();
1128
1179
  throw err;
@@ -2695,7 +2746,7 @@ function baseExpression(type, ctx) {
2695
2746
  case "union":
2696
2747
  return generateUnionExpression(type.members, ctx);
2697
2748
  case "date":
2698
- return 'z.preprocess((v) => (typeof v === "string" ? new Date(v) : v), z.date())';
2749
+ return 'z.preprocess((v) => (typeof v === "string" || typeof v === "number" ? new Date(v) : v), z.date())';
2699
2750
  case "record":
2700
2751
  return `z.record(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)})`;
2701
2752
  case "map":
@@ -4322,7 +4373,7 @@ function buildErrorResponse(err, config) {
4322
4373
  function wrapWsSocket(rawSocket) {
4323
4374
  return {
4324
4375
  send(data) {
4325
- const payload = typeof data === "string" || Buffer.isBuffer(data) ? data : JSON.stringify(data);
4376
+ const payload = typeof data === "string" || Buffer.isBuffer(data) ? data : stringifyJson(data);
4326
4377
  rawSocket.send(payload);
4327
4378
  },
4328
4379
  close(code, reason) {
@@ -4795,7 +4846,6 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
4795
4846
  globalInjectors
4796
4847
  });
4797
4848
  const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
4798
- res.__faapiHandlerResult = ctx.__faapiHandlerResult;
4799
4849
  await sendSuccessResponse(response, res);
4800
4850
  } catch (err) {
4801
4851
  if (res.destroyed || res.writableEnded) return;