befly 3.76.1 → 3.76.2

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.
Files changed (149) hide show
  1. package/Befly.js +159 -0
  2. package/README.md +50 -11
  3. package/apis/admin/delete.js +1 -3
  4. package/apis/admin/select.js +2 -7
  5. package/apis/admin/update.js +1 -5
  6. package/apis/api/select.js +2 -7
  7. package/apis/auth/login.js +16 -20
  8. package/apis/dict/delete.js +1 -1
  9. package/apis/dict/detail.js +1 -3
  10. package/apis/dict/select.js +1 -2
  11. package/apis/dict/update.js +1 -1
  12. package/apis/dictType/delete.js +1 -1
  13. package/apis/dictType/detail.js +1 -1
  14. package/apis/dictType/insert.js +1 -1
  15. package/apis/dictType/select.js +2 -7
  16. package/apis/dictType/update.js +2 -2
  17. package/apis/email/config.js +4 -7
  18. package/apis/email/logList.js +2 -7
  19. package/apis/email/send.js +1 -2
  20. package/apis/email/verify.js +1 -3
  21. package/apis/loginLog/select.js +2 -7
  22. package/apis/menu/select.js +2 -7
  23. package/apis/operateLog/select.js +2 -7
  24. package/apis/role/apiSave.js +2 -2
  25. package/apis/role/menuSave.js +2 -2
  26. package/apis/role/select.js +2 -7
  27. package/apis/source/imageList.js +2 -5
  28. package/apis/tongJi/dailyReport.js +12 -13
  29. package/apis/tongJi/dailyReportSelect.js +1 -2
  30. package/apis/tongJi/dailyStats.js +1 -1
  31. package/apis/tongJi/dailyStatsDistribution.js +2 -2
  32. package/apis/tongJi/errorList.js +1 -2
  33. package/apis/tongJi/errorReport.js +1 -2
  34. package/apis/tongJi/projectDelete.js +1 -3
  35. package/apis/tongJi/projectSelect.js +2 -4
  36. package/apis/tongJi/projectUpdate.js +2 -2
  37. package/apis/upload/file.js +7 -5
  38. package/checks/api.js +43 -66
  39. package/checks/config.js +17 -12
  40. package/checks/corns.js +28 -46
  41. package/checks/field.js +18 -0
  42. package/checks/items.js +16 -60
  43. package/checks/menu.js +28 -39
  44. package/checks/table.js +106 -26
  45. package/checks/validation.js +13 -0
  46. package/configs/beflyConfig.json +6 -6
  47. package/configs/constConfig.js +2 -63
  48. package/exports.js +2 -0
  49. package/hooks/auth.js +11 -7
  50. package/hooks/parser.js +55 -59
  51. package/hooks/permission.js +1 -0
  52. package/hooks/rateLimit.js +25 -62
  53. package/hooks/validator.js +43 -31
  54. package/index.js +43 -344
  55. package/{lib → libs}/cacheHelper.js +3 -8
  56. package/libs/cron.js +60 -0
  57. package/libs/logger/index.js +1 -0
  58. package/libs/logger/logger.js +245 -0
  59. package/libs/logger/sanitize.js +115 -0
  60. package/{lib → libs/mysql}/dbHelper.js +100 -73
  61. package/{lib → libs/mysql}/dbParse.js +16 -9
  62. package/{lib → libs/mysql}/dbUtil.js +15 -32
  63. package/libs/mysql/error.js +29 -0
  64. package/libs/mysql/index.js +6 -0
  65. package/libs/mysql/is.js +29 -0
  66. package/libs/mysql/mysql.js +43 -0
  67. package/libs/mysql/sql/identifier.js +85 -0
  68. package/libs/mysql/sql/index.js +2 -0
  69. package/{lib → libs/mysql/sql}/sqlBuilder.js +2 -3
  70. package/libs/mysql/util.js +44 -0
  71. package/libs/redis/index.js +2 -0
  72. package/libs/redis/redis.js +204 -0
  73. package/libs/redis/redisError.js +8 -0
  74. package/libs/tableModel.js +72 -0
  75. package/libs/tool.js +33 -0
  76. package/libs/validator/compiler.js +248 -0
  77. package/libs/validator/index.js +10 -0
  78. package/libs/validator/parser.js +173 -0
  79. package/libs/validator/util.js +5 -0
  80. package/package.json +10 -8
  81. package/paths.js +2 -3
  82. package/plugins/cache.js +2 -1
  83. package/plugins/email.js +4 -4
  84. package/plugins/mysql.js +5 -15
  85. package/plugins/redis.js +4 -12
  86. package/router/api.js +14 -10
  87. package/router/root.js +14 -0
  88. package/router/static.js +3 -3
  89. package/schemas/api.json +99 -0
  90. package/schemas/config.json +255 -0
  91. package/schemas/corn.json +39 -0
  92. package/schemas/item.json +43 -0
  93. package/schemas/menu.json +44 -0
  94. package/schemas/table.json +80 -0
  95. package/scripts/syncDb/apply.js +87 -0
  96. package/scripts/syncDb/ddl.js +43 -0
  97. package/scripts/syncDb/index.js +175 -30
  98. package/scripts/syncDb/model.js +60 -0
  99. package/scripts/syncDb/plan.js +160 -0
  100. package/scripts/syncDb/preflight.js +98 -0
  101. package/scripts/syncDb/report.js +177 -162
  102. package/scripts/syncDb/snapshot.js +118 -0
  103. package/tables/admin.json +51 -53
  104. package/tables/api.json +28 -39
  105. package/tables/dailyReport.json +85 -73
  106. package/tables/dict.json +27 -39
  107. package/tables/dictType.json +20 -34
  108. package/tables/emailLog.json +49 -47
  109. package/tables/errorReport.json +86 -60
  110. package/tables/file.json +47 -50
  111. package/tables/loginLog.json +68 -55
  112. package/tables/menu.json +20 -34
  113. package/tables/operateLog.json +53 -47
  114. package/tables/project.json +18 -30
  115. package/tables/role.json +33 -37
  116. package/utils/cors.js +37 -41
  117. package/utils/deepMerge.js +3 -1
  118. package/utils/error.js +1 -56
  119. package/utils/id.js +44 -0
  120. package/utils/is.js +6 -243
  121. package/utils/response.js +12 -1
  122. package/utils/scanFiles.js +13 -4
  123. package/utils/scanSources.js +58 -20
  124. package/utils/util.js +11 -163
  125. package/apis/_apis.js +0 -20
  126. package/apis/auth/sendSmsCode.js +0 -24
  127. package/configs/regexpAlias.json +0 -55
  128. package/lib/connect.js +0 -119
  129. package/lib/logger.js +0 -430
  130. package/lib/redisHelper.js +0 -399
  131. package/lib/validator.js +0 -274
  132. package/lib/xmlParse.js +0 -149
  133. package/plugins/config.js +0 -13
  134. package/plugins/cron.js +0 -115
  135. package/plugins/logger.js +0 -17
  136. package/plugins/tool.js +0 -131
  137. package/plugins/xmlParse.js +0 -10
  138. package/schemas/config.js +0 -67
  139. package/scripts/syncDb/context.js +0 -120
  140. package/scripts/syncDb/diff.js +0 -279
  141. package/scripts/syncDb/transform.js +0 -113
  142. package/sql/befly.sql +0 -271
  143. package/utils/email.js +0 -3
  144. package/utils/fieldClear.js +0 -22
  145. package/utils/formatValidationIssues.js +0 -28
  146. package/utils/getClientIp.js +0 -47
  147. package/utils/loggerUtils.js +0 -196
  148. package/utils/regexpUtil.js +0 -52
  149. /package/{lib → libs}/smtpText.js +0 -0
