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
package/lib/sqlBuilder.ts CHANGED
@@ -1,17 +1,16 @@
1
- /**
1
+ /**
2
2
  * SQL 构造器 - TypeScript 版本
3
3
  * 提供链式 API 构建 SQL 查询
4
4
  */
5
5
 
6
- import type { WhereConditions, OrderByField, WhereOperator, OrderDirection, SqlQuery, InsertData, UpdateData } from '../types/common.js';
7
- import type { SqlValue } from 'befly-shared/types';
6
+ import type { WhereConditions, WhereOperator, OrderDirection, SqlQuery, InsertData, UpdateData, SqlValue } from "../types/common.js";
8
7
 
9
8
  /**
10
9
  * SQL 构建器类
11
10
  */
12
11
  export class SqlBuilder {
13
12
  private _select: string[] = [];
14
- private _from: string = '';
13
+ private _from: string = "";
15
14
  private _where: string[] = [];
16
15
  private _joins: string[] = [];
17
16
  private _orderBy: string[] = [];
@@ -26,7 +25,7 @@ export class SqlBuilder {
26
25
  */
27
26
  reset(): this {
28
27
  this._select = [];
29
- this._from = '';
28
+ this._from = "";
30
29
  this._where = [];
31
30
  this._joins = [];
32
31
  this._orderBy = [];
@@ -42,19 +41,19 @@ export class SqlBuilder {
42
41
  * 转义字段名
43
42
  */
44
43
  private _escapeField(field: string): string {
45
- if (typeof field !== 'string') {
44
+ if (typeof field !== "string") {
46
45
  return field;
47
46
  }
48
47
 
49
48
  field = field.trim();
50
49
 
51
50
  // 如果是 * 或已经有着重号或包含函数,直接返回
52
- if (field === '*' || field.startsWith('`') || field.includes('(')) {
51
+ if (field === "*" || field.startsWith("`") || field.includes("(")) {
53
52
  return field;
54
53
  }
55
54
 
56
55
  // 处理别名(AS关键字)
57
- if (field.toUpperCase().includes(' AS ')) {
56
+ if (field.toUpperCase().includes(" AS ")) {
58
57
  const parts = field.split(/\s+AS\s+/i);
59
58
  const fieldPart = parts[0].trim();
60
59
  const aliasPart = parts[1].trim();
@@ -62,17 +61,17 @@ export class SqlBuilder {
62
61
  }
63
62
 
64
63
  // 处理表名.字段名的情况(多表联查)
65
- if (field.includes('.')) {
66
- const parts = field.split('.');
64
+ if (field.includes(".")) {
65
+ const parts = field.split(".");
67
66
  return parts
68
67
  .map((part) => {
69
68
  part = part.trim();
70
- if (part === '*' || part.startsWith('`')) {
69
+ if (part === "*" || part.startsWith("`")) {
71
70
  return part;
72
71
  }
73
72
  return `\`${part}\``;
74
73
  })
75
- .join('.');
74
+ .join(".");
76
75
  }
77
76
 
78
77
  // 处理单个字段名
@@ -83,18 +82,18 @@ export class SqlBuilder {
83
82
  * 转义表名
84
83
  */
85
84
  private _escapeTable(table: string): string {
86
- if (typeof table !== 'string') {
85
+ if (typeof table !== "string") {
87
86
  return table;
88
87
  }
89
88
 
90
89
  table = table.trim();
91
90
 
92
- if (table.startsWith('`')) {
91
+ if (table.startsWith("`")) {
93
92
  return table;
94
93
  }
95
94
 
96
95
  // 处理表别名(表名 + 空格 + 别名)
97
- if (table.includes(' ')) {
96
+ if (table.includes(" ")) {
98
97
  const parts = table.split(/\s+/);
99
98
  if (parts.length === 2) {
100
99
  const tableName = parts[0].trim();
@@ -124,75 +123,75 @@ export class SqlBuilder {
124
123
  const escapedField = this._escapeField(fieldName);
125
124
 
126
125
  switch (operator) {
127
- case '$ne':
128
- case '$not':
126
+ case "$ne":
127
+ case "$not":
129
128
  this._validateParam(value);
130
129
  this._where.push(`${escapedField} != ?`);
131
130
  this._params.push(value);
132
131
  break;
133
132
 
134
- case '$in':
133
+ case "$in":
135
134
  if (!Array.isArray(value)) {
136
135
  throw new Error(`$in 操作符的值必须是数组 (operator: ${operator})`);
137
136
  }
138
137
  if (value.length === 0) {
139
138
  throw new Error(`$in 操作符的数组不能为空。提示:空数组会导致查询永远不匹配任何记录,这通常不是预期行为。请检查查询条件或移除该字段。`);
140
139
  }
141
- const placeholders = value.map(() => '?').join(',');
140
+ const placeholders = value.map(() => "?").join(",");
142
141
  this._where.push(`${escapedField} IN (${placeholders})`);
143
142
  this._params.push(...value);
144
143
  break;
145
144
 
146
- case '$nin':
147
- case '$notIn':
145
+ case "$nin":
146
+ case "$notIn":
148
147
  if (!Array.isArray(value)) {
149
148
  throw new Error(`$nin/$notIn 操作符的值必须是数组 (operator: ${operator})`);
150
149
  }
151
150
  if (value.length === 0) {
152
151
  throw new Error(`$nin/$notIn 操作符的数组不能为空。提示:空数组会导致查询匹配所有记录,这通常不是预期行为。请检查查询条件或移除该字段。`);
153
152
  }
154
- const placeholders2 = value.map(() => '?').join(',');
153
+ const placeholders2 = value.map(() => "?").join(",");
155
154
  this._where.push(`${escapedField} NOT IN (${placeholders2})`);
156
155
  this._params.push(...value);
157
156
  break;
158
157
 
159
- case '$like':
158
+ case "$like":
160
159
  this._validateParam(value);
161
160
  this._where.push(`${escapedField} LIKE ?`);
162
161
  this._params.push(value);
163
162
  break;
164
163
 
165
- case '$notLike':
164
+ case "$notLike":
166
165
  this._validateParam(value);
167
166
  this._where.push(`${escapedField} NOT LIKE ?`);
168
167
  this._params.push(value);
169
168
  break;
170
169
 
171
- case '$gt':
170
+ case "$gt":
172
171
  this._validateParam(value);
173
172
  this._where.push(`${escapedField} > ?`);
174
173
  this._params.push(value);
175
174
  break;
176
175
 
177
- case '$gte':
176
+ case "$gte":
178
177
  this._validateParam(value);
179
178
  this._where.push(`${escapedField} >= ?`);
180
179
  this._params.push(value);
181
180
  break;
182
181
 
183
- case '$lt':
182
+ case "$lt":
184
183
  this._validateParam(value);
185
184
  this._where.push(`${escapedField} < ?`);
186
185
  this._params.push(value);
187
186
  break;
188
187
 
189
- case '$lte':
188
+ case "$lte":
190
189
  this._validateParam(value);
191
190
  this._where.push(`${escapedField} <= ?`);
192
191
  this._params.push(value);
193
192
  break;
194
193
 
195
- case '$between':
194
+ case "$between":
196
195
  if (Array.isArray(value) && value.length === 2) {
197
196
  this._validateParam(value[0]);
198
197
  this._validateParam(value[1]);
@@ -201,7 +200,7 @@ export class SqlBuilder {
201
200
  }
202
201
  break;
203
202
 
204
- case '$notBetween':
203
+ case "$notBetween":
205
204
  if (Array.isArray(value) && value.length === 2) {
206
205
  this._validateParam(value[0]);
207
206
  this._validateParam(value[1]);
@@ -210,13 +209,13 @@ export class SqlBuilder {
210
209
  }
211
210
  break;
212
211
 
213
- case '$null':
212
+ case "$null":
214
213
  if (value === true) {
215
214
  this._where.push(`${escapedField} IS NULL`);
216
215
  }
217
216
  break;
218
217
 
219
- case '$notNull':
218
+ case "$notNull":
220
219
  if (value === true) {
221
220
  this._where.push(`${escapedField} IS NOT NULL`);
222
221
  }
@@ -234,7 +233,7 @@ export class SqlBuilder {
234
233
  * 处理复杂的 WHERE 条件对象
235
234
  */
236
235
  private _processWhereConditions(whereObj: WhereConditions): void {
237
- if (!whereObj || typeof whereObj !== 'object') {
236
+ if (!whereObj || typeof whereObj !== "object") {
238
237
  return;
239
238
  }
240
239
 
@@ -244,11 +243,11 @@ export class SqlBuilder {
244
243
  return;
245
244
  }
246
245
 
247
- if (key === '$and') {
246
+ if (key === "$and") {
248
247
  if (Array.isArray(value)) {
249
248
  value.forEach((condition) => this._processWhereConditions(condition));
250
249
  }
251
- } else if (key === '$or') {
250
+ } else if (key === "$or") {
252
251
  if (Array.isArray(value)) {
253
252
  const orConditions: string[] = [];
254
253
  const tempParams: SqlValue[] = [];
@@ -257,25 +256,25 @@ export class SqlBuilder {
257
256
  const tempBuilder = new SqlBuilder();
258
257
  tempBuilder._processWhereConditions(condition);
259
258
  if (tempBuilder._where.length > 0) {
260
- orConditions.push(`(${tempBuilder._where.join(' AND ')})`);
259
+ orConditions.push(`(${tempBuilder._where.join(" AND ")})`);
261
260
  tempParams.push(...tempBuilder._params);
262
261
  }
263
262
  });
264
263
 
265
264
  if (orConditions.length > 0) {
266
- this._where.push(`(${orConditions.join(' OR ')})`);
265
+ this._where.push(`(${orConditions.join(" OR ")})`);
267
266
  this._params.push(...tempParams);
268
267
  }
269
268
  }
270
- } else if (key.includes('$')) {
269
+ } else if (key.includes("$")) {
271
270
  // 一级属性格式:age$gt, role$in 等
272
- const lastDollarIndex = key.lastIndexOf('$');
271
+ const lastDollarIndex = key.lastIndexOf("$");
273
272
  const fieldName = key.substring(0, lastDollarIndex);
274
- const operator = ('$' + key.substring(lastDollarIndex + 1)) as WhereOperator;
273
+ const operator = ("$" + key.substring(lastDollarIndex + 1)) as WhereOperator;
275
274
  this._applyOperator(fieldName, operator, value);
276
275
  } else {
277
276
  // 检查值是否为对象(嵌套条件)
278
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
277
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
279
278
  // 嵌套条件:如 { age: { $gt: 18 } }
280
279
  for (const [op, val] of Object.entries(value)) {
281
280
  this._applyOperator(key, op as WhereOperator, val);
@@ -296,7 +295,7 @@ export class SqlBuilder {
296
295
  */
297
296
  getWhereConditions(): { sql: string; params: SqlValue[] } {
298
297
  return {
299
- sql: this._where.length > 0 ? this._where.join(' AND ') : '',
298
+ sql: this._where.length > 0 ? this._where.join(" AND ") : "",
300
299
  params: [...this._params]
301
300
  };
302
301
  }
@@ -304,13 +303,13 @@ export class SqlBuilder {
304
303
  /**
305
304
  * SELECT 字段
306
305
  */
307
- select(fields: string | string[] = '*'): this {
306
+ select(fields: string | string[] = "*"): this {
308
307
  if (Array.isArray(fields)) {
309
308
  this._select = [...this._select, ...fields.map((field) => this._escapeField(field))];
310
- } else if (typeof fields === 'string') {
309
+ } else if (typeof fields === "string") {
311
310
  this._select.push(this._escapeField(fields));
312
311
  } else {
313
- throw new Error('SELECT 字段必须是字符串或数组');
312
+ throw new Error("SELECT 字段必须是字符串或数组");
314
313
  }
315
314
  return this;
316
315
  }
@@ -319,7 +318,7 @@ export class SqlBuilder {
319
318
  * FROM 表名
320
319
  */
321
320
  from(table: string): this {
322
- if (typeof table !== 'string' || !table.trim()) {
321
+ if (typeof table !== "string" || !table.trim()) {
323
322
  throw new Error(`FROM 表名必须是非空字符串 (table: ${table})`);
324
323
  }
325
324
  this._from = this._escapeTable(table.trim());
@@ -330,14 +329,14 @@ export class SqlBuilder {
330
329
  * WHERE 条件
331
330
  */
332
331
  where(condition: WhereConditions | string, value?: SqlValue): this {
333
- if (typeof condition === 'object' && condition !== null) {
332
+ if (typeof condition === "object" && condition !== null) {
334
333
  this._processWhereConditions(condition);
335
334
  } else if (value !== undefined && value !== null) {
336
335
  this._validateParam(value);
337
336
  const escapedCondition = this._escapeField(condition as string);
338
337
  this._where.push(`${escapedCondition} = ?`);
339
338
  this._params.push(value);
340
- } else if (typeof condition === 'string') {
339
+ } else if (typeof condition === "string") {
341
340
  this._where.push(condition);
342
341
  }
343
342
  return this;
@@ -347,7 +346,7 @@ export class SqlBuilder {
347
346
  * LEFT JOIN
348
347
  */
349
348
  leftJoin(table: string, on: string): this {
350
- if (typeof table !== 'string' || typeof on !== 'string') {
349
+ if (typeof table !== "string" || typeof on !== "string") {
351
350
  throw new Error(`JOIN 表名和条件必须是字符串 (table: ${table}, on: ${on})`);
352
351
  }
353
352
  const escapedTable = this._escapeTable(table);
@@ -359,7 +358,7 @@ export class SqlBuilder {
359
358
  * RIGHT JOIN
360
359
  */
361
360
  rightJoin(table: string, on: string): this {
362
- if (typeof table !== 'string' || typeof on !== 'string') {
361
+ if (typeof table !== "string" || typeof on !== "string") {
363
362
  throw new Error(`JOIN 表名和条件必须是字符串 (table: ${table}, on: ${on})`);
364
363
  }
365
364
  const escapedTable = this._escapeTable(table);
@@ -371,7 +370,7 @@ export class SqlBuilder {
371
370
  * INNER JOIN
372
371
  */
373
372
  innerJoin(table: string, on: string): this {
374
- if (typeof table !== 'string' || typeof on !== 'string') {
373
+ if (typeof table !== "string" || typeof on !== "string") {
375
374
  throw new Error(`JOIN 表名和条件必须是字符串 (table: ${table}, on: ${on})`);
376
375
  }
377
376
  const escapedTable = this._escapeTable(table);
@@ -389,11 +388,11 @@ export class SqlBuilder {
389
388
  }
390
389
 
391
390
  fields.forEach((item) => {
392
- if (typeof item !== 'string' || !item.includes('#')) {
391
+ if (typeof item !== "string" || !item.includes("#")) {
393
392
  throw new Error(`orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${item})`);
394
393
  }
395
394
 
396
- const [fieldName, direction] = item.split('#');
395
+ const [fieldName, direction] = item.split("#");
397
396
  const cleanField = fieldName.trim();
398
397
  const cleanDir = direction.trim().toUpperCase() as OrderDirection;
399
398
 
@@ -401,7 +400,7 @@ export class SqlBuilder {
401
400
  throw new Error(`orderBy 中字段名不能为空 (item: ${item})`);
402
401
  }
403
402
 
404
- if (!['ASC', 'DESC'].includes(cleanDir)) {
403
+ if (!["ASC", "DESC"].includes(cleanDir)) {
405
404
  throw new Error(`ORDER BY 方向必须是 ASC 或 DESC (direction: ${cleanDir})`);
406
405
  }
407
406
 
@@ -417,9 +416,9 @@ export class SqlBuilder {
417
416
  */
418
417
  groupBy(field: string | string[]): this {
419
418
  if (Array.isArray(field)) {
420
- const escapedFields = field.filter((f) => typeof f === 'string').map((f) => this._escapeField(f));
419
+ const escapedFields = field.filter((f) => typeof f === "string").map((f) => this._escapeField(f));
421
420
  this._groupBy = [...this._groupBy, ...escapedFields];
422
- } else if (typeof field === 'string') {
421
+ } else if (typeof field === "string") {
423
422
  this._groupBy.push(this._escapeField(field));
424
423
  }
425
424
  return this;
@@ -429,7 +428,7 @@ export class SqlBuilder {
429
428
  * HAVING
430
429
  */
431
430
  having(condition: string): this {
432
- if (typeof condition === 'string') {
431
+ if (typeof condition === "string") {
433
432
  this._having.push(condition);
434
433
  }
435
434
  return this;
@@ -439,12 +438,12 @@ export class SqlBuilder {
439
438
  * LIMIT
440
439
  */
441
440
  limit(count: number, offset?: number): this {
442
- if (typeof count !== 'number' || count < 0) {
441
+ if (typeof count !== "number" || count < 0) {
443
442
  throw new Error(`LIMIT 数量必须是非负数 (count: ${count})`);
444
443
  }
445
444
  this._limit = Math.floor(count);
446
445
  if (offset !== undefined && offset !== null) {
447
- if (typeof offset !== 'number' || offset < 0) {
446
+ if (typeof offset !== "number" || offset < 0) {
448
447
  throw new Error(`OFFSET 必须是非负数 (offset: ${offset})`);
449
448
  }
450
449
  this._offset = Math.floor(offset);
@@ -456,7 +455,7 @@ export class SqlBuilder {
456
455
  * OFFSET
457
456
  */
458
457
  offset(count: number): this {
459
- if (typeof count !== 'number' || count < 0) {
458
+ if (typeof count !== "number" || count < 0) {
460
459
  throw new Error(`OFFSET 必须是非负数 (count: ${count})`);
461
460
  }
462
461
  this._offset = Math.floor(count);
@@ -467,33 +466,33 @@ export class SqlBuilder {
467
466
  * 构建 SELECT 查询
468
467
  */
469
468
  toSelectSql(): SqlQuery {
470
- let sql = 'SELECT ';
469
+ let sql = "SELECT ";
471
470
 
472
- sql += this._select.length > 0 ? this._select.join(', ') : '*';
471
+ sql += this._select.length > 0 ? this._select.join(", ") : "*";
473
472
 
474
473
  if (!this._from) {
475
- throw new Error('FROM 表名是必需的');
474
+ throw new Error("FROM 表名是必需的");
476
475
  }
477
476
  sql += ` FROM ${this._from}`;
478
477
 
479
478
  if (this._joins.length > 0) {
480
- sql += ' ' + this._joins.join(' ');
479
+ sql += " " + this._joins.join(" ");
481
480
  }
482
481
 
483
482
  if (this._where.length > 0) {
484
- sql += ' WHERE ' + this._where.join(' AND ');
483
+ sql += " WHERE " + this._where.join(" AND ");
485
484
  }
486
485
 
487
486
  if (this._groupBy.length > 0) {
488
- sql += ' GROUP BY ' + this._groupBy.join(', ');
487
+ sql += " GROUP BY " + this._groupBy.join(", ");
489
488
  }
490
489
 
491
490
  if (this._having.length > 0) {
492
- sql += ' HAVING ' + this._having.join(' AND ');
491
+ sql += " HAVING " + this._having.join(" AND ");
493
492
  }
494
493
 
495
494
  if (this._orderBy.length > 0) {
496
- sql += ' ORDER BY ' + this._orderBy.join(', ');
495
+ sql += " ORDER BY " + this._orderBy.join(", ");
497
496
  }
498
497
 
499
498
  if (this._limit !== null) {
@@ -510,11 +509,11 @@ export class SqlBuilder {
510
509
  * 构建 INSERT 查询
511
510
  */
512
511
  toInsertSql(table: string, data: InsertData): SqlQuery {
513
- if (!table || typeof table !== 'string') {
512
+ if (!table || typeof table !== "string") {
514
513
  throw new Error(`INSERT 需要表名 (table: ${table})`);
515
514
  }
516
515
 
517
- if (!data || typeof data !== 'object') {
516
+ if (!data || typeof data !== "object") {
518
517
  throw new Error(`INSERT 需要数据 (table: ${table}, data: ${JSON.stringify(data)})`);
519
518
  }
520
519
 
@@ -531,10 +530,10 @@ export class SqlBuilder {
531
530
  }
532
531
 
533
532
  const escapedFields = fields.map((field) => this._escapeField(field));
534
- const placeholders = fields.map(() => '?').join(', ');
535
- const values = data.map(() => `(${placeholders})`).join(', ');
533
+ const placeholders = fields.map(() => "?").join(", ");
534
+ const values = data.map(() => `(${placeholders})`).join(", ");
536
535
 
537
- const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(', ')}) VALUES ${values}`;
536
+ const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES ${values}`;
538
537
  const params = data.flatMap((row) => fields.map((field) => row[field]));
539
538
 
540
539
  return { sql, params };
@@ -545,8 +544,8 @@ export class SqlBuilder {
545
544
  }
546
545
 
547
546
  const escapedFields = fields.map((field) => this._escapeField(field));
548
- const placeholders = fields.map(() => '?').join(', ');
549
- const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(', ')}) VALUES (${placeholders})`;
547
+ const placeholders = fields.map(() => "?").join(", ");
548
+ const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES (${placeholders})`;
550
549
  const params = fields.map((field) => data[field]);
551
550
 
552
551
  return { sql, params };
@@ -557,29 +556,29 @@ export class SqlBuilder {
557
556
  * 构建 UPDATE 查询
558
557
  */
559
558
  toUpdateSql(table: string, data: UpdateData): SqlQuery {
560
- if (!table || typeof table !== 'string') {
561
- throw new Error('UPDATE 需要表名');
559
+ if (!table || typeof table !== "string") {
560
+ throw new Error("UPDATE 需要表名");
562
561
  }
563
562
 
564
- if (!data || typeof data !== 'object' || Array.isArray(data)) {
565
- throw new Error('UPDATE 需要数据对象');
563
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
564
+ throw new Error("UPDATE 需要数据对象");
566
565
  }
567
566
 
568
567
  const fields = Object.keys(data);
569
568
  if (fields.length === 0) {
570
- throw new Error('更新数据必须至少有一个字段');
569
+ throw new Error("更新数据必须至少有一个字段");
571
570
  }
572
571
 
573
572
  const escapedTable = this._escapeTable(table);
574
573
  const setFields = fields.map((field) => `${this._escapeField(field)} = ?`);
575
574
  const params: SqlValue[] = [...Object.values(data), ...this._params];
576
575
 
577
- let sql = `UPDATE ${escapedTable} SET ${setFields.join(', ')}`;
576
+ let sql = `UPDATE ${escapedTable} SET ${setFields.join(", ")}`;
578
577
 
579
578
  if (this._where.length > 0) {
580
- sql += ' WHERE ' + this._where.join(' AND ');
579
+ sql += " WHERE " + this._where.join(" AND ");
581
580
  } else {
582
- throw new Error('为安全起见,UPDATE 需要 WHERE 条件');
581
+ throw new Error("为安全起见,UPDATE 需要 WHERE 条件");
583
582
  }
584
583
 
585
584
  return { sql, params };
@@ -589,17 +588,17 @@ export class SqlBuilder {
589
588
  * 构建 DELETE 查询
590
589
  */
591
590
  toDeleteSql(table: string): SqlQuery {
592
- if (!table || typeof table !== 'string') {
593
- throw new Error('DELETE 需要表名');
591
+ if (!table || typeof table !== "string") {
592
+ throw new Error("DELETE 需要表名");
594
593
  }
595
594
 
596
595
  const escapedTable = this._escapeTable(table);
597
596
  let sql = `DELETE FROM ${escapedTable}`;
598
597
 
599
598
  if (this._where.length > 0) {
600
- sql += ' WHERE ' + this._where.join(' AND ');
599
+ sql += " WHERE " + this._where.join(" AND ");
601
600
  } else {
602
- throw new Error('为安全起见,DELETE 需要 WHERE 条件');
601
+ throw new Error("为安全起见,DELETE 需要 WHERE 条件");
603
602
  }
604
603
 
605
604
  return { sql, params: [...this._params] };
@@ -609,19 +608,19 @@ export class SqlBuilder {
609
608
  * 构建 COUNT 查询
610
609
  */
611
610
  toCountSql(): SqlQuery {
612
- let sql = 'SELECT COUNT(*) as total';
611
+ let sql = "SELECT COUNT(*) as total";
613
612
 
614
613
  if (!this._from) {
615
- throw new Error('COUNT 需要 FROM 表名');
614
+ throw new Error("COUNT 需要 FROM 表名");
616
615
  }
617
616
  sql += ` FROM ${this._from}`;
618
617
 
619
618
  if (this._joins.length > 0) {
620
- sql += ' ' + this._joins.join(' ');
619
+ sql += " " + this._joins.join(" ");
621
620
  }
622
621
 
623
622
  if (this._where.length > 0) {
624
- sql += ' WHERE ' + this._where.join(' AND ');
623
+ sql += " WHERE " + this._where.join(" AND ");
625
624
  }
626
625
 
627
626
  return { sql, params: [...this._params] };