befly 3.76.1 → 3.76.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/Befly.js +159 -0
  2. package/README.md +50 -11
  3. package/apis/admin/delete.js +1 -3
  4. package/apis/admin/select.js +2 -7
  5. package/apis/admin/update.js +1 -5
  6. package/apis/api/select.js +2 -7
  7. package/apis/auth/login.js +16 -20
  8. package/apis/dict/delete.js +1 -1
  9. package/apis/dict/detail.js +1 -3
  10. package/apis/dict/select.js +1 -2
  11. package/apis/dict/update.js +1 -1
  12. package/apis/dictType/delete.js +1 -1
  13. package/apis/dictType/detail.js +1 -1
  14. package/apis/dictType/insert.js +1 -1
  15. package/apis/dictType/select.js +2 -7
  16. package/apis/dictType/update.js +2 -2
  17. package/apis/email/config.js +4 -7
  18. package/apis/email/logList.js +2 -7
  19. package/apis/email/send.js +1 -2
  20. package/apis/email/verify.js +1 -3
  21. package/apis/loginLog/select.js +2 -7
  22. package/apis/menu/select.js +2 -7
  23. package/apis/operateLog/select.js +2 -7
  24. package/apis/role/apiSave.js +2 -2
  25. package/apis/role/menuSave.js +2 -2
  26. package/apis/role/select.js +2 -7
  27. package/apis/source/imageList.js +2 -5
  28. package/apis/tongJi/dailyReport.js +12 -13
  29. package/apis/tongJi/dailyReportSelect.js +1 -2
  30. package/apis/tongJi/dailyStats.js +1 -1
  31. package/apis/tongJi/dailyStatsDistribution.js +2 -2
  32. package/apis/tongJi/errorList.js +1 -2
  33. package/apis/tongJi/errorReport.js +1 -2
  34. package/apis/tongJi/projectDelete.js +1 -3
  35. package/apis/tongJi/projectSelect.js +2 -4
  36. package/apis/tongJi/projectUpdate.js +2 -2
  37. package/apis/upload/file.js +7 -5
  38. package/checks/api.js +43 -66
  39. package/checks/config.js +17 -12
  40. package/checks/corns.js +28 -46
  41. package/checks/field.js +18 -0
  42. package/checks/items.js +16 -60
  43. package/checks/menu.js +28 -39
  44. package/checks/table.js +106 -26
  45. package/checks/validation.js +13 -0
  46. package/configs/beflyConfig.json +6 -6
  47. package/configs/constConfig.js +2 -63
  48. package/exports.js +2 -0
  49. package/hooks/auth.js +11 -7
  50. package/hooks/parser.js +55 -59
  51. package/hooks/permission.js +1 -0
  52. package/hooks/rateLimit.js +25 -62
  53. package/hooks/validator.js +43 -31
  54. package/index.js +43 -344
  55. package/{lib → libs}/cacheHelper.js +3 -8
  56. package/libs/cron.js +60 -0
  57. package/libs/logger/index.js +1 -0
  58. package/libs/logger/logger.js +245 -0
  59. package/libs/logger/sanitize.js +115 -0
  60. package/{lib → libs/mysql}/dbHelper.js +100 -73
  61. package/{lib → libs/mysql}/dbParse.js +16 -9
  62. package/{lib → libs/mysql}/dbUtil.js +15 -32
  63. package/libs/mysql/error.js +29 -0
  64. package/libs/mysql/index.js +6 -0
  65. package/libs/mysql/is.js +29 -0
  66. package/libs/mysql/mysql.js +43 -0
  67. package/libs/mysql/sql/identifier.js +85 -0
  68. package/libs/mysql/sql/index.js +2 -0
  69. package/{lib → libs/mysql/sql}/sqlBuilder.js +2 -3
  70. package/libs/mysql/util.js +44 -0
  71. package/libs/redis/index.js +2 -0
  72. package/libs/redis/redis.js +204 -0
  73. package/libs/redis/redisError.js +8 -0
  74. package/libs/tableModel.js +72 -0
  75. package/libs/tool.js +33 -0
  76. package/libs/validator/compiler.js +248 -0
  77. package/libs/validator/index.js +10 -0
  78. package/libs/validator/parser.js +173 -0
  79. package/libs/validator/util.js +5 -0
  80. package/package.json +10 -8
  81. package/paths.js +2 -3
  82. package/plugins/cache.js +2 -1
  83. package/plugins/email.js +4 -4
  84. package/plugins/mysql.js +5 -15
  85. package/plugins/redis.js +4 -12
  86. package/router/api.js +14 -10
  87. package/router/root.js +14 -0
  88. package/router/static.js +3 -3
  89. package/schemas/api.json +99 -0
  90. package/schemas/config.json +255 -0
  91. package/schemas/corn.json +39 -0
  92. package/schemas/item.json +43 -0
  93. package/schemas/menu.json +44 -0
  94. package/schemas/table.json +80 -0
  95. package/scripts/syncDb/apply.js +87 -0
  96. package/scripts/syncDb/ddl.js +43 -0
  97. package/scripts/syncDb/index.js +175 -30
  98. package/scripts/syncDb/model.js +60 -0
  99. package/scripts/syncDb/plan.js +160 -0
  100. package/scripts/syncDb/preflight.js +98 -0
  101. package/scripts/syncDb/report.js +177 -162
  102. package/scripts/syncDb/snapshot.js +118 -0
  103. package/tables/admin.json +51 -53
  104. package/tables/api.json +28 -39
  105. package/tables/dailyReport.json +85 -73
  106. package/tables/dict.json +27 -39
  107. package/tables/dictType.json +20 -34
  108. package/tables/emailLog.json +49 -47
  109. package/tables/errorReport.json +86 -60
  110. package/tables/file.json +47 -50
  111. package/tables/loginLog.json +68 -55
  112. package/tables/menu.json +20 -34
  113. package/tables/operateLog.json +53 -47
  114. package/tables/project.json +18 -30
  115. package/tables/role.json +33 -37
  116. package/utils/cors.js +37 -41
  117. package/utils/deepMerge.js +3 -1
  118. package/utils/error.js +1 -56
  119. package/utils/id.js +44 -0
  120. package/utils/is.js +6 -243
  121. package/utils/response.js +12 -1
  122. package/utils/scanFiles.js +13 -4
  123. package/utils/scanSources.js +58 -20
  124. package/utils/util.js +11 -163
  125. package/apis/_apis.js +0 -20
  126. package/apis/auth/sendSmsCode.js +0 -24
  127. package/configs/regexpAlias.json +0 -55
  128. package/lib/connect.js +0 -119
  129. package/lib/logger.js +0 -430
  130. package/lib/redisHelper.js +0 -399
  131. package/lib/validator.js +0 -274
  132. package/lib/xmlParse.js +0 -149
  133. package/plugins/config.js +0 -13
  134. package/plugins/cron.js +0 -115
  135. package/plugins/logger.js +0 -17
  136. package/plugins/tool.js +0 -131
  137. package/plugins/xmlParse.js +0 -10
  138. package/schemas/config.js +0 -67
  139. package/scripts/syncDb/context.js +0 -120
  140. package/scripts/syncDb/diff.js +0 -279
  141. package/scripts/syncDb/transform.js +0 -113
  142. package/sql/befly.sql +0 -271
  143. package/utils/email.js +0 -3
  144. package/utils/fieldClear.js +0 -22
  145. package/utils/formatValidationIssues.js +0 -28
  146. package/utils/getClientIp.js +0 -47
  147. package/utils/loggerUtils.js +0 -196
  148. package/utils/regexpUtil.js +0 -52
  149. /package/{lib → libs}/smtpText.js +0 -0