package/tables/menu.json CHANGED
@@ -1,47 +1,33 @@
1
- {
2
- "id": {
3
- "name": "ID",
4
- "input": "integer",
5
- "min": 1,
6
- "max": null
7
- },
1
+ {
2
+ "$tableName": "菜单表",
8
3
  "name": {
9
4
  "name": "菜单名称",
10
- "input": "string",
11
- "min": 2,
12
- "max": 50
5
+ "minValue": 2,
6
+ "maxValue": 50,
7
+ "paramType": "string",
8
+ "fieldType": "varchar",
9
+ "fieldDefault": ""
13
10
  },
14
11
  "path": {
15
12
  "name": "路由路径",
16
- "input": "string",
17
- "min": 1,
18
- "max": 150
13
+ "minValue": 1,
14
+ "maxValue": 150,
15
+ "paramType": "string",
16
+ "fieldType": "varchar",
17
+ "fieldDefault": ""
19
18
  },
20
19
  "sort": {
21
20
  "name": "排序",
22
- "input": "number",
23
- "max": 9999
21
+ "maxValue": 9999,
22
+ "paramType": "integer",
23
+ "fieldType": "integer",
24
+ "fieldDefault": 0
24
25
  },
25
26
  "parentPath": {
26
27
  "name": "父级路径",
27
- "input": "string",
28
- "max": 200
29
- },
30
- "state": {
31
- "name": "状态(0=已删除,1=正常,2=禁用)",
32
- "input": "enumInteger",
33
- "check": "0|1|2"
34
- },
35
- "createdAt": {
36
- "name": "创建时间",
37
- "input": "number"
38
- },
39
- "updatedAt": {
40
- "name": "更新时间",
41
- "input": "number"
42
- },
43
- "deletedAt": {
44
- "name": "删除时间",
45
- "input": "number"
28
+ "maxValue": 200,
29
+ "paramType": "string",
30
+ "fieldType": "varchar",
31
+ "fieldDefault": ""
46
32
  }
47
33
  }
@@ -1,91 +1,97 @@
1
1
  {
2
- "id": {
3
- "name": "ID",
4
- "input": "integer",
5
- "min": 1,
6
- "max": null
7
- },
2
+ "$tableName": "操作日志表",
8
3
  "adminId": {
9
4
  "name": "管理员ID",
10
- "input": "number"
5
+ "paramType": "integer",
6
+ "fieldType": "integer",
7
+ "fieldDefault": 0
11
8
  },
12
9
  "username": {
13
10
  "name": "用户名",
14
- "input": "string",
15
- "min": 1,
16
- "max": 50
11
+ "minValue": 1,
12
+ "maxValue": 50,
13
+ "paramType": "string",
14
+ "fieldType": "varchar",
15
+ "fieldDefault": ""
17
16
  },
18
17
  "nickname": {
19
18
  "name": "昵称",
20
- "input": "string",
21
- "max": 50
19
+ "maxValue": 50,
20
+ "paramType": "string",
21
+ "fieldType": "varchar",
22
+ "fieldDefault": ""
22
23
  },
23
24
  "ip": {
24
25
  "name": "操作IP",
25
- "input": "string",
26
- "max": 50
26
+ "maxValue": 50,
27
+ "paramType": "string",
28
+ "fieldType": "varchar",
29
+ "fieldDefault": ""
27
30
  },
28
31
  "module": {
29
32
  "name": "操作模块",
30
- "input": "string",
31
- "max": 50
33
+ "maxValue": 50,
34
+ "paramType": "string",
35
+ "fieldType": "varchar",
36
+ "fieldDefault": ""
32
37
  },
33
38
  "action": {
34
39
  "name": "操作类型",
35
- "input": "string",
36
- "max": 50
40
+ "maxValue": 50,
41
+ "paramType": "string",
42
+ "fieldType": "varchar",
43
+ "fieldDefault": ""
37
44
  },
38
45
  "method": {
39
46
  "name": "请求方法",
40
- "input": "string",
41
- "max": 10
47
+ "maxValue": 10,
48
+ "paramType": "string",
49
+ "fieldType": "varchar",
50
+ "fieldDefault": ""
42
51
  },
43
52
  "path": {
44
53
  "name": "请求路径",
45
- "input": "string",
46
- "max": 200
54
+ "maxValue": 200,
55
+ "paramType": "string",
56
+ "fieldType": "varchar",
57
+ "fieldDefault": ""
47
58
  },
48
59
  "params": {
49
60
  "name": "请求参数",
50
- "input": "string"
61
+ "paramType": "string",
62
+ "fieldType": "text",
63
+ "fieldNullable": true
51
64
  },
52
65
  "result": {
53
66
  "name": "操作结果",
54
- "input": "number",
55
- "max": 1
67
+ "maxValue": 1,
68
+ "paramType": "integer",
69
+ "fieldType": "integer",
70
+ "fieldDefault": 0
56
71
  },
57
72
  "response": {
58
73
  "name": "响应内容",
59
- "input": "string"
74
+ "paramType": "string",
75
+ "fieldType": "text",
76
+ "fieldNullable": true
60
77
  },
61
78
  "duration": {
62
79
  "name": "耗时毫秒",
63
- "input": "number"
80
+ "paramType": "integer",
81
+ "fieldType": "integer",
82
+ "fieldDefault": 0
64
83
  },
65
84
  "operateTime": {
66
85
  "name": "操作时间",
67
- "input": "number"
86
+ "paramType": "integer",
87
+ "fieldType": "integer",
88
+ "fieldDefault": 0
68
89
  },
69
90
  "remark": {
70
91
  "name": "备注",
71
- "input": "string",
72
- "max": 500
73
- },
74
- "state": {
75
- "name": "状态(0=已删除,1=正常,2=禁用)",
76
- "input": "enumInteger",
77
- "check": "0|1|2"
78
- },
79
- "createdAt": {
80
- "name": "创建时间",
81
- "input": "number"
82
- },
83
- "updatedAt": {
84
- "name": "更新时间",
85
- "input": "number"
86
- },
87
- "deletedAt": {
88
- "name": "删除时间",
89
- "input": "number"
92
+ "maxValue": 500,
93
+ "paramType": "string",
94
+ "fieldType": "varchar",
95
+ "fieldDefault": ""
90
96
  }
91
97
  }
@@ -1,42 +1,30 @@
1
1
  {
2
- "id": {
3
- "name": "ID",
4
- "input": "integer",
5
- "min": 1,
6
- "max": null
2
+ "$tableName": "项目表",
3
+ "$tableIndexes": {
4
+ "uk_befly_project_code": "unique#code",
5
+ "idx_befly_project_state": "index#state"
7
6
  },
8
7
  "code": {
9
8
  "name": "产品代号",
10
- "input": "string",
11
- "min": 1,
12
- "max": 100
9
+ "minValue": 1,
10
+ "maxValue": 100,
11
+ "paramType": "string",
12
+ "fieldType": "varchar",
13
+ "fieldDefault": ""
13
14
  },
14
15
  "name": {
15
16
  "name": "产品名称",
16
- "input": "string",
17
- "min": 1,
18
- "max": 100
17
+ "minValue": 1,
18
+ "maxValue": 100,
19
+ "paramType": "string",
20
+ "fieldType": "varchar",
21
+ "fieldDefault": ""
19
22
  },
20
23
  "sort": {
21
24
  "name": "排序",
22
- "input": "number",
23
- "max": 9999
24
- },
25
- "state": {
26
- "name": "状态(0=已删除,1=正常,2=禁用)",
27
- "input": "enumInteger",
28
- "check": "0|1|2"
29
- },
30
- "createdAt": {
31
- "name": "创建时间",
32
- "input": "number"
33
- },
34
- "updatedAt": {
35
- "name": "更新时间",
36
- "input": "number"
37
- },
38
- "deletedAt": {
39
- "name": "删除时间",
40
- "input": "number"
25
+ "maxValue": 9999,
26
+ "paramType": "integer",
27
+ "fieldType": "integer",
28
+ "fieldDefault": 0
41
29
  }
42
30
  }
package/tables/role.json CHANGED
@@ -1,56 +1,52 @@
1
- {
2
- "id": {
3
- "name": "ID",
4
- "input": "integer",
5
- "min": 1,
6
- "max": null
7
- },
1
+ {
2
+ "$tableName": "角色表",
8
3
  "name": {
9
4
  "name": "角色名称",
10
- "input": "string",
11
- "min": 2,
12
- "max": 50
5
+ "minValue": 2,
6
+ "maxValue": 50,
7
+ "paramType": "string",
8
+ "fieldType": "varchar",
9
+ "fieldDefault": ""
13
10
  },
14
11
  "code": {
15
12
  "name": "角色编码",
16
- "input": "regexp",
17
- "check": "@alphanumericDash_",
18
- "min": 2,
19
- "max": 50
13
+ "minValue": 2,
14
+ "maxValue": 50,
15
+ "paramType": "string",
16
+ "pattern": "^[a-zA-Z0-9_-]+$",
17
+ "fieldType": "varchar",
18
+ "fieldDefault": ""
20
19
  },
21
20
  "description": {
22
21
  "name": "角色描述",
23
- "input": "string",
24
- "max": 200
22
+ "maxValue": 200,
23
+ "paramType": "string",
24
+ "fieldType": "varchar",
25
+ "fieldDefault": ""
25
26
  },
26
27
  "menus": {
27
28
  "name": "菜单权限",
28
- "input": "array"
29
+ "paramType": "array",
30
+ "items": {
31
+ "paramType": "string"
32
+ },
33
+ "fieldType": "text",
34
+ "fieldNullable": true
29
35
  },
30
36
  "apis": {
31
37
  "name": "接口权限",
32
- "input": "array"
38
+ "paramType": "array",
39
+ "items": {
40
+ "paramType": "string"
41
+ },
42
+ "fieldType": "text",
43
+ "fieldNullable": true
33
44
  },
34
45
  "sort": {
35
46
  "name": "排序",
36
- "input": "number",
37
- "max": 9999
38
- },
39
- "state": {
40
- "name": "状态(0=已删除,1=正常,2=禁用)",
41
- "input": "enumInteger",
42
- "check": "0|1|2"
43
- },
44
- "createdAt": {
45
- "name": "创建时间",
46
- "input": "number"
47
- },
48
- "updatedAt": {
49
- "name": "更新时间",
50
- "input": "number"
51
- },
52
- "deletedAt": {
53
- "name": "删除时间",
54
- "input": "number"
47
+ "maxValue": 9999,
48
+ "paramType": "integer",
49
+ "fieldType": "integer",
50
+ "fieldDefault": 0
55
51
  }
56
52
  }
package/utils/cors.js CHANGED
@@ -1,52 +1,48 @@
1
+ const originMatcherCache = new WeakMap();
2
+
3
+ function getOriginMatchers(config) {
4
+ if (originMatcherCache.has(config)) return originMatcherCache.get(config);
5
+ const matchers = config.origins?.map((pattern) => new Bun.Glob(pattern));
6
+ originMatcherCache.set(config, matchers);
7
+ return matchers;
8
+ }
9
+
10
+ function splitHeaderList(value) {
11
+ return value
12
+ .split(",")
13
+ .map((item) => item.trim())
14
+ .filter(Boolean);
15
+ }
16
+
1
17
  /**
2
- * 设置 CORS 响应头
3
- * @param req - 请求对象
4
- * @param config - CORS 配置
5
- * @returns CORS 响应头对象
18
+ * origins 省略时回显任意 Origin;配置后使用 Bun.Glob 匹配 Origin。
19
+ * Origin VS Code、小程序、App、服务端请求不受浏览器 CORS 限制。
6
20
  */
7
21
  export function setCorsOptions(req, config) {
8
22
  const origin = req.headers.get("origin");
9
- const allowCredentials = config.credentials === true || config.credentials === "true" ? "true" : "false";
10
- const requestedHeaders = req.headers.get("Access-Control-Request-Headers") || "";
11
-
12
- const allowedHeaderItems = [];
13
- const allowedHeaderMap = Object.create(null);
14
-
15
- const pushHeaderIfNeeded = (rawValue) => {
16
- if (typeof rawValue !== "string") {
17
- return;
18
- }
19
-
20
- const value = rawValue.trim();
21
- if (value.length < 1) {
22
- return;
23
- }
24
-
25
- const key = value.toLowerCase();
26
- if (allowedHeaderMap[key]) {
27
- return;
28
- }
29
-
30
- allowedHeaderMap[key] = 1;
31
- allowedHeaderItems.push(value);
32
- };
33
-
34
- for (const item of String(config.allowedHeaders || "").split(",")) {
35
- pushHeaderIfNeeded(item);
23
+ if (!origin) return {};
24
+
25
+ const originMatchers = getOriginMatchers(config);
26
+ const unrestricted = originMatchers === undefined;
27
+ if (!unrestricted && !originMatchers.some((matcher) => matcher.match(origin))) return { Vary: "Origin" };
28
+
29
+ const requestedHeaders = splitHeaderList(req.headers.get("access-control-request-headers") || "");
30
+ let responseHeaders;
31
+ if (unrestricted) {
32
+ responseHeaders = requestedHeaders.length > 0 ? requestedHeaders : splitHeaderList(config.allowedHeaders);
33
+ } else {
34
+ const allowedHeaders = splitHeaderList(config.allowedHeaders);
35
+ const allowedHeaderSet = new Set(allowedHeaders.map((item) => item.toLowerCase()));
36
+ responseHeaders = requestedHeaders.length > 0 ? requestedHeaders.filter((item) => allowedHeaderSet.has(item.toLowerCase())) : allowedHeaders;
36
37
  }
37
38
 
38
- for (const item of String(requestedHeaders).split(",")) {
39
- pushHeaderIfNeeded(item);
40
- }
41
-
42
- const allowHeaders = allowedHeaderItems.join(", ");
43
-
44
39
  return {
45
- ...(origin ? { "Access-Control-Allow-Origin": origin, Vary: "Origin" } : {}),
46
- "Access-Control-Allow-Methods": config.methods,
47
- "Access-Control-Allow-Headers": allowHeaders,
40
+ "Access-Control-Allow-Origin": origin,
41
+ Vary: "Origin",
42
+ "Access-Control-Allow-Methods": "GET,POST",
43
+ "Access-Control-Allow-Headers": responseHeaders.join(", "),
48
44
  "Access-Control-Expose-Headers": config.exposedHeaders,
49
45
  "Access-Control-Max-Age": String(config.maxAge),
50
- "Access-Control-Allow-Credentials": allowCredentials
46
+ ...(config.credentials ? { "Access-Control-Allow-Credentials": "true" } : {})
51
47
  };
52
48
  }
@@ -22,7 +22,9 @@ function concat(targetArr, sourceArr, cloneTargetItems) {
22
22
  nextArr.push(clone(item));
23
23
  }
24
24
 
25
- return nextArr;
25
+ const merged = nextArr;
26
+ if (merged.every((item) => item === null || ["string", "number", "boolean"].includes(typeof item))) return [...new Set(merged)];
27
+ return merged;
26
28
  }
27
29
 
28
30
  function mergeValue(baseValue, incomingValue) {
package/utils/error.js CHANGED
@@ -1,64 +1,9 @@
1
- /**
2
- * 统一错误工厂
3
- * 框架错误对象的唯一构造入口,统一字段约定:
4
- * - code: "validation" | "runtime" | "policy"
5
- * - subsystem: 子系统标识(sql/cache/redis/sync/config/start/...)
6
- * - operation: 操作名
7
- * - 其余字段原样透传(如 table、roleCode、runMode 等)
8
- * - 不传 cause 时不设置(缺省为 undefined,避免 cause: null 噪音)
9
- */
10
1
  export function createError(message, options = {}) {
11
2
  const error = new Error(message, options.cause === undefined ? undefined : { cause: options.cause });
12
3
 
13
- if (options.code) error.code = options.code;
14
- if (options.subsystem) error.subsystem = options.subsystem;
15
- if (options.operation) error.operation = options.operation;
16
-
17
4
  for (const [key, value] of Object.entries(options)) {
18
- if (key === "cause" || key === "code" || key === "subsystem" || key === "operation") {
19
- continue;
20
- }
21
- error[key] = value;
5
+ if (key !== "cause") error[key] = value;
22
6
  }
23
7
 
24
8
  return error;
25
9
  }
26
-
27
- /**
28
- * 参数校验错误(code: "validation")
29
- */
30
- export function validationError(message) {
31
- return createError(message, { code: "validation" });
32
- }
33
-
34
- /**
35
- * 沿 cause 链取最底层错误(根因)
36
- */
37
- export function getRootCause(error) {
38
- let current = error;
39
- while (current?.cause instanceof Error) {
40
- current = current.cause;
41
- }
42
- return current;
43
- }
44
-
45
- /**
46
- * 错误链摘要:沿 cause 展开为单行文本,根因在后
47
- * 每层格式:message [code/subsystem/operation](无元信息时仅 message)
48
- * 例:SQL执行失败 [runtime/sql/execute] <- ER_DUP_ENTRY: 重复插入 [runtime]
49
- */
50
- export function errorChainSummary(error) {
51
- const parts = [];
52
- const visited = new Set();
53
- let current = error;
54
-
55
- while (current instanceof Error && !visited.has(current)) {
56
- visited.add(current);
57
- const meta = [current.code, current.subsystem, current.operation].filter(Boolean).join("/");
58
- const text = current.message || current.name || "Error";
59
- parts.push(meta ? `${text} [${meta}]` : text);
60
- current = current.cause;
61
- }
62
-
63
- return parts.join(" <- ");
64
- }
package/utils/id.js ADDED
@@ -0,0 +1,44 @@
1
+ function resolveInstanceId(value) {
2
+ const instanceId = value === undefined || value === "" ? 0 : Number(value);
3
+ if (!Number.isInteger(instanceId) || instanceId < 0 || instanceId > 9) {
4
+ throw new TypeError(`BM2_INSTANCE_ID 必须是 0 到 9 的整数,当前值: ${String(value)}`);
5
+ }
6
+ return instanceId;
7
+ }
8
+
9
+ /**
10
+ * 创建 13 位逻辑毫秒 + 1 位 BM2 实例编号 + 2 位序列的安全整数 ID 生成器。
11
+ */
12
+ export function createIdGenerator(options = {}) {
13
+ const instanceId = resolveInstanceId(options.instanceId ?? Bun.env.BM2_INSTANCE_ID);
14
+ const now = options.now || Date.now;
15
+ let lastTimestamp = 0;
16
+ let sequence = 0;
17
+
18
+ return function generateId() {
19
+ const currentTimestamp = now();
20
+ if (!Number.isInteger(currentTimestamp) || currentTimestamp <= 0) {
21
+ throw new TypeError(`当前时间戳必须是正整数,当前值: ${String(currentTimestamp)}`);
22
+ }
23
+
24
+ if (currentTimestamp > lastTimestamp) {
25
+ lastTimestamp = currentTimestamp;
26
+ sequence = 0;
27
+ } else {
28
+ sequence += 1;
29
+ if (sequence > 99) {
30
+ lastTimestamp += 1;
31
+ sequence = 0;
32
+ }
33
+ }
34
+
35
+ const id = lastTimestamp * 1000 + instanceId * 100 + sequence;
36
+ if (!Number.isSafeInteger(id)) throw new RangeError(`生成的 ID 超出 JavaScript 安全整数范围: ${id}`);
37
+ return id;
38
+ };
39
+ }
40
+
41
+ /**
42
+ * 使用当前 Bun 进程的 BM2_INSTANCE_ID 生成全局唯一的安全整数 ID。
43
+ */
44
+ export const genId = createIdGenerator();