befly 3.9.37 → 3.9.39

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 (155) hide show
  1. package/README.md +38 -39
  2. package/befly.config.ts +62 -40
  3. package/checks/checkApi.ts +16 -16
  4. package/checks/checkApp.ts +19 -25
  5. package/checks/checkTable.ts +42 -42
  6. package/docs/README.md +42 -35
  7. package/docs/{api.md → api/api.md} +225 -235
  8. package/docs/cipher.md +71 -69
  9. package/docs/database.md +155 -153
  10. package/docs/{examples.md → guide/examples.md} +181 -181
  11. package/docs/guide/quickstart.md +331 -0
  12. package/docs/hooks/auth.md +38 -0
  13. package/docs/hooks/cors.md +28 -0
  14. package/docs/{hook.md → hooks/hook.md} +140 -57
  15. package/docs/hooks/parser.md +19 -0
  16. package/docs/hooks/rateLimit.md +47 -0
  17. package/docs/{redis.md → infra/redis.md} +84 -93
  18. package/docs/plugins/cipher.md +61 -0
  19. package/docs/plugins/database.md +128 -0
  20. package/docs/{plugin.md → plugins/plugin.md} +83 -81
  21. package/docs/quickstart.md +26 -26
  22. package/docs/{addon.md → reference/addon.md} +46 -46
  23. package/docs/{config.md → reference/config.md} +32 -80
  24. package/docs/{logger.md → reference/logger.md} +52 -52
  25. package/docs/{sync.md → reference/sync.md} +32 -35
  26. package/docs/{table.md → reference/table.md} +7 -7
  27. package/docs/{validator.md → reference/validator.md} +57 -57
  28. package/hooks/auth.ts +8 -4
  29. package/hooks/cors.ts +13 -13
  30. package/hooks/parser.ts +37 -17
  31. package/hooks/permission.ts +26 -14
  32. package/hooks/rateLimit.ts +276 -0
  33. package/hooks/validator.ts +15 -7
  34. package/lib/asyncContext.ts +43 -0
  35. package/lib/cacheHelper.ts +212 -81
  36. package/lib/cacheKeys.ts +38 -0
  37. package/lib/cipher.ts +30 -30
  38. package/lib/connect.ts +28 -28
  39. package/lib/dbHelper.ts +211 -109
  40. package/lib/jwt.ts +16 -16
  41. package/lib/logger.ts +610 -19
  42. package/lib/redisHelper.ts +185 -44
  43. package/lib/sqlBuilder.ts +90 -91
  44. package/lib/validator.ts +59 -39
  45. package/loader/loadApis.ts +53 -47
  46. package/loader/loadHooks.ts +40 -14
  47. package/loader/loadPlugins.ts +16 -17
  48. package/main.ts +57 -47
  49. package/package.json +47 -45
  50. package/paths.ts +15 -14
  51. package/plugins/cache.ts +5 -4
  52. package/plugins/cipher.ts +3 -3
  53. package/plugins/config.ts +2 -2
  54. package/plugins/db.ts +9 -9
  55. package/plugins/jwt.ts +3 -3
  56. package/plugins/logger.ts +8 -12
  57. package/plugins/redis.ts +8 -8
  58. package/plugins/tool.ts +6 -6
  59. package/router/api.ts +85 -56
  60. package/router/static.ts +12 -12
  61. package/sync/syncAll.ts +12 -12
  62. package/sync/syncApi.ts +55 -54
  63. package/sync/syncDb/apply.ts +20 -19
  64. package/sync/syncDb/constants.ts +25 -23
  65. package/sync/syncDb/ddl.ts +35 -36
  66. package/sync/syncDb/helpers.ts +6 -9
  67. package/sync/syncDb/schema.ts +10 -9
  68. package/sync/syncDb/sqlite.ts +7 -8
  69. package/sync/syncDb/table.ts +37 -35
  70. package/sync/syncDb/tableCreate.ts +21 -20
  71. package/sync/syncDb/types.ts +23 -20
  72. package/sync/syncDb/version.ts +10 -10
  73. package/sync/syncDb.ts +43 -36
  74. package/sync/syncDev.ts +74 -66
  75. package/sync/syncMenu.ts +190 -57
  76. package/tests/api-integration-array-number.test.ts +282 -0
  77. package/tests/befly-config-env.test.ts +78 -0
  78. package/tests/cacheHelper.test.ts +135 -104
  79. package/tests/cacheKeys.test.ts +41 -0
  80. package/tests/cipher.test.ts +90 -89
  81. package/tests/dbHelper-advanced.test.ts +140 -134
  82. package/tests/dbHelper-all-array-types.test.ts +316 -0
  83. package/tests/dbHelper-array-serialization.test.ts +258 -0
  84. package/tests/dbHelper-columns.test.ts +56 -55
  85. package/tests/dbHelper-execute.test.ts +45 -44
  86. package/tests/dbHelper-joins.test.ts +124 -119
  87. package/tests/fields-redis-cache.test.ts +29 -27
  88. package/tests/fields-validate.test.ts +38 -38
  89. package/tests/getClientIp.test.ts +54 -0
  90. package/tests/integration.test.ts +69 -67
  91. package/tests/jwt.test.ts +27 -26
  92. package/tests/logger.test.ts +267 -34
  93. package/tests/rateLimit-hook.test.ts +477 -0
  94. package/tests/redisHelper.test.ts +187 -188
  95. package/tests/redisKeys.test.ts +6 -73
  96. package/tests/scanConfig.test.ts +144 -0
  97. package/tests/sqlBuilder-advanced.test.ts +217 -215
  98. package/tests/sqlBuilder.test.ts +92 -91
  99. package/tests/sync-connection.test.ts +29 -29
  100. package/tests/syncDb-apply.test.ts +97 -96
  101. package/tests/syncDb-array-number.test.ts +160 -0
  102. package/tests/syncDb-constants.test.ts +48 -47
  103. package/tests/syncDb-ddl.test.ts +99 -98
  104. package/tests/syncDb-helpers.test.ts +29 -28
  105. package/tests/syncDb-schema.test.ts +61 -60
  106. package/tests/syncDb-types.test.ts +60 -59
  107. package/tests/syncMenu-paths.test.ts +68 -0
  108. package/tests/util.test.ts +42 -41
  109. package/tests/validator-array-number.test.ts +310 -0
  110. package/tests/validator-default.test.ts +373 -0
  111. package/tests/validator.test.ts +271 -266
  112. package/tsconfig.json +4 -5
  113. package/types/api.d.ts +7 -12
  114. package/types/befly.d.ts +60 -13
  115. package/types/cache.d.ts +8 -4
  116. package/types/common.d.ts +17 -9
  117. package/types/context.d.ts +2 -2
  118. package/types/crypto.d.ts +23 -0
  119. package/types/database.d.ts +19 -19
  120. package/types/hook.d.ts +2 -2
  121. package/types/jwt.d.ts +118 -0
  122. package/types/logger.d.ts +30 -0
  123. package/types/plugin.d.ts +4 -4
  124. package/types/redis.d.ts +7 -3
  125. package/types/roleApisCache.ts +23 -0
  126. package/types/sync.d.ts +10 -10
  127. package/types/table.d.ts +50 -9
  128. package/types/validate.d.ts +69 -0
  129. package/utils/addonHelper.ts +90 -0
  130. package/utils/arrayKeysToCamel.ts +18 -0
  131. package/utils/calcPerfTime.ts +13 -0
  132. package/utils/configTypes.ts +3 -0
  133. package/utils/cors.ts +19 -0
  134. package/utils/fieldClear.ts +75 -0
  135. package/utils/genShortId.ts +12 -0
  136. package/utils/getClientIp.ts +45 -0
  137. package/utils/keysToCamel.ts +22 -0
  138. package/utils/keysToSnake.ts +22 -0
  139. package/utils/modules.ts +98 -0
  140. package/utils/pickFields.ts +19 -0
  141. package/utils/process.ts +56 -0
  142. package/utils/regex.ts +225 -0
  143. package/utils/response.ts +115 -0
  144. package/utils/route.ts +23 -0
  145. package/utils/scanConfig.ts +142 -0
  146. package/utils/scanFiles.ts +48 -0
  147. package/.prettierignore +0 -2
  148. package/.prettierrc +0 -12
  149. package/docs/1-/345/237/272/346/234/254/344/273/213/347/273/215.md +0 -35
  150. package/docs/2-/345/210/235/346/255/245/344/275/223/351/252/214.md +0 -64
  151. package/docs/3-/347/254/254/344/270/200/344/270/252/346/216/245/345/217/243.md +0 -46
  152. package/docs/4-/346/223/215/344/275/234/346/225/260/346/215/256/345/272/223.md +0 -172
  153. package/hooks/requestLogger.ts +0 -84
  154. package/types/index.ts +0 -24
  155. package/util.ts +0 -283