@@ -1,11 +1,11 @@
1
- import { createError, getRootCause, validationError } from "../utils/error.js";
2
- import { fieldClear } from "../utils/fieldClear.js";
3
- import { isLowerCamelCaseName, isNonEmptyString, isNullable, isNumber, isPlainObject, isString } from "../utils/is.js";
4
- import { camelCase, canConvertToNumber, keysToSnake, snakeCase } from "../utils/util.js";
1
+ import { genId } from "../../utils/id.js";
2
+ import { Logger } from "../logger/index.js";
5
3
  import { DbParse } from "./dbParse.js";
6
4
  import { deserializeArrayFields, parseTableRef, serializeArrayFields } from "./dbUtil.js";
7
- import { Logger } from "./logger.js";
8
- import { SqlBuilder } from "./sqlBuilder.js";
5
+ import { createError, getRootCause, validationError } from "./error.js";
6
+ import { isLowerCamelCaseName, isNonEmptyString, isNullable, isNumber, isPlainObject, isString } from "./is.js";
7
+ import { SqlBuilder } from "./sql/index.js";
8
+ import { camelCase, canConvertToNumber, fieldClear, keysToSnake, snakeCase } from "./util.js";
9
9
 
10
10
  function quoteIdentMySql(identifier) {
11
11
  if (!isString(identifier)) {
@@ -221,7 +221,7 @@ function assertBatchInsertRowsConsistent(rows, options) {
221
221
 
222
222
  class DbHelper {
223
223
  constructor(options = {}) {
224
- this.redis = options.redis;
224
+ this.closeHandler = options.close;
225
225
 
226
226
  if (!isNonEmptyString(options.dbName)) {
227
227
  throw validationError("DbHelper 初始化失败:dbName 必须为非空字符串");
@@ -236,21 +236,24 @@ class DbHelper {
236
236
  this.queryTimeout = (options.queryTimeout || 30) * 1000;
237
237
  }
238
238
 
239
- getTableDef(table) {
239
+ getTableModel(table) {
240
240
  const tableName = parseTableRef(table).table;
241
241
 
242
- if (!isLowerCamelCaseName(tableName)) {
243
- throw validationError(`表名必须使用小驼峰写法 (table: ${tableName})`);
242
+ if (!isLowerCamelCaseName(tableName)) throw validationError(`表名必须使用小驼峰写法 (table: ${tableName})`);
243
+ const tableModel = this.tables[tableName];
244
+ if (!isPlainObject(tableModel) || !isPlainObject(tableModel.fields)) {
245
+ const sourceLabel = tableName.startsWith("befly") ? "opensource/core/tables" : "app tables";
246
+ throw createError(`表 ${tableName} 缺少标准 tables 模型,请检查 ${sourceLabel}`, { code: "runtime" });
244
247
  }
248
+ return tableModel;
249
+ }
245
250
 
246
- const tableInfo = this.tables[tableName];
247
-
248
- if (!isPlainObject(tableInfo)) {
249
- const sourceLabel = tableName.startsWith("befly") ? "packages/core/tables" : "app tables";
250
- throw createError(`表 ${tableName} 缺少本地 tables 定义,请检查 ${sourceLabel}`, { code: "runtime" });
251
- }
251
+ getTableMode(table) {
252
+ return parseTableRef(table).table.startsWith("befly") ? "auto" : this.beflyMode;
253
+ }
252
254
 
253
- return tableInfo;
255
+ getTableFields(table) {
256
+ return this.getTableModel(table).fields;
254
257
  }
255
258
 
256
259
  normalizeDbFieldNames(row) {
@@ -297,17 +300,20 @@ class DbHelper {
297
300
  }
298
301
  }
299
302
 
303
+ let queryTimedOut = false;
300
304
  try {
301
- // 查询超时兜底:SQL 响应丢失/挂起时显式报错,避免进程静默卡死
302
- const queryPromise = safeParams.length > 0 ? this.sql.unsafe(sql, safeParams) : this.sql.unsafe(sql);
303
- let timeoutId;
304
- const timeoutPromise = new Promise((_, reject) => {
305
- timeoutId = setTimeout(() => {
306
- reject(createError(`SQL 执行超时(${this.queryTimeout}ms): ${sql.slice(0, 100)}`, { code: "runtime", subsystem: "sql", operation: "execute", sqlInfo: { sql: sql, params: safeParams } }));
307
- }, this.queryTimeout);
308
- });
309
- const queryResult = await Promise.race([queryPromise, timeoutPromise]);
310
- clearTimeout(timeoutId);
305
+ const query = safeParams.length > 0 ? this.sql.unsafe(sql, safeParams) : this.sql.unsafe(sql);
306
+ const timeoutId = setTimeout(() => {
307
+ queryTimedOut = true;
308
+ query.cancel();
309
+ }, this.queryTimeout);
310
+
311
+ let queryResult;
312
+ try {
313
+ queryResult = await query;
314
+ } finally {
315
+ clearTimeout(timeoutId);
316
+ }
311
317
 
312
318
  const duration = Date.now() - startTime;
313
319
 
@@ -320,10 +326,19 @@ class DbHelper {
320
326
  }
321
327
  };
322
328
  } catch (error) {
329
+ const executeError = queryTimedOut
330
+ ? createError(`SQL 执行超时(${this.queryTimeout}ms): ${sql.slice(0, 100)}`, {
331
+ cause: error,
332
+ code: "runtime",
333
+ subsystem: "sql",
334
+ operation: "execute"
335
+ })
336
+ : error;
337
+
323
338
  const duration = Date.now() - startTime;
324
- const msg = getExecuteErrorMessage(error);
339
+ const msg = getExecuteErrorMessage(executeError);
325
340
 
326
- if (retryOnConnectionClosed && this.isConnectionClosedError(error, msg)) {
341
+ if (retryOnConnectionClosed && this.isConnectionClosedError(executeError, msg)) {
327
342
  return this.execute(sql, params, false);
328
343
  }
329
344
 
@@ -333,7 +348,7 @@ class DbHelper {
333
348
  duration: duration
334
349
  };
335
350
  throw createError(`SQL执行失败: ${msg}`, {
336
- cause: error,
351
+ cause: executeError,
337
352
  code: "runtime",
338
353
  subsystem: "sql",
339
354
  operation: "execute",
@@ -350,9 +365,6 @@ class DbHelper {
350
365
  if (rootCause?.code === "ERR_MYSQL_CONNECTION_CLOSED") {
351
366
  return true;
352
367
  }
353
- if (rootCause?.code === "ERR_POSTGRES_CONNECTION_CLOSED") {
354
- return true;
355
- }
356
368
  if (typeof msg === "string" && msg.includes("Connection closed")) {
357
369
  return true;
358
370
  }
@@ -396,13 +408,13 @@ class DbHelper {
396
408
  };
397
409
  }
398
410
 
399
- normalizeRowData(row) {
411
+ normalizeRowData(row, tableInfo = {}) {
400
412
  if (!row) {
401
413
  return {};
402
414
  }
403
415
 
404
416
  const camelRow = this.normalizeDbFieldNames(row);
405
- const deserialized = deserializeArrayFields(camelRow);
417
+ const deserialized = deserializeArrayFields(camelRow, tableInfo);
406
418
 
407
419
  const convertedList = normalizeBigIntValues([deserialized]);
408
420
  if (convertedList && convertedList.length > 0) {
@@ -412,20 +424,20 @@ class DbHelper {
412
424
  return deserialized;
413
425
  }
414
426
 
415
- normalizeListData(list) {
427
+ normalizeListData(list, tableInfo = {}) {
416
428
  const camelList = list.map((item) => this.normalizeDbFieldNames(item));
417
- const deserializedList = camelList.map((item) => deserializeArrayFields(item)).filter((item) => item !== null);
429
+ const deserializedList = camelList.map((item) => deserializeArrayFields(item, tableInfo)).filter((item) => item !== null);
418
430
  return normalizeBigIntValues(deserializedList);
419
431
  }
420
432
 
421
- prepareWriteInputData(data) {
422
- return serializeArrayFields(keysToSnake(fieldClear(data, { excludeValues: [undefined] })));
433
+ prepareWriteInputData(data, tableInfo = {}) {
434
+ return serializeArrayFields(keysToSnake(fieldClear(data, { excludeValues: [undefined] })), tableInfo);
423
435
  }
424
436
 
425
- prepareWriteUserData(data, allowState) {
437
+ prepareWriteUserData(data, allowState, tableInfo = {}) {
426
438
  const result = {};
427
439
 
428
- for (const [key, value] of Object.entries(this.prepareWriteInputData(data))) {
440
+ for (const [key, value] of Object.entries(this.prepareWriteInputData(data, tableInfo))) {
429
441
  if (key === "id") continue;
430
442
  if (key === "created_at") continue;
431
443
  if (key === "updated_at") continue;
@@ -438,7 +450,7 @@ class DbHelper {
438
450
  }
439
451
 
440
452
  buildInsertRow(options) {
441
- const result = options.beflyMode === "manual" ? this.prepareWriteInputData(options.data) : this.prepareWriteUserData(options.data, false);
453
+ const result = options.beflyMode === "manual" ? this.prepareWriteInputData(options.data, options.tableInfo) : this.prepareWriteUserData(options.data, false, options.tableInfo);
442
454
 
443
455
  if (options.beflyMode === "auto") {
444
456
  assertTimeIdValue(options.id);
@@ -455,7 +467,7 @@ class DbHelper {
455
467
  }
456
468
 
457
469
  buildUpdateRow(options) {
458
- const result = options.beflyMode === "manual" ? this.prepareWriteInputData(options.data) : this.prepareWriteUserData(options.data, options.allowState);
470
+ const result = options.beflyMode === "manual" ? this.prepareWriteInputData(options.data, options.tableInfo) : this.prepareWriteUserData(options.data, options.allowState, options.tableInfo);
459
471
  if (options.beflyMode !== "manual") {
460
472
  result.updated_at = options.now;
461
473
  }
@@ -463,7 +475,7 @@ class DbHelper {
463
475
  }
464
476
 
465
477
  async getTableColumns(table) {
466
- const tableInfo = this.getTableDef(table);
478
+ const tableInfo = this.getTableFields(table);
467
479
  const fieldNames = Object.keys(tableInfo);
468
480
 
469
481
  if (fieldNames.length === 0) {
@@ -484,14 +496,17 @@ class DbHelper {
484
496
  }
485
497
 
486
498
  async createInsertRows(table, snakeTable, dataList, now) {
487
- if (this.beflyMode === "manual") {
499
+ const tableInfo = this.getTableFields(table);
500
+ const tableMode = this.getTableMode(table);
501
+ if (tableMode === "manual") {
488
502
  return {
489
503
  ids: [],
490
504
  processedList: dataList.map((data) =>
491
505
  this.buildInsertRow({
492
506
  data: data,
493
507
  now: now,
494
- beflyMode: this.beflyMode
508
+ beflyMode: tableMode,
509
+ tableInfo: tableInfo
495
510
  })
496
511
  )
497
512
  };
@@ -500,15 +515,15 @@ class DbHelper {
500
515
  const ids = [];
501
516
  try {
502
517
  for (let i = 0; i < dataList.length; i++) {
503
- ids.push(await this.redis.genTimeID());
518
+ ids.push(genId());
504
519
  }
505
520
  } catch (error) {
506
521
  if (dataList.length === 1) {
507
- throw createError(`生成 ID 失败,Redis 可能不可用 (table: ${table})`, {
522
+ throw createError(`生成 ID 失败 (table: ${table})`, {
508
523
  cause: error,
509
524
  code: "runtime",
510
525
  subsystem: "db",
511
- operation: "genTimeID",
526
+ operation: "genId",
512
527
  table: table
513
528
  });
514
529
  }
@@ -525,7 +540,8 @@ class DbHelper {
525
540
  data: data,
526
541
  id: id,
527
542
  now: now,
528
- beflyMode: this.beflyMode
543
+ beflyMode: tableMode,
544
+ tableInfo: tableInfo
529
545
  });
530
546
  });
531
547
 
@@ -538,7 +554,7 @@ class DbHelper {
538
554
  resolveInsertedData(table, count, executeRes, generatedIds, operation) {
539
555
  const lastInsertRowidNum = normalizeSqlMetaNumber(executeRes.data?.lastInsertRowid);
540
556
 
541
- if (this.beflyMode === "manual") {
557
+ if (this.getTableMode(table) === "manual") {
542
558
  if (lastInsertRowidNum <= 0) {
543
559
  throw createError(operation === "insBatch" ? `批量插入失败:beflyMode=manual 时无法获取 lastInsertRowid (table: ${table})` : `插入失败:beflyMode=manual 时无法获取 lastInsertRowid (table: ${table})`, { code: "runtime" });
544
560
  }
@@ -581,7 +597,7 @@ class DbHelper {
581
597
  const executeRes = await this.execute(sql, params);
582
598
  const result = executeRes.data;
583
599
 
584
- const data = this.normalizeRowData(result?.[0] || null);
600
+ const data = this.normalizeRowData(result?.[0] || null, this.getTableFields(prepared.codeTable));
585
601
  return {
586
602
  data: data,
587
603
  sql: executeRes.sql
@@ -625,7 +641,7 @@ class DbHelper {
625
641
 
626
642
  return {
627
643
  data: {
628
- lists: this.normalizeListData(list),
644
+ lists: this.normalizeListData(list, this.getTableFields(prepared.codeTable)),
629
645
  total: total,
630
646
  page: prepared.page,
631
647
  limit: prepared.limit,
@@ -694,7 +710,7 @@ class DbHelper {
694
710
  });
695
711
  }
696
712
 
697
- const lists = this.normalizeListData(result);
713
+ const lists = this.normalizeListData(result, this.getTableFields(prepared.codeTable));
698
714
 
699
715
  return {
700
716
  data: {
@@ -721,8 +737,8 @@ class DbHelper {
721
737
  async insData(options) {
722
738
  const parsed = this.createDbParse().parseInsert(options);
723
739
  const { table, snakeTable, data } = parsed;
724
- const tableInfo = this.getTableDef(table);
725
- const inputData = this.prepareWriteInputData(data);
740
+ const tableInfo = this.getTableFields(table);
741
+ const inputData = this.prepareWriteInputData(data, tableInfo);
726
742
  assertWriteDataHasFields(inputData, "插入数据必须至少有一个字段", snakeTable);
727
743
  const now = Date.now();
728
744
  const insertRows = await this.createInsertRows(table, snakeTable, [data], now);
@@ -758,7 +774,7 @@ class DbHelper {
758
774
  assertInsertBatchSize(dataList.length, MAX_BATCH_SIZE);
759
775
 
760
776
  const snakeTable = parsed.snakeTable;
761
- const tableInfo = this.getTableDef(table);
777
+ const tableInfo = this.getTableFields(table);
762
778
  const now = Date.now();
763
779
  const insertRows = await this.createInsertRows(table, snakeTable, dataList, now);
764
780
  const processedList = insertRows.processedList;
@@ -800,9 +816,10 @@ class DbHelper {
800
816
  }
801
817
 
802
818
  const snakeTable = parsed.snakeTable;
819
+ const primaryField = snakeCase(this.getTableModel(table).primary || "id");
803
820
  const query = SqlBuilder.toDeleteInSql({
804
821
  table: snakeTable,
805
- idField: "id",
822
+ idField: primaryField,
806
823
  ids: ids,
807
824
  quoteIdent: quoteIdentMySql
808
825
  });
@@ -826,20 +843,24 @@ class DbHelper {
826
843
  }
827
844
 
828
845
  const snakeTable = parsed.snakeTable;
829
- const tableInfo = this.getTableDef(table);
846
+ const tableModel = this.getTableModel(table);
847
+ const tableInfo = tableModel.fields;
848
+ const tableMode = this.getTableMode(table);
849
+ const primaryCodeName = tableModel.primary || "id";
850
+ const primaryDbName = snakeCase(primaryCodeName);
830
851
  const now = Date.now();
831
852
 
832
853
  const processedList = [];
833
854
  const fieldSet = new Set();
834
855
 
835
856
  for (const item of dataList) {
836
- const userData = this.prepareWriteUserData(item.data, true);
857
+ const userData = this.prepareWriteUserData(item.data, true, tableInfo);
837
858
 
838
859
  for (const key of Object.keys(userData)) {
839
860
  fieldSet.add(key);
840
861
  }
841
862
 
842
- processedList.push({ id: item.id, data: userData });
863
+ processedList.push({ id: item[primaryCodeName], data: userData });
843
864
  }
844
865
 
845
866
  const fields = Array.from(fieldSet).toSorted();
@@ -850,21 +871,21 @@ class DbHelper {
850
871
  };
851
872
  }
852
873
  const writeFields = fields.slice();
853
- if (this.beflyMode === "auto") {
874
+ if (tableMode === "auto") {
854
875
  writeFields.push("updated_at");
855
876
  }
856
877
  assertWriteFieldNamesDefined(tableInfo, writeFields, table, "updBatch.dataList");
857
878
 
858
879
  const query = SqlBuilder.toUpdateCaseByIdSql({
859
880
  table: snakeTable,
860
- idField: "id",
881
+ idField: primaryDbName,
861
882
  rows: processedList,
862
883
  fields: fields,
863
884
  quoteIdent: quoteIdentMySql,
864
- updatedAtField: this.beflyMode === "auto" ? "updated_at" : "",
865
- updatedAtValue: this.beflyMode === "auto" ? now : null,
885
+ updatedAtField: tableMode === "auto" ? "updated_at" : "",
886
+ updatedAtValue: tableMode === "auto" ? now : null,
866
887
  stateField: "state",
867
- stateGtZero: this.beflyMode === "auto"
888
+ stateGtZero: tableMode === "auto"
868
889
  });
869
890
 
870
891
  const executeRes = await this.execute(query.sql, query.params);
@@ -877,15 +898,16 @@ class DbHelper {
877
898
 
878
899
  async updData(options) {
879
900
  const parsed = this.createDbParse().parseUpdate(options);
880
- const tableInfo = this.getTableDef(parsed.table);
881
- const inputData = this.prepareWriteInputData(parsed.data);
901
+ const tableInfo = this.getTableFields(parsed.table);
902
+ const inputData = this.prepareWriteInputData(parsed.data, tableInfo);
882
903
  assertWriteDataHasFields(inputData, "更新数据必须至少有一个字段", parsed.snakeTable);
883
904
 
884
905
  const processed = this.buildUpdateRow({
885
906
  data: parsed.data,
886
907
  now: Date.now(),
887
908
  allowState: true,
888
- beflyMode: this.beflyMode
909
+ beflyMode: this.getTableMode(parsed.table),
910
+ tableInfo: tableInfo
889
911
  });
890
912
  assertWriteDataHasFields(processed, "更新数据必须至少有一个字段", parsed.snakeTable);
891
913
  assertWriteFieldsDefined(tableInfo, processed, parsed.table, "updData.data");
@@ -902,11 +924,11 @@ class DbHelper {
902
924
 
903
925
  async delData(options) {
904
926
  const parsed = this.createDbParse().parseDelete(options, false);
905
- const tableInfo = this.getTableDef(parsed.table);
927
+ const tableInfo = this.getTableFields(parsed.table);
906
928
  let processed;
907
929
 
908
930
  if (parsed.deleteMode === "manual") {
909
- processed = this.prepareWriteInputData(parsed.data);
931
+ processed = this.prepareWriteInputData(parsed.data, tableInfo);
910
932
  assertWriteDataHasFields(processed, "delData 在 beflyMode=manual 时 data 必须至少有一个字段", parsed.snakeTable);
911
933
  } else {
912
934
  const now = Date.now();
@@ -945,7 +967,7 @@ class DbHelper {
945
967
 
946
968
  async increment(table, field, where, value = 1) {
947
969
  const parsed = this.createDbParse().parseIncrement(table, field, where, value, "increment");
948
- const tableInfo = this.getTableDef(table);
970
+ const tableInfo = this.getTableFields(table);
949
971
  assertWriteFieldNamesDefined(tableInfo, [parsed.snakeField], table, "increment.field");
950
972
 
951
973
  const builder = this.createSqlBuilder().where(parsed.where);
@@ -972,6 +994,11 @@ class DbHelper {
972
994
  return await this.increment(table, field, where, -value);
973
995
  }
974
996
 
997
+ async close() {
998
+ if (this.isTransaction) return;
999
+ await this.closeHandler?.();
1000
+ }
1001
+
975
1002
  async trans(callback) {
976
1003
  if (this.isTransaction) {
977
1004
  throw createError("不支持嵌套事务", { code: "policy" });
@@ -983,11 +1010,11 @@ class DbHelper {
983
1010
 
984
1011
  return await this.sql.begin(async (tx) => {
985
1012
  const trans = new this.constructor({
986
- redis: this.redis,
987
1013
  dbName: this.dbName,
988
1014
  sql: tx,
989
1015
  isTransaction: true,
990
1016
  beflyMode: this.beflyMode,
1017
+ queryTimeout: this.queryTimeout / 1000,
991
1018
  tables: this.tables
992
1019
  });
993
1020
  return await callback(trans);
@@ -1,7 +1,7 @@
1
- import { validationError } from "../utils/error.js";
2
- import { isNonEmptyString, isNullable, isPlainObject, isString } from "../utils/is.js";
3
- import { snakeCase } from "../utils/util.js";
4
1
  import { assertNoExprField, clearDeep, fieldsToSnake, normalizeQualifierField, orderByToSnake, parseFieldAliasParts, parseFlatWhereKey, parseTableRef, processJoinField, processJoinOn, processJoinOrderBy, processJoinWhere, whereKeysToSnake } from "./dbUtil.js";
2
+ import { validationError } from "./error.js";
3
+ import { isNonEmptyString, isNullable, isPlainObject, isString } from "./is.js";
4
+ import { snakeCase } from "./util.js";
5
5
 
6
6
  function assertNonEmptyString(value, label) {
7
7
  if (!isNonEmptyString(value)) {
@@ -577,6 +577,10 @@ export class DbParse {
577
577
  this.getTableColumns = options.getTableColumns;
578
578
  }
579
579
 
580
+ getTableMode(table) {
581
+ return parseTableRef(table).table.startsWith("befly") ? "auto" : this.beflyMode;
582
+ }
583
+
580
584
  async parseRead(options, mode) {
581
585
  const label = mode === "one" ? "getOne.options" : mode === "list" ? "getList.options" : "getAll.options";
582
586
  validateQueryOptions(options, label);
@@ -609,7 +613,7 @@ export class DbParse {
609
613
  }
610
614
 
611
615
  const snakeTable = snakeCase(Array.isArray(options.table) ? options.table[0] : options.table);
612
- const normalizedWhere = applyDefaultStateFilter(whereKeysToSnake(cleanWhere), snakeTable, false, this.beflyMode);
616
+ const normalizedWhere = applyDefaultStateFilter(whereKeysToSnake(cleanWhere), snakeTable, false, this.getTableMode(Array.isArray(options.table) ? options.table[0] : options.table));
613
617
  return {
614
618
  table: snakeTable,
615
619
  fields: [],
@@ -662,7 +666,7 @@ export class DbParse {
662
666
  const label = force ? "delForce" : "delData";
663
667
  assertNonEmptyString(options.table, `${label}.table`);
664
668
  validateWhereObject(options.where, `${label}.where`, true);
665
- const deleteMode = resolveDeleteMode(options.table, this.beflyMode);
669
+ const deleteMode = resolveDeleteMode(options.table, this.getTableMode(options.table));
666
670
  const result = {
667
671
  table: options.table,
668
672
  snakeTable: snakeCase(options.table),
@@ -719,10 +723,11 @@ export class DbParse {
719
723
  const normalizedTableRef = normalizeTableRef(mainTableRef);
720
724
  const mainQualifier = getTableQualifier(mainTableRef);
721
725
  const joins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
722
- const normalizedWhere = applyDefaultStateFilter(processJoinWhere(cleanWhere), mainQualifier, true, this.beflyMode);
726
+ const normalizedWhere = applyDefaultStateFilter(processJoinWhere(cleanWhere), mainQualifier, true, this.getTableMode(mainTableRef));
723
727
 
724
728
  return {
725
729
  table: normalizedTableRef,
730
+ codeTable: parseTableRef(mainTableRef).table,
726
731
  fields: processedFields,
727
732
  where: parseWhereObject(normalizedWhere),
728
733
  leftJoins: joins,
@@ -738,10 +743,11 @@ export class DbParse {
738
743
  const normalizedTableRef = normalizeTableRef(mainTableRef);
739
744
  const mainQualifier = getTableQualifier(mainTableRef);
740
745
  const joins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
741
- const normalizedWhere = applyDefaultStateFilter(processJoinWhere(cleanWhere), mainQualifier, true, this.beflyMode);
746
+ const normalizedWhere = applyDefaultStateFilter(processJoinWhere(cleanWhere), mainQualifier, true, this.getTableMode(mainTableRef));
742
747
 
743
748
  return {
744
749
  table: normalizedTableRef,
750
+ codeTable: parseTableRef(mainTableRef).table,
745
751
  fields: [],
746
752
  where: parseWhereObject(normalizedWhere),
747
753
  leftJoins: joins,
@@ -755,10 +761,11 @@ export class DbParse {
755
761
  const tableRef = Array.isArray(options.table) ? options.table[0] : options.table;
756
762
  const snakeTable = snakeCase(tableRef);
757
763
  const processedFields = await fieldsToSnake(tableRef, classifiedFields, this.getTableColumns);
758
- const normalizedWhere = applyDefaultStateFilter(whereKeysToSnake(cleanWhere), snakeTable, false, this.beflyMode);
764
+ const normalizedWhere = applyDefaultStateFilter(whereKeysToSnake(cleanWhere), snakeTable, false, this.getTableMode(tableRef));
759
765
 
760
766
  return {
761
767
  table: snakeTable,
768
+ codeTable: parseTableRef(tableRef).table,
762
769
  fields: processedFields,
763
770
  where: parseWhereObject(normalizedWhere),
764
771
  leftJoins: [],
@@ -768,7 +775,7 @@ export class DbParse {
768
775
  };
769
776
  }
770
777
 
771
- prepareSingleTableWhere(table, where, useDefaultStateFilter, required, label, beflyMode = this.beflyMode) {
778
+ prepareSingleTableWhere(table, where, useDefaultStateFilter, required, label, beflyMode = this.getTableMode(table)) {
772
779
  const snakeTable = snakeCase(table);
773
780
  const normalizedWhere = whereKeysToSnake(clearDeep(where || {}));
774
781
  if (required) {
@@ -1,6 +1,6 @@
1
- import { validationError } from "../utils/error.js";
2
- import { isFiniteNumber, isNonEmptyString, isNullable, isString } from "../utils/is.js";
3
- import { snakeCase } from "../utils/util.js";
1
+ import { validationError } from "./error.js";
2
+ import { isFiniteNumber, isNonEmptyString, isNullable, isString } from "./is.js";
3
+ import { camelCase, snakeCase } from "./util.js";
4
4
 
5
5
  export function assertNoExprField(field) {
6
6
  if (!isString(field)) {
@@ -613,45 +613,28 @@ export function whereKeysToSnake(where) {
613
613
  });
614
614
  }
615
615
 
616
- export function serializeArrayFields(data) {
616
+ export function serializeArrayFields(data, tableInfo = {}) {
617
617
  const serialized = {};
618
618
  for (const [key, value] of Object.entries(data)) {
619
- if (isNullable(value)) {
620
- serialized[key] = value;
621
- continue;
622
- }
623
- if (Array.isArray(value)) {
624
- serialized[key] = JSON.stringify(value);
625
- continue;
626
- }
627
- serialized[key] = value;
619
+ const fieldRule = tableInfo[camelCase(key)] || tableInfo[key];
620
+ const complexParam = ["array", "object", "record"].includes(fieldRule?.paramType);
621
+ serialized[key] = !isNullable(value) && fieldRule?.fieldType === "text" && complexParam && typeof value === "object" ? JSON.stringify(value) : value;
628
622
  }
629
623
  return serialized;
630
624
  }
631
625
 
632
- export function deserializeArrayFields(data) {
633
- if (!data) {
634
- return null;
635
- }
636
- const deserialized = {};
637
- for (const [key, value] of Object.entries(data)) {
638
- deserialized[key] = value;
639
- }
626
+ export function deserializeArrayFields(data, tableInfo = {}) {
627
+ if (!data) return null;
628
+ const deserialized = { ...data };
640
629
 
641
630
  for (const [key, value] of Object.entries(deserialized)) {
642
- if (!isString(value)) {
631
+ const fieldRule = tableInfo[camelCase(key)] || tableInfo[key];
632
+ if (!isString(value) || fieldRule?.fieldType !== "text" || !["array", "object", "record"].includes(fieldRule.paramType)) continue;
633
+ try {
634
+ deserialized[key] = JSON.parse(value);
635
+ } catch {
643
636
  continue;
644
637
  }
645
- if (value.startsWith("[") && value.endsWith("]")) {
646
- try {
647
- const parsed = JSON.parse(value);
648
- if (Array.isArray(parsed)) {
649
- deserialized[key] = parsed;
650
- }
651
- } catch {
652
- continue;
653
- }
654
- }
655
638
  }
656
639
 
657
640
  return deserialized;
@@ -0,0 +1,29 @@
1
+ export function createError(message, options = {}) {
2
+ const error = new Error(message, options.cause === undefined ? undefined : { cause: options.cause });
3
+ if (options.code) error.code = options.code;
4
+ if (options.subsystem) error.subsystem = options.subsystem;
5
+ if (options.operation) error.operation = options.operation;
6
+ for (const [key, value] of Object.entries(options)) {
7
+ if (key !== "cause" && key !== "code" && key !== "subsystem" && key !== "operation") error[key] = value;
8
+ }
9
+ return error;
10
+ }
11
+
12
+ export function validationError(message) {
13
+ return createError(message, { code: "validation" });
14
+ }
15
+
16
+ export function getRootCause(error) {
17
+ let current = error;
18
+ while (current?.cause instanceof Error) current = current.cause;
19
+ return current;
20
+ }
21
+
22
+ export class MysqlError extends Error {
23
+ constructor(message, options = {}) {
24
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
25
+ this.name = "MysqlError";
26
+ this.code = options.code || "runtime";
27
+ this.operation = options.operation;
28
+ }
29
+ }
@@ -0,0 +1,6 @@
1
+ export { DbHelper } from "./dbHelper.js";
2
+ export { DbParse, applyDefaultStateFilter, parseFieldSelection, parseJoinFieldSelection, parseLeftJoinItem } from "./dbParse.js";
3
+ export { clearDeep, deserializeArrayFields, fieldsToSnake, parseTableRef, processJoinOn, processJoinOrderBy, processJoinWhere, serializeArrayFields, whereKeysToSnake } from "./dbUtil.js";
4
+ export { MysqlError } from "./error.js";
5
+ export { createMysql } from "./mysql.js";
6
+ export { escapeField, escapeTable, resolveQuoteIdent, SqlBuilder } from "./sql/index.js";
@@ -0,0 +1,29 @@
1
+ export function isPlainObject(value) {
2
+ if (typeof value !== "object" || value === null) return false;
3
+ const proto = Object.getPrototypeOf(value);
4
+ return proto === Object.prototype || proto === null;
5
+ }
6
+
7
+ export function isFiniteNumber(value) {
8
+ return typeof value === "number" && Number.isFinite(value);
9
+ }
10
+
11
+ export function isString(value) {
12
+ return typeof value === "string";
13
+ }
14
+
15
+ export function isNumber(value) {
16
+ return Object.prototype.toString.call(value) === "[object Number]";
17
+ }
18
+
19
+ export function isNullable(value) {
20
+ return value === null || value === undefined;
21
+ }
22
+
23
+ export function isNonEmptyString(value) {
24
+ return typeof value === "string" && value.trim().length > 0;
25
+ }
26
+
27
+ export function isLowerCamelCaseName(value) {
28
+ return isNonEmptyString(value) && /^_?[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/.test(value);
29
+ }