befly 2.0.7 → 2.0.10

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 (3) hide show
  1. package/package.json +2 -2
  2. package/plugins/db.js +166 -85
  3. package/utils/curd.js +104 -113
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "2.0.7",
3
+ "version": "2.0.10",
4
4
  "description": "Buma - 为 Bun 专属打造的 API 接口框架核心引擎",
5
5
  "type": "module",
6
6
  "private": false,
@@ -51,5 +51,5 @@
51
51
  "README.md",
52
52
  "vitest.config.js"
53
53
  ],
54
- "gitHead": "f6e4038c4b81010d5898b4f61c132d9ac05882f2"
54
+ "gitHead": "e87ddebec60d7dcde96510b2177ec23719972f54"
55
55
  }
package/plugins/db.js CHANGED
@@ -101,75 +101,36 @@ export default {
101
101
  }
102
102
  }
103
103
 
104
- // 执行原始 SQL - 核心方法
105
- async execute(sql, params = []) {
104
+ // 私有方法:执行 SQL(支持传入连接对象)
105
+ async #executeWithConn(sql, params = [], conn = null) {
106
106
  if (!sql || typeof sql !== 'string') {
107
107
  throw new Error('SQL 语句是必需的');
108
108
  }
109
109
 
110
- let conn;
110
+ let providedConn = conn;
111
+ let shouldRelease = false;
112
+
111
113
  try {
112
- conn = await this.#pool.getConnection();
114
+ // 如果没有提供连接,从池中获取
115
+ if (!providedConn) {
116
+ providedConn = await this.#pool.getConnection();
117
+ shouldRelease = true;
118
+ }
113
119
 
114
120
  if (Env.MYSQL_DEBUG === 1) {
115
121
  Logger.debug('执行SQL:', { sql, params });
116
122
  }
117
123
 
118
- const result = await conn.query(sql, params);
124
+ const result = await providedConn.query(sql, params);
119
125
  return result;
120
126
  } catch (error) {
121
127
  Logger.error('SQL 执行失败:', { sql, params, error: error.message });
122
128
  throw error;
123
129
  } finally {
124
- if (conn) {
125
- try {
126
- conn.release();
127
- } catch (releaseError) {
128
- Logger.warn('连接释放警告:', releaseError.message);
129
- }
130
- }
131
- }
132
- }
133
-
134
- // 事务处理
135
- async transaction(callback) {
136
- if (typeof callback !== 'function') {
137
- throw new Error('事务回调函数是必需的');
138
- }
139
-
140
- let conn;
141
- try {
142
- conn = await this.#pool.getConnection();
143
- await conn.beginTransaction();
144
-
145
- // 为回调函数提供连接对象
146
- const txMethods = {
147
- query: async (sql, params = []) => {
148
- return await conn.query(sql, params);
149
- },
150
- execute: async (sql, params = []) => {
151
- return await conn.query(sql, params);
152
- }
153
- };
154
-
155
- const result = await callback(txMethods);
156
-
157
- await conn.commit();
158
- return result;
159
- } catch (error) {
160
- if (conn) {
161
- try {
162
- await conn.rollback();
163
- Logger.info('事务已回滚');
164
- } catch (rollbackError) {
165
- Logger.error('事务回滚失败:', rollbackError);
166
- }
167
- }
168
- throw error;
169
- } finally {
170
- if (conn) {
130
+ // 只有当连接是我们获取的时候才释放
131
+ if (shouldRelease && providedConn) {
171
132
  try {
172
- conn.release();
133
+ providedConn.release();
173
134
  } catch (releaseError) {
174
135
  Logger.warn('连接释放警告:', releaseError.message);
175
136
  }
@@ -177,18 +138,13 @@ export default {
177
138
  }
178
139
  }
179
140
 
180
- // 获取单条记录详情
181
- async getDetail(table, options = {}) {
141
+ // 私有方法:获取单条记录详情(支持传入连接对象)
142
+ async #getDetailWithConn(table, options = {}, conn = null) {
182
143
  if (!table || typeof table !== 'string') {
183
144
  throw new Error('表名是必需的');
184
145
  }
185
146
 
186
- const {
187
- //
188
- where = {},
189
- fields = '*',
190
- leftJoins = []
191
- } = typeof options === 'object' && !Array.isArray(options) ? options : { where: options };
147
+ const { where = {}, fields = '*', leftJoins = [] } = typeof options === 'object' && !Array.isArray(options) ? options : { where: options };
192
148
 
193
149
  try {
194
150
  const builder = createQueryBuilder().select(fields).from(table).where(where).limit(1);
@@ -206,7 +162,7 @@ export default {
206
162
  });
207
163
 
208
164
  const { sql, params } = builder.toSelectSql();
209
- const result = await this.execute(sql, params);
165
+ const result = await this.#executeWithConn(sql, params, conn);
210
166
  return result[0] || null;
211
167
  } catch (error) {
212
168
  Logger.error('getDetail 执行失败:', error);
@@ -214,8 +170,8 @@ export default {
214
170
  }
215
171
  }
216
172
 
217
- // 获取列表(支持分页)
218
- async getList(table, options = {}) {
173
+ // 私有方法:获取列表(支持传入连接对象)
174
+ async #getListWithConn(table, options = {}, conn = null) {
219
175
  if (!table || typeof table !== 'string') {
220
176
  throw new Error('表名是必需的');
221
177
  }
@@ -260,7 +216,7 @@ export default {
260
216
  }
261
217
 
262
218
  const { sql, params } = builder.toSelectSql();
263
- const list = await this.execute(sql, params);
219
+ const rows = await this.#executeWithConn(sql, params, conn);
264
220
 
265
221
  // 获取总数(如果需要分页)
266
222
  let total = 0;
@@ -280,12 +236,12 @@ export default {
280
236
  });
281
237
 
282
238
  const { sql: countSql, params: countParams } = countBuilder.toCountSql();
283
- const countResult = await this.execute(countSql, countParams);
239
+ const countResult = await this.#executeWithConn(countSql, countParams, conn);
284
240
  total = countResult[0]?.total || 0;
285
241
  }
286
242
 
287
243
  return {
288
- list: Array.isArray(list) ? list : [],
244
+ rows: Array.isArray(rows) ? rows : [],
289
245
  total,
290
246
  page: numPage,
291
247
  pageSize: numPageSize
@@ -296,8 +252,8 @@ export default {
296
252
  }
297
253
  }
298
254
 
299
- // 获取所有记录
300
- async getAll(table, options = {}) {
255
+ // 私有方法:获取所有记录(支持传入连接对象)
256
+ async #getAllWithConn(table, options = {}, conn = null) {
301
257
  if (!table || typeof table !== 'string') {
302
258
  throw new Error('表名是必需的');
303
259
  }
@@ -324,7 +280,7 @@ export default {
324
280
  }
325
281
 
326
282
  const { sql, params } = builder.toSelectSql();
327
- const result = await this.execute(sql, params);
283
+ const result = await this.#executeWithConn(sql, params, conn);
328
284
  return Array.isArray(result) ? result : [];
329
285
  } catch (error) {
330
286
  Logger.error('getAll 执行失败:', error);
@@ -332,8 +288,8 @@ export default {
332
288
  }
333
289
  }
334
290
 
335
- // 插入数据 - 增强版,自动添加 ID 和时间戳
336
- async insData(table, data) {
291
+ // 私有方法:插入数据(支持传入连接对象)
292
+ async #insDataWithConn(table, data, conn = null) {
337
293
  if (!table || typeof table !== 'string') {
338
294
  throw new Error('表名是必需的');
339
295
  }
@@ -346,15 +302,15 @@ export default {
346
302
  const processedData = await this.#processDataForInsert(data);
347
303
  const builder = createQueryBuilder();
348
304
  const { sql, params } = builder.toInsertSql(table, processedData);
349
- return await this.execute(sql, params);
305
+ return await this.#executeWithConn(sql, params, conn);
350
306
  } catch (error) {
351
307
  Logger.error('insData 执行失败:', error);
352
308
  throw error;
353
309
  }
354
310
  }
355
311
 
356
- // 更新数据 - 增强版,自动添加 updated_at,过滤敏感字段
357
- async upData(table, data, where) {
312
+ // 私有方法:更新数据(支持传入连接对象)
313
+ async #upDataWithConn(table, data, where, conn = null) {
358
314
  if (!table || typeof table !== 'string') {
359
315
  throw new Error('表名是必需的');
360
316
  }
@@ -379,15 +335,15 @@ export default {
379
335
 
380
336
  const builder = createQueryBuilder().where(where);
381
337
  const { sql, params } = builder.toUpdateSql(table, updateData);
382
- return await this.execute(sql, params);
338
+ return await this.#executeWithConn(sql, params, conn);
383
339
  } catch (error) {
384
340
  Logger.error('upData 执行失败:', error);
385
341
  throw error;
386
342
  }
387
343
  }
388
344
 
389
- // 删除数据
390
- async delData(table, where) {
345
+ // 私有方法:删除数据(支持传入连接对象)
346
+ async #delDataWithConn(table, where, conn = null) {
391
347
  if (!table || typeof table !== 'string') {
392
348
  throw new Error('表名是必需的');
393
349
  }
@@ -399,15 +355,15 @@ export default {
399
355
  try {
400
356
  const builder = createQueryBuilder().where(where);
401
357
  const { sql, params } = builder.toDeleteSql(table);
402
- return await this.execute(sql, params);
358
+ return await this.#executeWithConn(sql, params, conn);
403
359
  } catch (error) {
404
360
  Logger.error('delData 执行失败:', error);
405
361
  throw error;
406
362
  }
407
363
  }
408
364
 
409
- // 批量插入 - 增强版,自动添加 ID 和时间戳
410
- async insBatch(table, dataArray) {
365
+ // 私有方法:批量插入(支持传入连接对象)
366
+ async #insBatchWithConn(table, dataArray, conn = null) {
411
367
  if (!table || typeof table !== 'string') {
412
368
  throw new Error('表名是必需的');
413
369
  }
@@ -420,15 +376,15 @@ export default {
420
376
  const processedDataArray = await this.#processDataForInsert(dataArray);
421
377
  const builder = createQueryBuilder();
422
378
  const { sql, params } = builder.toInsertSql(table, processedDataArray);
423
- return await this.execute(sql, params);
379
+ return await this.#executeWithConn(sql, params, conn);
424
380
  } catch (error) {
425
381
  Logger.error('insBatch 执行失败:', error);
426
382
  throw error;
427
383
  }
428
384
  }
429
385
 
430
- // 获取记录总数
431
- async getCount(table, options = {}) {
386
+ // 私有方法:获取记录总数(支持传入连接对象)
387
+ async #getCountWithConn(table, options = {}, conn = null) {
432
388
  if (!table || typeof table !== 'string') {
433
389
  throw new Error('表名是必需的');
434
390
  }
@@ -451,7 +407,7 @@ export default {
451
407
  });
452
408
 
453
409
  const { sql, params } = builder.toCountSql();
454
- const result = await this.execute(sql, params);
410
+ const result = await this.#executeWithConn(sql, params, conn);
455
411
  return result[0]?.total || 0;
456
412
  } catch (error) {
457
413
  Logger.error('getCount 执行失败:', error);
@@ -459,6 +415,131 @@ export default {
459
415
  }
460
416
  }
461
417
 
418
+ // 执行原始 SQL - 核心方法
419
+ async execute(sql, params = []) {
420
+ return await this.#executeWithConn(sql, params);
421
+ }
422
+
423
+ // 获取单条记录详情
424
+ async getDetail(table, options = {}) {
425
+ return await this.#getDetailWithConn(table, options);
426
+ }
427
+
428
+ // 获取列表(支持分页)
429
+ async getList(table, options = {}) {
430
+ return await this.#getListWithConn(table, options);
431
+ }
432
+
433
+ // 获取所有记录
434
+ async getAll(table, options = {}) {
435
+ return await this.#getAllWithConn(table, options);
436
+ }
437
+
438
+ // 插入数据 - 增强版,自动添加 ID 和时间戳
439
+ async insData(table, data) {
440
+ return await this.#insDataWithConn(table, data);
441
+ }
442
+
443
+ // 更新数据 - 增强版,自动添加 updated_at,过滤敏感字段
444
+ async upData(table, data, where) {
445
+ return await this.#upDataWithConn(table, data, where);
446
+ }
447
+
448
+ // 删除数据
449
+ async delData(table, where) {
450
+ return await this.#delDataWithConn(table, where);
451
+ }
452
+
453
+ // 批量插入 - 增强版,自动添加 ID 和时间戳
454
+ async insBatch(table, dataArray) {
455
+ return await this.#insBatchWithConn(table, dataArray);
456
+ }
457
+
458
+ // 获取记录总数
459
+ async getCount(table, options = {}) {
460
+ return await this.#getCountWithConn(table, options);
461
+ }
462
+
463
+ // 事务处理
464
+ async trans(callback) {
465
+ if (typeof callback !== 'function') {
466
+ throw new Error('事务回调函数是必需的');
467
+ }
468
+
469
+ let conn;
470
+ try {
471
+ conn = await this.#pool.getConnection();
472
+ await conn.beginTransaction();
473
+
474
+ // 为回调函数提供连接对象和高级方法
475
+ const txMethods = {
476
+ // 原始SQL执行方法
477
+ query: async (sql, params = []) => {
478
+ return await conn.query(sql, params);
479
+ },
480
+ execute: async (sql, params = []) => {
481
+ return await conn.query(sql, params);
482
+ },
483
+
484
+ // 高级数据操作方法 - 直接调用私有方法,传入事务连接
485
+ getDetail: async (table, options = {}) => {
486
+ return await this.#getDetailWithConn(table, options, conn);
487
+ },
488
+
489
+ getList: async (table, options = {}) => {
490
+ return await this.#getListWithConn(table, options, conn);
491
+ },
492
+
493
+ getAll: async (table, options = {}) => {
494
+ return await this.#getAllWithConn(table, options, conn);
495
+ },
496
+
497
+ insData: async (table, data) => {
498
+ return await this.#insDataWithConn(table, data, conn);
499
+ },
500
+
501
+ upData: async (table, data, where) => {
502
+ return await this.#upDataWithConn(table, data, where, conn);
503
+ },
504
+
505
+ delData: async (table, where) => {
506
+ return await this.#delDataWithConn(table, where, conn);
507
+ },
508
+
509
+ getCount: async (table, options = {}) => {
510
+ return await this.#getCountWithConn(table, options, conn);
511
+ },
512
+
513
+ insBatch: async (table, dataArray) => {
514
+ return await this.#insBatchWithConn(table, dataArray, conn);
515
+ }
516
+ };
517
+
518
+ const result = await callback(txMethods);
519
+
520
+ await conn.commit();
521
+ return result;
522
+ } catch (error) {
523
+ if (conn) {
524
+ try {
525
+ await conn.rollback();
526
+ Logger.info('事务已回滚');
527
+ } catch (rollbackError) {
528
+ Logger.error('事务回滚失败:', rollbackError);
529
+ }
530
+ }
531
+ throw error;
532
+ } finally {
533
+ if (conn) {
534
+ try {
535
+ conn.release();
536
+ } catch (releaseError) {
537
+ Logger.warn('连接释放警告:', releaseError.message);
538
+ }
539
+ }
540
+ }
541
+ }
542
+
462
543
  // 获取连接池状态
463
544
  getPoolStatus() {
464
545
  return {
package/utils/curd.js CHANGED
@@ -58,6 +58,7 @@ export class SqlBuilder {
58
58
  if (value === undefined) {
59
59
  return;
60
60
  }
61
+
61
62
  if (key === '$and') {
62
63
  if (Array.isArray(value)) {
63
64
  value.forEach((condition) => this._processWhereConditions(condition));
@@ -81,83 +82,85 @@ export class SqlBuilder {
81
82
  this._params.push(...tempParams);
82
83
  }
83
84
  }
84
- } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
85
- // 字段级操作符
86
- Object.entries(value).forEach(([operator, operatorValue]) => {
87
- this._validateParam(operatorValue);
88
-
89
- switch (operator) {
90
- case '$ne':
91
- case '$not':
92
- this._where.push(`${key} != ?`);
93
- this._params.push(operatorValue);
94
- break;
95
- case '$in':
96
- if (Array.isArray(operatorValue) && operatorValue.length > 0) {
97
- const placeholders = operatorValue.map(() => '?').join(',');
98
- this._where.push(`${key} IN (${placeholders})`);
99
- this._params.push(...operatorValue);
100
- }
101
- break;
102
- case '$nin':
103
- case '$notIn':
104
- if (Array.isArray(operatorValue) && operatorValue.length > 0) {
105
- const placeholders = operatorValue.map(() => '?').join(',');
106
- this._where.push(`${key} NOT IN (${placeholders})`);
107
- this._params.push(...operatorValue);
108
- }
109
- break;
110
- case '$like':
111
- this._where.push(`${key} LIKE ?`);
112
- this._params.push(operatorValue);
113
- break;
114
- case '$notLike':
115
- this._where.push(`${key} NOT LIKE ?`);
116
- this._params.push(operatorValue);
117
- break;
118
- case '$gt':
119
- this._where.push(`${key} > ?`);
120
- this._params.push(operatorValue);
121
- break;
122
- case '$gte':
123
- this._where.push(`${key} >= ?`);
124
- this._params.push(operatorValue);
125
- break;
126
- case '$lt':
127
- this._where.push(`${key} < ?`);
128
- this._params.push(operatorValue);
129
- break;
130
- case '$lte':
131
- this._where.push(`${key} <= ?`);
132
- this._params.push(operatorValue);
133
- break;
134
- case '$between':
135
- if (Array.isArray(operatorValue) && operatorValue.length === 2) {
136
- this._where.push(`${key} BETWEEN ? AND ?`);
137
- this._params.push(operatorValue[0], operatorValue[1]);
138
- }
139
- break;
140
- case '$notBetween':
141
- if (Array.isArray(operatorValue) && operatorValue.length === 2) {
142
- this._where.push(`${key} NOT BETWEEN ? AND ?`);
143
- this._params.push(operatorValue[0], operatorValue[1]);
144
- }
145
- break;
146
- case '$null':
147
- if (operatorValue === true) {
148
- this._where.push(`${key} IS NULL`);
149
- }
150
- break;
151
- case '$notNull':
152
- if (operatorValue === true) {
153
- this._where.push(`${key} IS NOT NULL`);
154
- }
155
- break;
156
- default:
157
- this._where.push(`${key} = ?`);
158
- this._params.push(operatorValue);
159
- }
160
- });
85
+ } else if (key.includes('$')) {
86
+ // 一级属性格式:age$gt, role$in 等
87
+ const lastDollarIndex = key.lastIndexOf('$');
88
+ const fieldName = key.substring(0, lastDollarIndex);
89
+ const operator = '$' + key.substring(lastDollarIndex + 1);
90
+
91
+ this._validateParam(value);
92
+
93
+ switch (operator) {
94
+ case '$ne':
95
+ case '$not':
96
+ this._where.push(`${fieldName} != ?`);
97
+ this._params.push(value);
98
+ break;
99
+ case '$in':
100
+ if (Array.isArray(value) && value.length > 0) {
101
+ const placeholders = value.map(() => '?').join(',');
102
+ this._where.push(`${fieldName} IN (${placeholders})`);
103
+ this._params.push(...value);
104
+ }
105
+ break;
106
+ case '$nin':
107
+ case '$notIn':
108
+ if (Array.isArray(value) && value.length > 0) {
109
+ const placeholders = value.map(() => '?').join(',');
110
+ this._where.push(`${fieldName} NOT IN (${placeholders})`);
111
+ this._params.push(...value);
112
+ }
113
+ break;
114
+ case '$like':
115
+ this._where.push(`${fieldName} LIKE ?`);
116
+ this._params.push(value);
117
+ break;
118
+ case '$notLike':
119
+ this._where.push(`${fieldName} NOT LIKE ?`);
120
+ this._params.push(value);
121
+ break;
122
+ case '$gt':
123
+ this._where.push(`${fieldName} > ?`);
124
+ this._params.push(value);
125
+ break;
126
+ case '$gte':
127
+ this._where.push(`${fieldName} >= ?`);
128
+ this._params.push(value);
129
+ break;
130
+ case '$lt':
131
+ this._where.push(`${fieldName} < ?`);
132
+ this._params.push(value);
133
+ break;
134
+ case '$lte':
135
+ this._where.push(`${fieldName} <= ?`);
136
+ this._params.push(value);
137
+ break;
138
+ case '$between':
139
+ if (Array.isArray(value) && value.length === 2) {
140
+ this._where.push(`${fieldName} BETWEEN ? AND ?`);
141
+ this._params.push(value[0], value[1]);
142
+ }
143
+ break;
144
+ case '$notBetween':
145
+ if (Array.isArray(value) && value.length === 2) {
146
+ this._where.push(`${fieldName} NOT BETWEEN ? AND ?`);
147
+ this._params.push(value[0], value[1]);
148
+ }
149
+ break;
150
+ case '$null':
151
+ if (value === true) {
152
+ this._where.push(`${fieldName} IS NULL`);
153
+ }
154
+ break;
155
+ case '$notNull':
156
+ if (value === true) {
157
+ this._where.push(`${fieldName} IS NOT NULL`);
158
+ }
159
+ break;
160
+ default:
161
+ this._where.push(`${fieldName} = ?`);
162
+ this._params.push(value);
163
+ }
161
164
  } else {
162
165
  // 简单的等于条件
163
166
  this._validateParam(value);
@@ -189,43 +192,31 @@ export class SqlBuilder {
189
192
  return this;
190
193
  }
191
194
 
192
- orderBy(field, direction = 'ASC') {
193
- if (Array.isArray(field)) {
194
- field.forEach((item) => {
195
- if (typeof item === 'string' && item.includes('#')) {
196
- const [fieldName, dir] = item.split('#');
197
- const cleanDir = (dir || 'ASC').trim().toUpperCase();
198
- if (!['ASC', 'DESC'].includes(cleanDir)) {
199
- throw new Error('ORDER BY direction must be ASC or DESC');
200
- }
201
- this._orderBy.push(`${fieldName.trim()} ${cleanDir}`);
202
- } else if (Array.isArray(item) && item.length >= 1) {
203
- const [fieldName, dir] = item;
204
- const cleanDir = (dir || 'ASC').toUpperCase();
205
- if (!['ASC', 'DESC'].includes(cleanDir)) {
206
- throw new Error('ORDER BY direction must be ASC or DESC');
207
- }
208
- this._orderBy.push(`${fieldName} ${cleanDir}`);
209
- } else if (typeof item === 'string') {
210
- this._orderBy.push(`${item} ASC`);
211
- }
212
- });
213
- } else if (typeof field === 'string') {
214
- if (field.includes('#')) {
215
- const [fieldName, dir] = field.split('#');
216
- const cleanDir = (dir || 'ASC').trim().toUpperCase();
217
- if (!['ASC', 'DESC'].includes(cleanDir)) {
218
- throw new Error('ORDER BY direction must be ASC or DESC');
219
- }
220
- this._orderBy.push(`${fieldName.trim()} ${cleanDir}`);
221
- } else {
222
- const cleanDir = direction.toUpperCase();
223
- if (!['ASC', 'DESC'].includes(cleanDir)) {
224
- throw new Error('ORDER BY direction must be ASC or DESC');
225
- }
226
- this._orderBy.push(`${field} ${cleanDir}`);
227
- }
195
+ orderBy(fields) {
196
+ if (!Array.isArray(fields)) {
197
+ throw new Error('orderBy must be an array of strings in "field#direction" format');
228
198
  }
199
+
200
+ fields.forEach((item) => {
201
+ if (typeof item !== 'string' || !item.includes('#')) {
202
+ throw new Error('orderBy field must be a string in "field#direction" format (e.g., "name#ASC", "id#DESC")');
203
+ }
204
+
205
+ const [fieldName, direction] = item.split('#');
206
+ const cleanField = fieldName.trim();
207
+ const cleanDir = direction.trim().toUpperCase();
208
+
209
+ if (!cleanField) {
210
+ throw new Error('Field name cannot be empty in orderBy');
211
+ }
212
+
213
+ if (!['ASC', 'DESC'].includes(cleanDir)) {
214
+ throw new Error('ORDER BY direction must be ASC or DESC');
215
+ }
216
+
217
+ this._orderBy.push(`${cleanField} ${cleanDir}`);
218
+ });
219
+
229
220
  return this;
230
221
  }
231
222