befly 3.9.38 → 3.9.40

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 +37 -38
  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} +223 -231
  8. package/docs/cipher.md +71 -69
  9. package/docs/database.md +143 -141
  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} +1 -1
  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 +8 -8
  34. package/lib/asyncContext.ts +43 -0
  35. package/lib/cacheHelper.ts +212 -77
  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 +183 -102
  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 +48 -44
  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 -52
  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 -65
  75. package/sync/syncMenu.ts +190 -55
  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
@@ -3,135 +3,136 @@
3
3
  * 测试复杂 WHERE 条件、边界条件、SQL 注入防护
4
4
  */
5
5
 
6
- import { describe, test, expect } from 'bun:test';
7
- import { SqlBuilder } from '../lib/sqlBuilder';
6
+ import { describe, test, expect } from "bun:test";
8
7
 
9
- describe('SqlBuilder - WHERE 操作符完整测试', () => {
10
- test('$in - 空数组应抛出错误', () => {
8
+ import { SqlBuilder } from "../lib/sqlBuilder";
9
+
10
+ describe("SqlBuilder - WHERE 操作符完整测试", () => {
11
+ test("$in - 空数组应抛出错误", () => {
11
12
  // **已修复**:$in 传入空数组 [] 会抛出错误
12
13
  // 空数组会导致查询永远不匹配任何记录
13
14
 
14
15
  const builder = new SqlBuilder();
15
16
 
16
17
  expect(() => {
17
- builder.select(['*']).from('users').where({ id$in: [] }).toSelectSql();
18
- }).toThrow('$in 操作符的数组不能为空');
18
+ builder.select(["*"]).from("users").where({ id$in: [] }).toSelectSql();
19
+ }).toThrow("$in 操作符的数组不能为空");
19
20
  });
20
21
 
21
- test('$in - 单个值应生成正确的 SQL', () => {
22
+ test("$in - 单个值应生成正确的 SQL", () => {
22
23
  const builder = new SqlBuilder();
23
24
  const result = builder
24
- .select(['*'])
25
- .from('users')
25
+ .select(["*"])
26
+ .from("users")
26
27
  .where({ id$in: [1] })
27
28
  .toSelectSql();
28
29
 
29
- expect(result.sql).toContain('IN (?)');
30
+ expect(result.sql).toContain("IN (?)");
30
31
  expect(result.params).toEqual([1]);
31
32
  });
32
33
 
33
- test('$in - 多个值应生成多个占位符', () => {
34
+ test("$in - 多个值应生成多个占位符", () => {
34
35
  const builder = new SqlBuilder();
35
36
  const result = builder
36
- .select(['*'])
37
- .from('users')
37
+ .select(["*"])
38
+ .from("users")
38
39
  .where({ id$in: [1, 2, 3, 4, 5] })
39
40
  .toSelectSql();
40
41
 
41
- expect(result.sql).toContain('IN (?,?,?,?,?)');
42
+ expect(result.sql).toContain("IN (?,?,?,?,?)");
42
43
  expect(result.params).toEqual([1, 2, 3, 4, 5]);
43
44
  });
44
45
 
45
- test('$notIn - 应生成 NOT IN 语句', () => {
46
+ test("$notIn - 应生成 NOT IN 语句", () => {
46
47
  const builder = new SqlBuilder();
47
48
  const result = builder
48
- .select(['*'])
49
- .from('users')
50
- .where({ status$notIn: ['banned', 'deleted'] })
49
+ .select(["*"])
50
+ .from("users")
51
+ .where({ status$notIn: ["banned", "deleted"] })
51
52
  .toSelectSql();
52
53
 
53
- expect(result.sql).toContain('NOT IN');
54
- expect(result.params).toEqual(['banned', 'deleted']);
54
+ expect(result.sql).toContain("NOT IN");
55
+ expect(result.params).toEqual(["banned", "deleted"]);
55
56
  });
56
57
 
57
- test('$between - 应生成 BETWEEN AND 语句', () => {
58
+ test("$between - 应生成 BETWEEN AND 语句", () => {
58
59
  const builder = new SqlBuilder();
59
60
  const result = builder
60
- .select(['*'])
61
- .from('users')
61
+ .select(["*"])
62
+ .from("users")
62
63
  .where({ age$between: [18, 65] })
63
64
  .toSelectSql();
64
65
 
65
- expect(result.sql).toContain('BETWEEN ? AND ?');
66
+ expect(result.sql).toContain("BETWEEN ? AND ?");
66
67
  expect(result.params).toEqual([18, 65]);
67
68
  });
68
69
 
69
- test('$between - 非数组值应如何处理', () => {
70
+ test("$between - 非数组值应如何处理", () => {
70
71
  // **问题**:$between 应该接收 [min, max]
71
72
  // 如果传入非数组,应该报错还是忽略?
72
73
 
73
74
  const builder = new SqlBuilder();
74
75
  const result = builder
75
- .select(['*'])
76
- .from('users')
76
+ .select(["*"])
77
+ .from("users")
77
78
  .where({ age$between: 18 as any })
78
79
  .toSelectSql();
79
80
 
80
- // 当前可能不会生成 BETWEEN 条件
81
- console.log('$between 非数组生成的 SQL:', result.sql);
81
+ // 当前实现:非数组不会生成 BETWEEN 条件(保持为其它 where 逻辑输出)
82
+ expect(typeof result.sql).toBe("string");
82
83
  });
83
84
 
84
- test('$between - 数组长度不为 2 应如何处理', () => {
85
+ test("$between - 数组长度不为 2 应如何处理", () => {
85
86
  const builder = new SqlBuilder();
86
87
  const result = builder
87
- .select(['*'])
88
- .from('users')
88
+ .select(["*"])
89
+ .from("users")
89
90
  .where({ age$between: [18] as any })
90
91
  .toSelectSql();
91
92
 
92
- // **建议**:应该验证数组长度为 2
93
- console.log('$between 数组长度不足生成的 SQL:', result.sql);
93
+ // **建议**:未来可验证数组长度必须为 2
94
+ expect(typeof result.sql).toBe("string");
94
95
  });
95
96
 
96
- test('$null - 应生成 IS NULL 语句', () => {
97
+ test("$null - 应生成 IS NULL 语句", () => {
97
98
  const builder = new SqlBuilder();
98
- const result = builder.select(['*']).from('users').where({ deletedAt$null: true }).toSelectSql();
99
+ const result = builder.select(["*"]).from("users").where({ deletedAt$null: true }).toSelectSql();
99
100
 
100
- expect(result.sql).toContain('IS NULL');
101
+ expect(result.sql).toContain("IS NULL");
101
102
  expect(result.params.length).toBe(0); // IS NULL 不需要参数
102
103
  });
103
104
 
104
- test('$notNull - 应生成 IS NOT NULL 语句', () => {
105
+ test("$notNull - 应生成 IS NOT NULL 语句", () => {
105
106
  const builder = new SqlBuilder();
106
- const result = builder.select(['*']).from('users').where({ email$notNull: true }).toSelectSql();
107
+ const result = builder.select(["*"]).from("users").where({ email$notNull: true }).toSelectSql();
107
108
 
108
- expect(result.sql).toContain('IS NOT NULL');
109
+ expect(result.sql).toContain("IS NOT NULL");
109
110
  });
110
111
 
111
- test('$like - 应生成 LIKE 语句', () => {
112
+ test("$like - 应生成 LIKE 语句", () => {
112
113
  const builder = new SqlBuilder();
113
- const result = builder.select(['*']).from('users').where({ name$like: '%john%' }).toSelectSql();
114
+ const result = builder.select(["*"]).from("users").where({ name$like: "%john%" }).toSelectSql();
114
115
 
115
- expect(result.sql).toContain('LIKE ?');
116
- expect(result.params).toEqual(['%john%']);
116
+ expect(result.sql).toContain("LIKE ?");
117
+ expect(result.params).toEqual(["%john%"]);
117
118
  });
118
119
 
119
- test('$like - 通配符应由用户控制', () => {
120
+ test("$like - 通配符应由用户控制", () => {
120
121
  // 用户应自己添加 %
121
122
  const builder = new SqlBuilder();
122
- const result = builder.select(['*']).from('users').where({ name$like: 'john' }).toSelectSql();
123
+ const result = builder.select(["*"]).from("users").where({ name$like: "john" }).toSelectSql();
123
124
 
124
125
  // 不应自动添加 %
125
- expect(result.params).toEqual(['john']);
126
+ expect(result.params).toEqual(["john"]);
126
127
  });
127
128
 
128
- test('比较操作符 - 应生成正确的符号', () => {
129
+ test("比较操作符 - 应生成正确的符号", () => {
129
130
  const cases = [
130
- { op: 'gt', symbol: '>', value: 18 },
131
- { op: 'gte', symbol: '>=', value: 18 },
132
- { op: 'lt', symbol: '<', value: 65 },
133
- { op: 'lte', symbol: '<=', value: 65 },
134
- { op: 'ne', symbol: '!=', value: 0 }
131
+ { op: "gt", symbol: ">", value: 18 },
132
+ { op: "gte", symbol: ">=", value: 18 },
133
+ { op: "lt", symbol: "<", value: 65 },
134
+ { op: "lte", symbol: "<=", value: 65 },
135
+ { op: "ne", symbol: "!=", value: 0 }
135
136
  ];
136
137
 
137
138
  cases.forEach(({ op, symbol, value }) => {
@@ -139,7 +140,7 @@ describe('SqlBuilder - WHERE 操作符完整测试', () => {
139
140
  const where: any = {};
140
141
  where[`age$${op}`] = value;
141
142
 
142
- const result = builder.select(['*']).from('users').where(where).toSelectSql();
143
+ const result = builder.select(["*"]).from("users").where(where).toSelectSql();
143
144
 
144
145
  expect(result.sql).toContain(symbol);
145
146
  expect(result.params).toEqual([value]);
@@ -147,82 +148,82 @@ describe('SqlBuilder - WHERE 操作符完整测试', () => {
147
148
  });
148
149
  });
149
150
 
150
- describe('SqlBuilder - 复杂 WHERE 条件', () => {
151
- test('$or - 单个条件应正确处理', () => {
151
+ describe("SqlBuilder - 复杂 WHERE 条件", () => {
152
+ test("$or - 单个条件应正确处理", () => {
152
153
  const builder = new SqlBuilder();
153
154
  const result = builder
154
- .select(['*'])
155
- .from('users')
155
+ .select(["*"])
156
+ .from("users")
156
157
  .where({
157
- $or: [{ name: 'john' }]
158
+ $or: [{ name: "john" }]
158
159
  })
159
160
  .toSelectSql();
160
161
 
161
162
  // 单个条件不应该显示OR关键字
162
- expect(result.sql).not.toContain(' OR ');
163
- expect(result.params).toEqual(['john']);
163
+ expect(result.sql).not.toContain(" OR ");
164
+ expect(result.params).toEqual(["john"]);
164
165
  });
165
166
 
166
- test('$or - 多个条件应用 OR 连接', () => {
167
+ test("$or - 多个条件应用 OR 连接", () => {
167
168
  const builder = new SqlBuilder();
168
169
  const result = builder
169
- .select(['*'])
170
- .from('users')
170
+ .select(["*"])
171
+ .from("users")
171
172
  .where({
172
- $or: [{ name: 'john' }, { email: 'john@example.com' }]
173
+ $or: [{ name: "john" }, { email: "john@example.com" }]
173
174
  })
174
175
  .toSelectSql();
175
176
 
176
- expect(result.sql).toContain('OR');
177
- expect(result.params).toEqual(['john', 'john@example.com']);
177
+ expect(result.sql).toContain("OR");
178
+ expect(result.params).toEqual(["john", "john@example.com"]);
178
179
  });
179
180
 
180
- test('$or - 空数组应如何处理', () => {
181
+ test("$or - 空数组应如何处理", () => {
181
182
  const builder = new SqlBuilder();
182
- const result = builder.select(['*']).from('users').where({ $or: [] }).toSelectSql();
183
+ const result = builder.select(["*"]).from("users").where({ $or: [] }).toSelectSql();
183
184
 
184
185
  // **问题**:空 $or 应该跳过还是报错?
185
- console.log('$or 空数组生成的 SQL:', result.sql);
186
+ console.log("$or 空数组生成的 SQL:", result.sql);
186
187
  });
187
188
 
188
- test('$and + $or 嵌套', () => {
189
+ test("$and + $or 嵌套", () => {
189
190
  const builder = new SqlBuilder();
190
191
  const result = builder
191
- .select(['*'])
192
- .from('users')
192
+ .select(["*"])
193
+ .from("users")
193
194
  .where({
194
195
  age$gte: 18,
195
- $or: [{ role: 'admin' }, { role: 'moderator' }]
196
+ $or: [{ role: "admin" }, { role: "moderator" }]
196
197
  })
197
198
  .toSelectSql();
198
199
 
199
- expect(result.sql).toContain('AND');
200
- expect(result.sql).toContain('OR');
201
- expect(result.params).toEqual([18, 'admin', 'moderator']);
200
+ expect(result.sql).toContain("AND");
201
+ expect(result.sql).toContain("OR");
202
+ expect(result.params).toEqual([18, "admin", "moderator"]);
202
203
  });
203
204
 
204
- test('$or 内嵌套多个操作符', () => {
205
+ test("$or 内嵌套多个操作符", () => {
205
206
  const builder = new SqlBuilder();
206
207
  const result = builder
207
- .select(['*'])
208
- .from('users')
208
+ .select(["*"])
209
+ .from("users")
209
210
  .where({
210
211
  $or: [{ age$gt: 60 }, { age$lt: 18 }]
211
212
  })
212
213
  .toSelectSql();
213
214
 
214
- expect(result.sql).toContain('OR');
215
- expect(result.sql).toContain('>');
216
- expect(result.sql).toContain('<');
215
+ expect(result.sql).toContain("OR");
216
+ expect(result.sql).toContain(">");
217
+ expect(result.sql).toContain("<");
217
218
  });
218
219
 
219
- test('深层嵌套的 $or 和 $and', () => {
220
+ test("深层嵌套的 $or 和 $and", () => {
220
221
  const builder = new SqlBuilder();
221
222
  const result = builder
222
- .select(['*'])
223
- .from('users')
223
+ .select(["*"])
224
+ .from("users")
224
225
  .where({
225
- status: 'active',
226
+ status: "active",
226
227
  $or: [
227
228
  {
228
229
  $and: [{ age$gte: 18 }, { age$lte: 65 }]
@@ -233,241 +234,242 @@ describe('SqlBuilder - 复杂 WHERE 条件', () => {
233
234
  .toSelectSql();
234
235
 
235
236
  expect(result.sql).toBeDefined();
236
- console.log('深层嵌套生成的 SQL:', result.sql);
237
+ console.log("深层嵌套生成的 SQL:", result.sql);
237
238
  });
238
239
  });
239
240
 
240
- describe('SqlBuilder - 字段名转义', () => {
241
- test('普通字段名应添加反引号', () => {
241
+ describe("SqlBuilder - 字段名转义", () => {
242
+ test("普通字段名应添加反引号", () => {
242
243
  const builder = new SqlBuilder();
243
- const result = builder.select(['name', 'email']).from('users').toSelectSql();
244
+ const result = builder.select(["name", "email"]).from("users").toSelectSql();
244
245
 
245
- expect(result.sql).toContain('`name`');
246
- expect(result.sql).toContain('`email`');
246
+ expect(result.sql).toContain("`name`");
247
+ expect(result.sql).toContain("`email`");
247
248
  });
248
249
 
249
- test('星号不应添加反引号', () => {
250
+ test("星号不应添加反引号", () => {
250
251
  const builder = new SqlBuilder();
251
- const result = builder.select(['*']).from('users').toSelectSql();
252
+ const result = builder.select(["*"]).from("users").toSelectSql();
252
253
 
253
- expect(result.sql).toContain('SELECT *');
254
- expect(result.sql).not.toContain('`*`');
254
+ expect(result.sql).toContain("SELECT *");
255
+ expect(result.sql).not.toContain("`*`");
255
256
  });
256
257
 
257
- test('已有反引号的字段不应重复添加', () => {
258
+ test("已有反引号的字段不应重复添加", () => {
258
259
  const builder = new SqlBuilder();
259
- const result = builder.select(['`user_id`', '`user_name`']).from('users').toSelectSql();
260
+ const result = builder.select(["`user_id`", "`user_name`"]).from("users").toSelectSql();
260
261
 
261
262
  // 不应变成 `\`user_id\``
262
- expect(result.sql).toContain('`user_id`');
263
- expect(result.sql).not.toContain('``');
263
+ expect(result.sql).toContain("`user_id`");
264
+ expect(result.sql).not.toContain("``");
264
265
  });
265
266
 
266
- test('表名.字段名应分别转义', () => {
267
+ test("表名.字段名应分别转义", () => {
267
268
  const builder = new SqlBuilder();
268
- const result = builder.select(['users.id', 'profiles.bio']).from('users').toSelectSql();
269
+ const result = builder.select(["users.id", "profiles.bio"]).from("users").toSelectSql();
269
270
 
270
- expect(result.sql).toContain('`users`.`id`');
271
- expect(result.sql).toContain('`profiles`.`bio`');
271
+ expect(result.sql).toContain("`users`.`id`");
272
+ expect(result.sql).toContain("`profiles`.`bio`");
272
273
  });
273
274
 
274
- test('函数调用不应转义', () => {
275
+ test("函数调用不应转义", () => {
275
276
  const builder = new SqlBuilder();
276
- const result = builder.select(['COUNT(*)', 'MAX(age)']).from('users').toSelectSql();
277
+ const result = builder.select(["COUNT(*)", "MAX(age)"]).from("users").toSelectSql();
277
278
 
278
- expect(result.sql).toContain('COUNT(*)');
279
- expect(result.sql).toContain('MAX(age)');
280
- expect(result.sql).not.toContain('`COUNT(*)`');
279
+ expect(result.sql).toContain("COUNT(*)");
280
+ expect(result.sql).toContain("MAX(age)");
281
+ expect(result.sql).not.toContain("`COUNT(*)`");
281
282
  });
282
283
 
283
- test('AS 别名应正确处理', () => {
284
+ test("AS 别名应正确处理", () => {
284
285
  const builder = new SqlBuilder();
285
- const result = builder.select(['name AS userName', 'COUNT(*) AS total']).from('users').toSelectSql();
286
+ const result = builder.select(["name AS userName", "COUNT(*) AS total"]).from("users").toSelectSql();
286
287
 
287
- expect(result.sql).toContain('`name` AS userName');
288
- expect(result.sql).toContain('COUNT(*) AS total');
288
+ expect(result.sql).toContain("`name` AS userName");
289
+ expect(result.sql).toContain("COUNT(*) AS total");
289
290
  });
290
291
 
291
- test('特殊字符字段名应转义', () => {
292
+ test("特殊字符字段名应转义", () => {
292
293
  // 包含空格、横杠等特殊字符的字段名
293
294
  const builder = new SqlBuilder();
294
- const result = builder.select(['user-name', 'created at']).from('users').toSelectSql();
295
+ const result = builder.select(["user-name", "created at"]).from("users").toSelectSql();
295
296
 
296
297
  // 应该添加反引号保护
297
- expect(result.sql).toContain('`user-name`');
298
- expect(result.sql).toContain('`created at`');
298
+ expect(result.sql).toContain("`user-name`");
299
+ expect(result.sql).toContain("`created at`");
299
300
  });
300
301
  });
301
302
 
302
- describe('SqlBuilder - 表名转义', () => {
303
- test('普通表名应添加反引号', () => {
303
+ describe("SqlBuilder - 表名转义", () => {
304
+ test("普通表名应添加反引号", () => {
304
305
  const builder = new SqlBuilder();
305
- const result = builder.select(['*']).from('users').toSelectSql();
306
+ const result = builder.select(["*"]).from("users").toSelectSql();
306
307
 
307
- expect(result.sql).toContain('FROM `users`');
308
+ expect(result.sql).toContain("FROM `users`");
308
309
  });
309
310
 
310
- test('表别名应正确处理', () => {
311
+ test("表别名应正确处理", () => {
311
312
  const builder = new SqlBuilder();
312
- const result = builder.select(['*']).from('users u').toSelectSql();
313
+ const result = builder.select(["*"]).from("users u").toSelectSql();
313
314
 
314
- expect(result.sql).toContain('FROM `users` u');
315
+ expect(result.sql).toContain("FROM `users` u");
315
316
  });
316
317
 
317
- test('已有反引号的表名不应重复添加', () => {
318
+ test("已有反引号的表名不应重复添加", () => {
318
319
  const builder = new SqlBuilder();
319
- const result = builder.select(['*']).from('`user_profiles`').toSelectSql();
320
+ const result = builder.select(["*"]).from("`user_profiles`").toSelectSql();
320
321
 
321
- expect(result.sql).toContain('FROM `user_profiles`');
322
- expect(result.sql).not.toContain('``');
322
+ expect(result.sql).toContain("FROM `user_profiles`");
323
+ expect(result.sql).not.toContain("``");
323
324
  });
324
325
  });
325
326
 
326
- describe('SqlBuilder - ORDER BY', () => {
327
- test('单字段升序排序', () => {
327
+ describe("SqlBuilder - ORDER BY", () => {
328
+ test("单字段升序排序", () => {
328
329
  const builder = new SqlBuilder();
329
- const result = builder.select(['*']).from('users').orderBy(['createdAt#ASC']).toSelectSql();
330
+ const result = builder.select(["*"]).from("users").orderBy(["createdAt#ASC"]).toSelectSql();
330
331
 
331
332
  // ORDER BY目前不转换驼峰,保持原样
332
- expect(result.sql).toContain('ORDER BY `createdAt` ASC');
333
+ expect(result.sql).toContain("ORDER BY `createdAt` ASC");
333
334
  });
334
335
 
335
- test('单字段降序排序', () => {
336
+ test("单字段降序排序", () => {
336
337
  const builder = new SqlBuilder();
337
- const result = builder.select(['*']).from('users').orderBy(['createdAt#DESC']).toSelectSql();
338
+ const result = builder.select(["*"]).from("users").orderBy(["createdAt#DESC"]).toSelectSql();
338
339
 
339
340
  // ORDER BY目前不转换驼峰,保持原样
340
- expect(result.sql).toContain('ORDER BY `createdAt` DESC');
341
+ expect(result.sql).toContain("ORDER BY `createdAt` DESC");
341
342
  });
342
343
 
343
- test('多字段排序', () => {
344
+ test("多字段排序", () => {
344
345
  const builder = new SqlBuilder();
345
- const result = builder.select(['*']).from('users').orderBy(['priority#DESC', 'createdAt#ASC']).toSelectSql();
346
+ const result = builder.select(["*"]).from("users").orderBy(["priority#DESC", "createdAt#ASC"]).toSelectSql();
346
347
 
347
- expect(result.sql).toContain('ORDER BY');
348
- expect(result.sql).toContain('`priority` DESC');
349
- expect(result.sql).toContain('`createdAt` ASC');
348
+ expect(result.sql).toContain("ORDER BY");
349
+ expect(result.sql).toContain("`priority` DESC");
350
+ expect(result.sql).toContain("`createdAt` ASC");
350
351
  });
351
352
 
352
- test('无方向标识应抛错', () => {
353
+ test("无方向标识应抛错", () => {
353
354
  // **当前实现**:要求必须有 #ASC 或 #DESC
354
355
 
355
356
  const builder = new SqlBuilder();
356
357
 
357
358
  expect(() => {
358
- builder.select(['*']).from('users').orderBy(['createdAt']).toSelectSql();
359
+ builder.select(["*"]).from("users").orderBy(["createdAt"]).toSelectSql();
359
360
  }).toThrow('orderBy 字段必须是 "字段#方向" 格式的字符串');
360
361
  });
361
362
 
362
- test('字段名当前不转换为下划线', () => {
363
+ test("字段名当前不转换为下划线", () => {
363
364
  const builder = new SqlBuilder();
364
- const result = builder.select(['*']).from('users').orderBy(['createdAt#DESC', 'userId#ASC']).toSelectSql();
365
+ const result = builder.select(["*"]).from("users").orderBy(["createdAt#DESC", "userId#ASC"]).toSelectSql();
365
366
 
366
367
  // 当前实现不转换驼峰
367
- expect(result.sql).toContain('createdAt');
368
- expect(result.sql).toContain('userId');
368
+ expect(result.sql).toContain("createdAt");
369
+ expect(result.sql).toContain("userId");
369
370
  });
370
371
  });
371
372
 
372
- describe('SqlBuilder - LIMIT 和 OFFSET', () => {
373
- test('LIMIT 应生成正确的 SQL', () => {
373
+ describe("SqlBuilder - LIMIT 和 OFFSET", () => {
374
+ test("LIMIT 应生成正确的 SQL", () => {
374
375
  const builder = new SqlBuilder();
375
- const result = builder.select(['*']).from('users').limit(10).toSelectSql();
376
+ const result = builder.select(["*"]).from("users").limit(10).toSelectSql();
376
377
 
377
- expect(result.sql).toContain('LIMIT 10');
378
+ expect(result.sql).toContain("LIMIT 10");
378
379
  });
379
380
 
380
- test('OFFSET 单独使用时无效(需要配合LIMIT)', () => {
381
+ test("OFFSET 单独使用时无效(需要配合LIMIT)", () => {
381
382
  const builder = new SqlBuilder();
382
- const result = builder.select(['*']).from('users').offset(20).toSelectSql();
383
+ const result = builder.select(["*"]).from("users").offset(20).toSelectSql();
383
384
 
384
385
  // 当前实现:单独使用OFFSET不生效
385
- expect(result.sql).not.toContain('OFFSET');
386
+ expect(result.sql).not.toContain("OFFSET");
386
387
  });
387
388
 
388
- test('LIMIT + OFFSET 组合', () => {
389
+ test("LIMIT + OFFSET 组合", () => {
389
390
  const builder = new SqlBuilder();
390
- const result = builder.select(['*']).from('users').limit(10).offset(20).toSelectSql();
391
+ const result = builder.select(["*"]).from("users").limit(10).offset(20).toSelectSql();
391
392
 
392
- expect(result.sql).toContain('LIMIT 10');
393
- expect(result.sql).toContain('OFFSET 20');
393
+ expect(result.sql).toContain("LIMIT 10");
394
+ expect(result.sql).toContain("OFFSET 20");
394
395
  });
395
396
 
396
- test('LIMIT 0 应允许(查询结构不查数据)', () => {
397
+ test("LIMIT 0 应允许(查询结构不查数据)", () => {
397
398
  const builder = new SqlBuilder();
398
- const result = builder.select(['*']).from('users').limit(0).toSelectSql();
399
+ const result = builder.select(["*"]).from("users").limit(0).toSelectSql();
399
400
 
400
- expect(result.sql).toContain('LIMIT 0');
401
+ expect(result.sql).toContain("LIMIT 0");
401
402
  });
402
403
 
403
- test('LIMIT 负数应抛错', () => {
404
+ test("LIMIT 负数应抛错", () => {
404
405
  const builder = new SqlBuilder();
405
406
 
406
407
  expect(() => {
407
- builder.select(['*']).from('users').limit(-10).toSelectSql();
408
- }).toThrow('LIMIT 数量必须是非负数');
408
+ builder.select(["*"]).from("users").limit(-10).toSelectSql();
409
+ }).toThrow("LIMIT 数量必须是非负数");
409
410
  });
410
411
 
411
- test('OFFSET 负数应抛错', () => {
412
+ test("OFFSET 负数应抛错", () => {
412
413
  const builder = new SqlBuilder();
413
414
 
414
415
  expect(() => {
415
- builder.select(['*']).from('users').offset(-5).toSelectSql();
416
- }).toThrow('OFFSET 必须是非负数');
416
+ builder.select(["*"]).from("users").offset(-5).toSelectSql();
417
+ }).toThrow("OFFSET 必须是非负数");
417
418
  });
418
419
  });
419
420
 
420
- describe('SqlBuilder - INSERT/UPDATE/DELETE 语句', () => {
421
- test('当前 SqlBuilder 不直接支持 INSERT/UPDATE/DELETE', () => {
421
+ describe("SqlBuilder - INSERT/UPDATE/DELETE 语句", () => {
422
+ test("当前 SqlBuilder 不直接支持 INSERT/UPDATE/DELETE", () => {
422
423
  // **说明**:当前 SqlBuilder 主要用于 SELECT 查询
423
424
  // INSERT/UPDATE/DELETE 由 DbHelper 的 insData/updData/delData 方法处理
424
425
 
425
426
  const builder = new SqlBuilder();
426
427
 
427
428
  // 验证没有这些方法
428
- expect(typeof (builder as any).insert).toBe('undefined');
429
- expect(typeof (builder as any).update).toBe('undefined');
430
- expect(typeof (builder as any).delete).toBe('undefined');
429
+ expect(typeof (builder as any).insert).toBe("undefined");
430
+ expect(typeof (builder as any).update).toBe("undefined");
431
+ expect(typeof (builder as any).delete).toBe("undefined");
431
432
  });
432
433
  });
433
434
 
434
- describe('SqlBuilder - SQL 注入防护', () => {
435
- test('参数化查询应防止 SQL 注入', () => {
435
+ describe("SqlBuilder - SQL 注入防护", () => {
436
+ test("参数化查询应防止 SQL 注入", () => {
436
437
  const builder = new SqlBuilder();
437
438
  const maliciousInput = "' OR '1'='1";
438
439
 
439
- const result = builder.select(['*']).from('users').where({ name: maliciousInput }).toSelectSql();
440
+ const result = builder.select(["*"]).from("users").where({ name: maliciousInput }).toSelectSql();
440
441
 
441
442
  // 参数应该被正确转义
442
443
  expect(result.params).toEqual([maliciousInput]);
443
444
  expect(result.sql).not.toContain("' OR '1'='1");
444
- expect(result.sql).toContain('?'); // 使用占位符
445
+ expect(result.sql).toContain("?"); // 使用占位符
445
446
  });
446
447
 
447
- test('字段名不应允许用户输入', () => {
448
+ test("字段名不应允许用户输入", () => {
448
449
  // **潜在风险**:如果字段名来自用户输入
449
- const userInput = 'name; DROP TABLE users; --';
450
+ const userInput = "name; DROP TABLE users; --";
451
+ expect(userInput).toContain("DROP TABLE");
450
452
 
451
- const builder = new SqlBuilder();
452
453
  // 字段名应该从白名单选择,不应直接使用用户输入
453
454
  // const result = builder.select([userInput]).from('users').toSelectSql();
454
455
 
455
456
  // **建议**:字段名应该验证是否在允许的字段列表中
456
457
  });
457
458
 
458
- test('表名不应允许用户输入', () => {
459
+ test("表名不应允许用户输入", () => {
459
460
  // **潜在风险**:表名来自用户输入
460
- const userInput = 'users; DROP TABLE sensitive_data; --';
461
+ const userInput = "users; DROP TABLE sensitive_data; --";
462
+ expect(userInput).toContain("DROP TABLE");
461
463
 
462
464
  // **建议**:表名应该从白名单选择
463
465
  });
464
466
  });
465
467
 
466
- describe('SqlBuilder - reset 功能', () => {
467
- test('reset 应清空所有状态', () => {
468
+ describe("SqlBuilder - reset 功能", () => {
469
+ test("reset 应清空所有状态", () => {
468
470
  const builder = new SqlBuilder();
469
471
 
470
- builder.select(['name']).from('users').where({ id: 1 }).orderBy(['name#ASC']).limit(10);
472
+ builder.select(["name"]).from("users").where({ id: 1 }).orderBy(["name#ASC"]).limit(10);
471
473
 
472
474
  const beforeReset = builder.toSelectSql();
473
475
  expect(beforeReset.sql).toBeDefined();
@@ -475,56 +477,56 @@ describe('SqlBuilder - reset 功能', () => {
475
477
  builder.reset();
476
478
 
477
479
  // reset 后应该是空状态
478
- builder.select(['email']).from('profiles');
480
+ builder.select(["email"]).from("profiles");
479
481
  const afterReset = builder.toSelectSql();
480
482
 
481
- expect(afterReset.sql).not.toContain('users');
482
- expect(afterReset.sql).toContain('profiles');
483
- expect(afterReset.sql).not.toContain('LIMIT');
483
+ expect(afterReset.sql).not.toContain("users");
484
+ expect(afterReset.sql).toContain("profiles");
485
+ expect(afterReset.sql).not.toContain("LIMIT");
484
486
  });
485
487
 
486
- test('reset 应支持链式调用', () => {
488
+ test("reset 应支持链式调用", () => {
487
489
  const builder = new SqlBuilder();
488
490
 
489
- const result = builder.select(['*']).from('users').reset().select(['id']).from('posts').toSelectSql();
491
+ const result = builder.select(["*"]).from("users").reset().select(["id"]).from("posts").toSelectSql();
490
492
 
491
- expect(result.sql).toContain('posts');
492
- expect(result.sql).not.toContain('users');
493
+ expect(result.sql).toContain("posts");
494
+ expect(result.sql).not.toContain("users");
493
495
  });
494
496
  });
495
497
 
496
- describe('SqlBuilder - 代码逻辑问题分析', () => {
497
- test('问题1:$in 空数组会生成错误的 SQL', () => {
498
+ describe("SqlBuilder - 代码逻辑问题分析", () => {
499
+ test("问题1:$in 空数组会生成错误的 SQL", () => {
498
500
  // **问题**:WHERE id IN () 是非法的 SQL 语法
499
501
  // **建议**:检测到空数组应跳过条件或抛出错误
500
502
 
501
503
  const mockWhere = (values: any[]) => {
502
504
  if (values.length === 0) {
503
- throw new Error('$in 操作符不能使用空数组');
505
+ throw new Error("$in 操作符不能使用空数组");
504
506
  }
505
- return `id IN (${values.map(() => '?').join(', ')})`;
507
+ return `id IN (${values.map(() => "?").join(", ")})`;
506
508
  };
507
509
 
508
- expect(() => mockWhere([])).toThrow('$in 操作符不能使用空数组');
509
- expect(mockWhere([1, 2])).toBe('id IN (?, ?)');
510
+ expect(() => mockWhere([])).toThrow("$in 操作符不能使用空数组");
511
+ expect(mockWhere([1, 2])).toBe("id IN (?, ?)");
510
512
  });
511
513
 
512
- test('问题2:LIMIT 和 OFFSET 没有验证', () => {
514
+ test("问题2:LIMIT 和 OFFSET 没有验证", () => {
513
515
  // **问题**:负数会导致 SQL 错误
514
516
  // **建议**:添加参数验证
515
517
 
516
518
  const mockLimit = (value: number) => {
517
519
  if (value < 0) {
518
- throw new Error('LIMIT 必须 >= 0');
520
+ throw new Error("LIMIT 必须 >= 0");
519
521
  }
520
522
  return `LIMIT ${value}`;
521
523
  };
522
524
 
523
- expect(() => mockLimit(-10)).toThrow('LIMIT 必须 >= 0');
524
- expect(mockLimit(10)).toBe('LIMIT 10');
525
+ expect(() => mockLimit(-10)).toThrow("LIMIT 必须 >= 0");
526
+ expect(mockLimit(10)).toBe("LIMIT 10");
525
527
  });
526
528
 
527
- test('问题3:无 WHERE 的 UPDATE 和 DELETE 很危险', () => {
529
+ test("问题3:无 WHERE 的 UPDATE 和 DELETE 很危险", () => {
528
530
  // **问题**:容易误操作导致数据丢失
529
531
  // **建议**:要求必须有 WHERE 条件或明确的标志
530
532
 
@@ -532,37 +534,37 @@ describe('SqlBuilder - 代码逻辑问题分析', () => {
532
534
  const hasWhere = where && Object.keys(where).length > 0;
533
535
 
534
536
  if (!hasWhere && !allowNoWhere) {
535
- throw new Error('UPDATE 必须有 WHERE 条件。如果确实要更新所有行,请设置 allowNoWhere: true');
537
+ throw new Error("UPDATE 必须有 WHERE 条件。如果确实要更新所有行,请设置 allowNoWhere: true");
536
538
  }
537
539
 
538
- return 'UPDATE users SET status = ?';
540
+ return "UPDATE users SET status = ?";
539
541
  };
540
542
 
541
- expect(() => mockUpdate({})).toThrow('UPDATE 必须有 WHERE 条件');
543
+ expect(() => mockUpdate({})).toThrow("UPDATE 必须有 WHERE 条件");
542
544
  expect(mockUpdate({}, true)).toBeDefined(); // 明确允许
543
545
  expect(mockUpdate({ id: 1 })).toBeDefined(); // 有 WHERE
544
546
  });
545
547
 
546
- test('问题4:$between 参数验证不足', () => {
548
+ test("问题4:$between 参数验证不足", () => {
547
549
  // **问题**:应该验证是否为数组且长度为 2
548
550
  // **建议**:添加严格的参数检查
549
551
 
550
552
  const mockBetween = (value: any) => {
551
553
  if (!Array.isArray(value)) {
552
- throw new Error('$between 必须是数组');
554
+ throw new Error("$between 必须是数组");
553
555
  }
554
556
  if (value.length !== 2) {
555
- throw new Error('$between 数组长度必须为 2');
557
+ throw new Error("$between 数组长度必须为 2");
556
558
  }
557
559
  return `age BETWEEN ? AND ?`;
558
560
  };
559
561
 
560
- expect(() => mockBetween(18)).toThrow('$between 必须是数组');
561
- expect(() => mockBetween([18])).toThrow('$between 数组长度必须为 2');
562
+ expect(() => mockBetween(18)).toThrow("$between 必须是数组");
563
+ expect(() => mockBetween([18])).toThrow("$between 数组长度必须为 2");
562
564
  expect(mockBetween([18, 65])).toBeDefined();
563
565
  });
564
566
 
565
- test('问题5:字段名转义可能影响性能', () => {
567
+ test("问题5:字段名转义可能影响性能", () => {
566
568
  // **问题**:每次查询都要转义字段名
567
569
  // **建议**:可以缓存转义结果
568
570
 
@@ -573,19 +575,19 @@ describe('SqlBuilder - 代码逻辑问题分析', () => {
573
575
  return escapeCache.get(field)!;
574
576
  }
575
577
 
576
- const escaped = field === '*' ? field : `\`${field}\``;
578
+ const escaped = field === "*" ? field : `\`${field}\``;
577
579
  escapeCache.set(field, escaped);
578
580
  return escaped;
579
581
  };
580
582
 
581
583
  // 第一次转义
582
584
  const t1 = performance.now();
583
- mockEscape('user_name');
585
+ mockEscape("user_name");
584
586
  const time1 = performance.now() - t1;
585
587
 
586
588
  // 第二次应该更快(从缓存读取)
587
589
  const t2 = performance.now();
588
- mockEscape('user_name');
590
+ mockEscape("user_name");
589
591
  const time2 = performance.now() - t2;
590
592
 
591
593
  console.log(`首次转义: ${time1}ms, 缓存命中: ${time2}ms`);