@faapi/faapi 6.10.1 → 6.12.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,6 +1622,11 @@ interface InjectOptions {
1622
1622
  interface InjectResponse {
1623
1623
  status: number;
1624
1624
  headers: Headers;
1625
+ /**
1626
+ * 响应字节按 JSON 反序列化的结果(非 JSON 回退字符串)——HTTP 语义,与真实客户端一致。
1627
+ * 序列化契约(Date → 毫秒时间戳、BigInt → 字符串等,JSON 原生类型之外全部转换)
1628
+ * 见 `src/utils/stringifyJson.md`,与真实 HTTP 响应完全相同
1629
+ */
1625
1630
  body: unknown;
1626
1631
  }
1627
1632
  /**
@@ -1682,7 +1687,8 @@ interface AppBase {
1682
1687
  * 无服务器测试注入
1683
1688
  *
1684
1689
  * 构建一个模拟请求直接走完整请求链路(CORS / helmet / logger / 全局中间件 / 路由匹配 /
1685
- * schema 校验 / 目录中间件 / handler),不绑定端口,返回已解析的 `{ status, headers, body }`。
1690
+ * schema 校验 / 目录中间件 / handler),不绑定端口,返回已解析的 `{ status, headers, body }`
1691
+ * ——body 为 JSON 反序列化结果,与真实 HTTP 客户端拿到的一致(同一序列化契约)。
1686
1692
  *
1687
1693
  * `listen()` 前后均可调用——`listen()` 后调用常用于 Next.js Server Component 等同进程场景
1688
1694
  * (配合 `getApp()` 拿到 app 实例)。
package/dist/index.js CHANGED
@@ -1388,7 +1388,7 @@ function baseExpression(type, ctx) {
1388
1388
  case "union":
1389
1389
  return generateUnionExpression(type.members, ctx);
1390
1390
  case "date":
1391
- return 'z.preprocess((v) => (typeof v === "string" ? new Date(v) : v), z.date())';
1391
+ return 'z.preprocess((v) => (typeof v === "string" || typeof v === "number" ? new Date(v) : v), z.date())';
1392
1392
  case "record":
1393
1393
  return `z.record(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)})`;
1394
1394
  case "map":
@@ -4508,6 +4508,74 @@ async function loadRouteModule(filePath, method, rootDir) {
4508
4508
  return { handler, method };
4509
4509
  }
4510
4510
 
4511
+ // src/utils/isPlainObject.ts
4512
+ function isPlainObject(value) {
4513
+ if (value === null || typeof value !== "object") {
4514
+ return false;
4515
+ }
4516
+ if (Array.isArray(value)) {
4517
+ return false;
4518
+ }
4519
+ const proto = Object.getPrototypeOf(value);
4520
+ return proto === null || proto === Object.prototype;
4521
+ }
4522
+
4523
+ // src/utils/stringifyJson.ts
4524
+ function stringifyJson(value) {
4525
+ return JSON.stringify(convertValue(value, /* @__PURE__ */ new Set()));
4526
+ }
4527
+ function convertValue(value, ancestors) {
4528
+ if (value === null) return value;
4529
+ const type = typeof value;
4530
+ if (type === "bigint") return value.toString();
4531
+ if (type === "number") return Number.isFinite(value) ? value : String(value);
4532
+ if (type !== "object") return value;
4533
+ const obj = value;
4534
+ if (ancestors.has(obj)) {
4535
+ throw new TypeError("[faapi] Converting circular structure to JSON");
4536
+ }
4537
+ if (obj instanceof Date) return obj.getTime();
4538
+ if (obj instanceof Map) {
4539
+ ancestors.add(obj);
4540
+ const entries = Array.from(obj, ([k, v]) => [
4541
+ convertValue(k, ancestors),
4542
+ convertValue(v, ancestors)
4543
+ ]);
4544
+ ancestors.delete(obj);
4545
+ return entries;
4546
+ }
4547
+ if (obj instanceof Set) {
4548
+ ancestors.add(obj);
4549
+ const values = Array.from(obj, (v) => convertValue(v, ancestors));
4550
+ ancestors.delete(obj);
4551
+ return values;
4552
+ }
4553
+ if (obj instanceof RegExp) return obj.toString();
4554
+ const toJSON = obj.toJSON;
4555
+ if (typeof toJSON === "function") {
4556
+ ancestors.add(obj);
4557
+ const result = convertValue(toJSON.call(obj), ancestors);
4558
+ ancestors.delete(obj);
4559
+ return result;
4560
+ }
4561
+ if (Array.isArray(obj)) {
4562
+ ancestors.add(obj);
4563
+ const arr = obj.map((item) => convertValue(item, ancestors));
4564
+ ancestors.delete(obj);
4565
+ return arr;
4566
+ }
4567
+ if (isPlainObject(obj)) {
4568
+ ancestors.add(obj);
4569
+ const out = {};
4570
+ for (const [key, val] of Object.entries(obj)) {
4571
+ out[key] = convertValue(val, ancestors);
4572
+ }
4573
+ ancestors.delete(obj);
4574
+ return out;
4575
+ }
4576
+ return obj;
4577
+ }
4578
+
4511
4579
  // src/runtime/sse.ts
