befly 3.17.10 → 3.17.12

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/hooks/auth.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { toSessionTtlSeconds } from "../utils/toSessionTtlSeconds.js";
2
2
 
3
3
  export default {
4
- deps: ["cors"],
4
+ deps: [],
5
5
  handler: async (befly, ctx) => {
6
6
  const authHeader = ctx.req.headers.get("authorization");
7
7
  const ttlSeconds = toSessionTtlSeconds(befly.config?.session?.expireDays);
@@ -19,10 +19,18 @@ export const executeMethods = {
19
19
 
20
20
  try {
21
21
  let queryResult;
22
- if (safeParams.length > 0) {
23
- queryResult = await this.sql.unsafe(sql, safeParams);
24
- } else {
25
- queryResult = await this.sql.unsafe(sql);
22
+ // Bun 事件循环 bug workaround:
23
+ // 当事件循环中只有一个 I/O 源(SQL socket)时,Bun 会走单源快速路径,
24
+ // I/O 回调内直接唤醒 JS Promise,跳过正常调度,导致 SQL 连接状态
25
+ // 未完全重置,下一条查询的 Promise resolver 永久丢失(远程 TCP 连接必现)。
26
+ // 预注册一个 timer 使事件循环中同时存在两种事件源,强制 Bun 走正常多源调度路径。
27
+ // timer 时长无关紧要,查询完成后会被 clearTimeout 清理。
28
+ const _timer = setTimeout(() => {}, 2147483647);
29
+ const queryPromise = safeParams.length > 0 ? this.sql.unsafe(sql, safeParams) : this.sql.unsafe(sql);
30
+ try {
31
+ queryResult = await queryPromise;
32
+ } finally {
33
+ clearTimeout(_timer);
26
34
  }
27
35
 
28
36
  const duration = Date.now() - startTime;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.17.10",
4
- "gitHead": "7f5250a2de8ff14fa0c838bdce4bac269c6eccf4",
3
+ "version": "3.17.12",
4
+ "gitHead": "098baa90ed47149819a515c6169b29072e4840bb",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
7
7
  "keywords": [
package/router/api.js CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { Logger } from "../lib/logger.js";
7
7
  // 相对导入
8
+ import { setCorsOptions } from "../utils/cors.js";
8
9
  import { getClientIp } from "../utils/getClientIp.js";
9
10
  import { FinalResponse } from "../utils/response.js";
10
11
  import { genShortId } from "../utils/util.js";
@@ -20,16 +21,39 @@ export function apiHandler(apis, hooks, context) {
20
21
  // 1. 生成请求 ID
21
22
  const requestId = genShortId();
22
23
 
23
- // 2. 创建请求上下文
24
+ const corsHeaders = setCorsOptions(req, context.config?.cors || {});
25
+ corsHeaders["X-Request-ID"] = requestId;
26
+
27
+ // 2. OPTIONS 预检请求:直接返回,不走 hooks,不打日志
28
+ if (req.method === "OPTIONS") {
29
+ return new Response(null, {
30
+ status: 204,
31
+ headers: corsHeaders
32
+ });
33
+ }
34
+
35
+ // 3. 创建请求上下文
24
36
  const url = new URL(req.url);
25
37
  // 只用接口路径做存在性判断与匹配:不要把 method 拼进 key
26
38
  // 说明:apisMap 的 key 来源于 scanFiles/loadApis 生成的 path(例如 /api/core/xxx)
27
39
 
28
40
  const clientIp = getClientIp(req, server);
29
41
 
30
- const now = Date.now();
42
+ const apiData = apis[url.pathname];
43
+ if (!apiData) {
44
+ return Response.json(
45
+ {
46
+ code: 1,
47
+ msg: "接口不存在"
48
+ },
49
+ {
50
+ headers: corsHeaders
51
+ }
52
+ );
53
+ }
31
54
 
32
55
  const ctx = {
56
+ // 请求的参数
33
57
  method: req.method,
34
58
  body: {},
35
59
  req: req,
@@ -37,24 +61,19 @@ export function apiHandler(apis, hooks, context) {
37
61
  ip: clientIp,
38
62
  headers: req.headers,
39
63
  requestId: requestId,
40
- corsHeaders: {
41
- "X-Request-ID": requestId
42
- }
64
+ corsHeaders: corsHeaders,
65
+ // 接口的参数
66
+ apiPath: apiData.apiPath,
67
+ apiName: apiData.name,
68
+ filePath: apiData.filePath,
69
+ handler: apiData.handler,
70
+ method: apiData.method,
71
+ body: apiData.body,
72
+ auth: apiData.auth,
73
+ fields: apiData.fields,
74
+ required: apiData.required
43
75
  };
44
76
 
45
- const apiData = apis[url.pathname];
46
- if (apiData) {
47
- ctx.apiPath = apiData.apiPath;
48
- ctx.apiName = apiData.name;
49
- ctx.filePath = apiData.filePath;
50
- ctx.handler = apiData.handler;
51
- ctx.method = apiData.method;
52
- ctx.body = apiData.body;
53
- ctx.auth = apiData.auth;
54
- ctx.fields = apiData.fields;
55
- ctx.required = apiData.required;
56
- }
57
-
58
77
  try {
59
78
  // 4. 串联执行所有钩子
60
79
  for (const hook of hooks) {
@@ -67,49 +86,33 @@ export function apiHandler(apis, hooks, context) {
67
86
  }
68
87
 
69
88
  // hooks 全部通过后记录请求日志(拦截请求仅由 ErrorResponse 记录)
70
- if (ctx.handler && req.method !== "OPTIONS") {
71
- const logData = {
72
- event: "request",
73
- requestId: requestId,
74
- method: req.method,
75
- apiPath: ctx.apiPath,
76
- apiName: ctx.apiName,
77
- ip: clientIp,
78
- now: now,
79
- userId: ctx.userId,
80
- nickname: ctx.nickname,
81
- roleCode: ctx.roleCode,
82
- roleType: ctx.roleType
83
- };
84
-
85
- if (ctx.body && Object.keys(ctx.body).length > 0) {
86
- logData["body"] = ctx.body;
87
- }
89
+ const logData = {
90
+ event: "request",
91
+ requestId: requestId,
92
+ method: req.method,
93
+ apiPath: ctx.apiPath,
94
+ apiName: ctx.apiName,
95
+ ip: clientIp,
96
+ now: now,
97
+ userId: ctx.userId,
98
+ nickname: ctx.nickname,
99
+ roleCode: ctx.roleCode,
100
+ roleType: ctx.roleType
101
+ };
88
102
 
89
- Logger.info("请求", logData);
103
+ if (ctx.body && Object.keys(ctx.body).length > 0) {
104
+ logData["body"] = ctx.body;
90
105
  }
91
106
 
107
+ Logger.info("请求", logData);
108
+
92
109
  // 5. 执行 API handler
93
- if (!ctx.handler) {
94
- if (req.method !== "OPTIONS") {
95
- ctx.response = Response.json(
96
- {
97
- code: 1,
98
- msg: "接口不存在"
99
- },
100
- {
101
- headers: ctx.corsHeaders
102
- }
103
- );
104
- }
105
- } else if (ctx.handler) {
106
- const result = await ctx.handler(context, ctx);
110
+ const result = await ctx.handler(context, ctx);
107
111
 
108
- if (result instanceof Response) {
109
- ctx.response = result;
110
- } else {
111
- ctx.result = result;
112
- }
112
+ if (result instanceof Response) {
113
+ ctx.response = result;
114
+ } else {
115
+ ctx.result = result;
113
116
  }
114
117
 
115
118
  // 7. 返回响应(自动处理 response/result/日志)
package/utils/cors.js CHANGED
@@ -5,13 +5,24 @@
5
5
  * @returns CORS 响应头对象
6
6
  */
7
7
  export function setCorsOptions(req, config = {}) {
8
- const origin = config.origin || "*";
8
+ const defaultConfig = {
9
+ origin: "*",
10
+ methods: "GET, POST, OPTIONS",
11
+ allowedHeaders: "Content-Type, Authorization, authorization, token",
12
+ exposedHeaders: "Content-Range, X-Content-Range, Authorization, authorization, token",
13
+ maxAge: 86400,
14
+ credentials: "true"
15
+ };
16
+
17
+ const merged = Object.assign({}, defaultConfig, config || {});
18
+ const origin = merged.origin;
19
+
9
20
  return {
10
21
  "Access-Control-Allow-Origin": origin === "*" ? req.headers.get("origin") || "*" : origin,
11
- "Access-Control-Allow-Methods": config.methods || "GET, POST, PUT, DELETE, OPTIONS",
12
- "Access-Control-Allow-Headers": config.allowedHeaders || "Content-Type, Authorization, authorization, token",
13
- "Access-Control-Expose-Headers": config.exposedHeaders || "Content-Range, X-Content-Range, Authorization, authorization, token",
14
- "Access-Control-Max-Age": String(config.maxAge || 86400),
15
- "Access-Control-Allow-Credentials": config.credentials || "true"
22
+ "Access-Control-Allow-Methods": merged.methods,
23
+ "Access-Control-Allow-Headers": merged.allowedHeaders,
24
+ "Access-Control-Expose-Headers": merged.exposedHeaders,
25
+ "Access-Control-Max-Age": String(merged.maxAge),
26
+ "Access-Control-Allow-Credentials": merged.credentials
16
27
  };
17
28
  }
package/utils/response.js CHANGED
@@ -9,17 +9,17 @@ import { isString } from "./is.js";
9
9
  * @param code - 错误码,默认 1
10
10
  * @param data - 附加数据,默认 null
11
11
  * @param detail - 详细信息,用于标记具体提示位置,默认 null
12
- * @param reasonCode - 拦截原因标识(用于统计/聚合),默认 null
12
+ * @param reason - 拦截原因标识(用于统计/聚合),默认 null
13
13
  * @returns Response 对象
14
14
  */
15
- export function ErrorResponse(ctx, msg, code = 1, data = null, detail = null, reasonCode = null) {
15
+ export function ErrorResponse(ctx, msg, code = 1, data = null, detail = null, reason = null) {
16
16
  // 记录拦截日志
17
17
  if (ctx.requestId) {
18
18
  // requestId/apiPath/user/duration 等字段由调用方显式写入日志上下文,避免在 msg 中重复拼接
19
19
  Logger.info("请求已拦截", {
20
20
  event: "request_blocked",
21
- reason: msg,
22
- reasonCode: reasonCode,
21
+ msg: msg,
22
+ reason: reason,
23
23
  code: code,
24
24
  detail: detail
25
25
  });
@@ -30,7 +30,8 @@ export function ErrorResponse(ctx, msg, code = 1, data = null, detail = null, re
30
30
  code: code,
31
31
  msg: msg,
32
32
  data: data,
33
- detail: detail
33
+ detail: detail,
34
+ reason: reason
34
35
  },
35
36
  {
36
37
  headers: ctx.corsHeaders
package/hooks/cors.js DELETED
@@ -1,39 +0,0 @@
1
- // 相对导入
2
- import { setCorsOptions } from "../utils/cors.js";
3
-
4
- /**
5
- * CORS 跨域处理钩子
6
- * 设置跨域响应头并处理 OPTIONS 预检请求
7
- */
8
- export default {
9
- deps: [],
10
- handler: async (befly, ctx) => {
11
- const req = ctx.req;
12
-
13
- // 合并默认配置和用户配置
14
- const defaultConfig = {
15
- origin: "*",
16
- methods: "GET, POST, OPTIONS",
17
- allowedHeaders: "Content-Type, Authorization, authorization, token",
18
- exposedHeaders: "Content-Range, X-Content-Range, Authorization, authorization, token",
19
- maxAge: 86400,
20
- credentials: "true"
21
- };
22
-
23
- const corsConfig = Object.assign({}, defaultConfig, befly.config && befly.config.cors ? befly.config.cors : {});
24
-
25
- // 设置 CORS 响应头
26
- const headers = setCorsOptions(req, corsConfig);
27
-
28
- ctx.corsHeaders = headers;
29
-
30
- // 处理 OPTIONS 预检请求
31
- if (req.method === "OPTIONS") {
32
- ctx.response = new Response(null, {
33
- status: 204,
34
- headers: headers
35
- });
36
- return;
37
- }
38
- }
39
- };