@@ -0,0 +1,282 @@
1
+ /**
2
+ * API 集成测试 - 验证 array_number_string 类型在完整数据流中的表现
3
+ *
4
+ * 测试流程:请求验证 → 数据库存储 → 查询返回
5
+ */
6
+
7
+ import type { TableDefinition } from "befly/types/validate";
8
+
9
+ import { describe, expect, test } from "bun:test";
10
+
11
+ import { Validator } from "../lib/validator.js";
12
+
13
+ describe("API 集成测试 - array_number_string 数据流", () => {
14
+ // 模拟角色表定义
15
+ const roleTable: TableDefinition = {
16
+ name: {
17
+ name: "角色名称",
18
+ type: "string",
19
+ min: 2,
20
+ max: 50,
21
+ default: null,
22
+ regexp: null
23
+ },
24
+ code: {
25
+ name: "角色编码",
26
+ type: "string",
27
+ min: 2,
28
+ max: 50,
29
+ default: null,
30
+ regexp: null
31
+ },
32
+ menus: {
33
+ name: "菜单权限",
34
+ type: "array_number_string",
35
+ min: null,
36
+ max: 2000,
37
+ default: null,
38
+ regexp: null
39
+ },
40
+ apis: {
41
+ name: "接口权限",
42
+ type: "array_number_string",
43
+ min: null,
44
+ max: 3000,
45
+ default: null,
46
+ regexp: null
47
+ }
48
+ };
49
+
50
+ // ==================== 请求验证阶段 ====================
51
+
52
+ test("请求验证: 接受合法的数字数组", () => {
53
+ const requestData = {
54
+ name: "测试角色",
55
+ code: "test_role",
56
+ menus: [1, 2, 3, 4, 5],
57
+ apis: [10, 20, 30]
58
+ };
59
+
60
+ const result = Validator.validate(requestData, roleTable, ["name", "code"]);
61
+ expect(result.failed).toBe(false);
62
+ });
63
+
64
+ test("请求验证: 拒绝字符串数组", () => {
65
+ const requestData = {
66
+ name: "测试角色",
67
+ code: "test_role",
68
+ menus: ["1", "2", "3"], // 错误:字符串数组
69
+ apis: [10, 20, 30]
70
+ };
71
+
72
+ const result = Validator.validate(requestData, roleTable, ["name", "code"]);
73
+ expect(result.failed).toBe(true);
74
+ expect(result.firstError).toContain("数组元素必须是数字");
75
+ });
76
+
77
+ test("请求验证: 拒绝混合类型数组", () => {
78
+ const requestData = {
79
+ name: "测试角色",
80
+ code: "test_role",
81
+ menus: [1, 2, 3],
82
+ apis: [10, "20", 30] // 错误:混合类型
83
+ };
84
+
85
+ const result = Validator.validate(requestData, roleTable, ["name", "code"]);
86
+ expect(result.failed).toBe(true);
87
+ expect(result.firstError).toContain("数组元素必须是数字");
88
+ });
89
+
90
+ test("请求验证: 空数组应通过验证", () => {
91
+ const requestData = {
92
+ name: "测试角色",
93
+ code: "test_role",
94
+ menus: [],
95
+ apis: []
96
+ };
97
+
98
+ const result = Validator.validate(requestData, roleTable, ["name", "code"]);
99
+ expect(result.failed).toBe(false);
100
+ });
101
+
102
+ test("请求验证: 未传递数组字段时使用默认值", () => {
103
+ const requestData = {
104
+ name: "测试角色",
105
+ code: "test_role"
106
+ // menus 和 apis 未传递
107
+ };
108
+
109
+ const result = Validator.validate(requestData, roleTable, ["name", "code"]);
110
+ expect(result.failed).toBe(false);
111
+ });
112
+
113
+ test("请求验证: max 元素数量限制生效", () => {
114
+ // 注意:max 参数对 array_number_string 是限制数组长度,不是字符串长度
115
+ const roleTableWithMax: TableDefinition = {
116
+ ...roleTable,
117
+ menus: {
118
+ name: "菜单权限",
119
+ type: "array_number_string",
120
+ min: null,
121
+ max: 10, // 最多 10 个元素
122
+ default: null,
123
+ regexp: null
124
+ }
125
+ };
126
+
127
+ const requestData = {
128
+ name: "测试角色",
129
+ code: "test_role",
130
+ menus: Array.from({ length: 15 }, (_, i) => i), // 15 个元素,超过限制
131
+ apis: [10, 20, 30]
132
+ };
133
+
134
+ const result = Validator.validate(requestData, roleTableWithMax, ["name", "code"]);
135
+ expect(result.failed).toBe(true);
136
+ expect(result.firstError).toContain("最多只能有10个元素");
137
+ });
138
+
139
+ // ==================== 数据库存储模拟 ====================
140
+
141
+ test("数据库存储: 数字数组应被正确序列化", () => {
142
+ const menuIds = [1, 2, 3, 4, 5];
143
+ const apiIds = [10, 20, 30, 40];
144
+
145
+ // 模拟数据库存储时的序列化(实际由 dbHelper 处理)
146
+ const serializedMenus = JSON.stringify(menuIds);
147
+ const serializedApis = JSON.stringify(apiIds);
148
+
149
+ expect(serializedMenus).toBe("[1,2,3,4,5]");
150
+ expect(serializedApis).toBe("[10,20,30,40]");
151
+ });
152
+
153
+ test('数据库存储: 空数组应被序列化为 "[]"', () => {
154
+ const menuIds: number[] = [];
155
+
156
+ const serialized = JSON.stringify(menuIds);
157
+ expect(serialized).toBe("[]");
158
+ });
159
+
160
+ // ==================== 查询返回模拟 ====================
161
+
162
+ test("查询返回: 数据库字符串应被正确解析为数组", () => {
163
+ // 模拟从数据库查询返回的数据(实际由 dbHelper 处理)
164
+ const dbRow = {
165
+ id: 1,
166
+ name: "测试角色",
167
+ code: "test_role",
168
+ menus: "[1,2,3,4,5]",
169
+ apis: "[10,20,30,40]"
170
+ };
171
+
172
+ // 解析
173
+ const parsedMenus = JSON.parse(dbRow.menus);
174
+ const parsedApis = JSON.parse(dbRow.apis);
175
+
176
+ expect(Array.isArray(parsedMenus)).toBe(true);
177
+ expect(Array.isArray(parsedApis)).toBe(true);
178
+ expect(parsedMenus).toEqual([1, 2, 3, 4, 5]);
179
+ expect(parsedApis).toEqual([10, 20, 30, 40]);
180
+ });
181
+
182
+ test('查询返回: 空数组字符串 "[]" 应解析为空数组', () => {
183
+ const dbRow = {
184
+ id: 1,
185
+ name: "测试角色",
186
+ code: "test_role",
187
+ menus: "[]",
188
+ apis: "[]"
189
+ };
190
+
191
+ const parsedMenus = JSON.parse(dbRow.menus);
192
+ const parsedApis = JSON.parse(dbRow.apis);
193
+
194
+ expect(parsedMenus).toEqual([]);
195
+ expect(parsedApis).toEqual([]);
196
+ });
197
+
198
+ // ==================== 完整数据流测试 ====================
199
+
200
+ test("完整流程: 请求 → 验证 → 存储 → 查询 → 返回", () => {
201
+ // 1. 模拟 API 请求
202
+ const requestData = {
203
+ name: "管理员角色",
204
+ code: "admin",
205
+ menus: [1, 2, 3, 10, 11, 12],
206
+ apis: [100, 101, 102, 200, 201]
207
+ };
208
+
209
+ // 2. 验证请求数据
210
+ const validationResult = Validator.validate(requestData, roleTable, ["name", "code"]);
211
+ expect(validationResult.failed).toBe(false);
212
+
213
+ // 3. 模拟数据库存储(序列化)
214
+ const dbData = {
215
+ name: requestData.name,
216
+ code: requestData.code,
217
+ menus: JSON.stringify(requestData.menus),
218
+ apis: JSON.stringify(requestData.apis)
219
+ };
220
+
221
+ expect(dbData.menus).toBe("[1,2,3,10,11,12]");
222
+ expect(dbData.apis).toBe("[100,101,102,200,201]");
223
+
224
+ // 4. 模拟查询返回(反序列化)
225
+ const responseData = {
226
+ id: 1,
227
+ name: dbData.name,
228
+ code: dbData.code,
229
+ menus: JSON.parse(dbData.menus),
230
+ apis: JSON.parse(dbData.apis)
231
+ };
232
+
233
+ // 5. 验证返回数据
234
+ expect(responseData.menus).toEqual(requestData.menus);
235
+ expect(responseData.apis).toEqual(requestData.apis);
236
+ });
237
+
238
+ test("完整流程: 更新权限(部分字段)", () => {
239
+ // 模拟更新操作,只更新 menus
240
+ const existingData = {
241
+ id: 1,
242
+ name: "管理员角色",
243
+ code: "admin",
244
+ menus: "[1,2,3]",
245
+ apis: "[100,101,102]"
246
+ };
247
+
248
+ const updateRequest = {
249
+ id: 1,
250
+ menus: [1, 2, 3, 4, 5, 6] // 新增菜单
251
+ };
252
+
253
+ // 验证更新数据
254
+ const fields: TableDefinition = {
255
+ menus: roleTable.menus
256
+ };
257
+ const validationResult = Validator.validate(updateRequest, fields, []);
258
+ expect(validationResult.failed).toBe(false);
259
+
260
+ // 更新后的数据
261
+ const updatedData = {
262
+ ...existingData,
263
+ menus: JSON.stringify(updateRequest.menus)
264
+ };
265
+
266
+ expect(updatedData.menus).toBe("[1,2,3,4,5,6]");
267
+ expect(updatedData.apis).toBe("[100,101,102]"); // 未变
268
+ });
269
+
270
+ test("错误场景: 尝试存储非法数据", () => {
271
+ const invalidRequest = {
272
+ name: "测试角色",
273
+ code: "test",
274
+ menus: "invalid", // 错误:不是数组
275
+ apis: [10, 20]
276
+ };
277
+
278
+ const result = Validator.validate(invalidRequest, roleTable, ["name", "code"]);
279
+ expect(result.failed).toBe(true);
280
+ expect(result.errorFields).toContain("menus");
281
+ });
282
+ });
@@ -0,0 +1,78 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ import { loadBeflyConfig } from "../befly.config.js";
6
+
7
+ describe("Config - NODE_ENV 选择 development/production", () => {
8
+ test("production -> befly.production.json;其他 -> befly.development.json(common 总是生效)", async () => {
9
+ const tempRoot = join(process.cwd(), "temp", `befly-config-env-test-${Date.now()}-${Math.random().toString(16).slice(2)}`);
10
+ const tempConfigsDir = join(tempRoot, "configs");
11
+
12
+ mkdirSync(tempConfigsDir, { recursive: true });
13
+
14
+ writeFileSync(
15
+ join(tempConfigsDir, "befly.common.json"),
16
+ JSON.stringify(
17
+ {
18
+ appPort: 31111,
19
+ logger: {
20
+ debug: 2
21
+ }
22
+ },
23
+ null,
24
+ 4
25
+ ),
26
+ { encoding: "utf8" }
27
+ );
28
+
29
+ writeFileSync(
30
+ join(tempConfigsDir, "befly.development.json"),
31
+ JSON.stringify(
32
+ {
33
+ appName: "development",
34
+ logger: {
35
+ debug: 3
36
+ }
37
+ },
38
+ null,
39
+ 4
40
+ ),
41
+ { encoding: "utf8" }
42
+ );
43
+
44
+ writeFileSync(
45
+ join(tempConfigsDir, "befly.production.json"),
46
+ JSON.stringify(
47
+ {
48
+ appName: "production",
49
+ logger: {
50
+ debug: 9
51
+ }
52
+ },
53
+ null,
54
+ 4
55
+ ),
56
+ { encoding: "utf8" }
57
+ );
58
+
59
+ try {
60
+ const productionConfig = await loadBeflyConfig({ cwd: tempRoot, nodeEnv: "production" });
61
+ expect(productionConfig.appName).toBe("production");
62
+ expect(productionConfig.logger.debug).toBe(9);
63
+ expect(productionConfig.appPort).toBe(31111);
64
+
65
+ const developmentConfig = await loadBeflyConfig({ cwd: tempRoot, nodeEnv: "development" });
66
+ expect(developmentConfig.appName).toBe("development");
67
+ expect(developmentConfig.logger.debug).toBe(3);
68
+ expect(developmentConfig.appPort).toBe(31111);
69
+
70
+ const fallbackConfig = await loadBeflyConfig({ cwd: tempRoot, nodeEnv: "test" });
71
+ expect(fallbackConfig.appName).toBe("development");
72
+ expect(fallbackConfig.logger.debug).toBe(3);
73
+ expect(fallbackConfig.appPort).toBe(31111);
74
+ } finally {
75
+ rmSync(tempRoot, { recursive: true, force: true });
76
+ }
77
+ });
78
+ });