4512
4580
  function encodeSseEvent(event) {
4513
4581
  let out = "";
@@ -4534,7 +4602,7 @@ function encodeSseEvent(event) {
4534
4602
  } else if (event.data === null) {
4535
4603
  dataStr = "null";
4536
4604
  } else {
4537
- dataStr = JSON.stringify(event.data);
4605
+ dataStr = stringifyJson(event.data);
4538
4606
  }
4539
4607
  const lines = dataStr.split("\n");
4540
4608
  for (const line of lines) {
@@ -4640,7 +4708,7 @@ function jsonRaw(body, status, extraHeaders) {
4640
4708
  const extra = new Headers(extraHeaders);
4641
4709
  extra.forEach((value, key) => headers.set(key, value));
4642
4710
  }
4643
- return new Response(JSON.stringify(body), { status, headers });
4711
+ return new Response(stringifyJson(body), { status, headers });
4644
4712
  }
4645
4713
  function wrapOkResult(result, config) {
4646
4714
  if (result instanceof Response) return result;
@@ -4966,18 +5034,6 @@ async function resolveBodyForQueryMethod(request) {
4966
5034
  return result.data;
4967
5035
  }
4968
5036
 
4969
- // src/utils/isPlainObject.ts
4970
- function isPlainObject(value) {
4971
- if (value === null || typeof value !== "object") {
4972
- return false;
4973
- }
4974
- if (Array.isArray(value)) {
4975
- return false;
4976
- }
4977
- const proto = Object.getPrototypeOf(value);
4978
- return proto === null || proto === Object.prototype;
4979
- }
4980
-
4981
5037
  // src/response/pendingMeta.ts
4982
5038
  var pending = /* @__PURE__ */ new WeakMap();
4983
5039
  function deferMetaHeaders(response, headers) {
@@ -5057,7 +5113,7 @@ async function toResponse(value, meta) {
5057
5113
  });
5058
5114
  }
5059
5115
  if (isPlainObject(value) || Array.isArray(value)) {
5060
- const body2 = JSON.stringify(value);
5116
+ const body2 = stringifyJson(value);
5061
5117
  const headers2 = new Headers({ "Content-Type": "application/json" });
5062
5118
  applyMeta(headers2);
5063
5119
  return new Response(body2, {
@@ -5081,7 +5137,7 @@ async function toResponse(value, meta) {
5081
5137
  headers: headers2
5082
5138
  });
5083
5139
  }
5084
- const body = JSON.stringify(value);
5140
+ const body = stringifyJson(value);
5085
5141
  const headers = new Headers({ "Content-Type": "application/json" });
5086
5142
  applyMeta(headers);
5087
5143
  return new Response(body, {
@@ -5679,7 +5735,7 @@ async function loadMergedMiddlewares(middlewarePaths) {
5679
5735
  function wrapWsSocket(rawSocket) {
5680
5736
  return {
5681
5737
  send(data) {
5682
- const payload = typeof data === "string" || Buffer.isBuffer(data) ? data : JSON.stringify(data);
5738
+ const payload = typeof data === "string" || Buffer.isBuffer(data) ? data : stringifyJson(data);
5683
5739
  rawSocket.send(payload);
5684
5740
  },
5685
5741
  close(code, reason) {