befly 3.76.4 → 3.76.6

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.76.4",
3
+ "version": "3.76.6",
4
4
  "gitHead": "49c39d36695036e85fc64083cc43c1652fff96cb",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
@@ -30,7 +30,7 @@ async function buildPlan(mysql, targetTables, tableNames) {
30
30
  export async function syncDb(mysqlConfig, options = {}) {
31
31
  const apply = options.apply === true;
32
32
  const list = options.list === true;
33
- const tableNames = Array.isArray(options.tableNames) ? options.tableNames.filter(Boolean) : options.tableName ? [options.tableName] : [];
33
+ const tableNames = [...new Set((Array.isArray(options.tableNames) ? options.tableNames : options.tableName ? [options.tableName] : []).filter(Boolean))];
34
34
  const tableScope = tableNames.length > 0 ? tableNames.join(", ") : "";
35
35
  const startedAt = nowText();
36
36
  let client;
@@ -33,7 +33,7 @@ export function selectTables(tables, tableNames = []) {
33
33
 
34
34
  const selectedNameSet = new Set(selectedNames);
35
35
  const selectedTables = Object.fromEntries(Object.entries(tables).filter(([key, table]) => selectedNameSet.has(key) || selectedNameSet.has(table.codeName) || selectedNameSet.has(table.fileName)));
36
- const matchedNames = new Set(Object.values(selectedTables).flatMap((table) => [table.codeName, table.fileName]));
36
+ const matchedNames = new Set(Object.entries(selectedTables).flatMap(([key, table]) => [key, table.codeName, table.fileName]));
37
37
  const missingName = selectedNames.find((name) => !matchedNames.has(name));
38
38
  if (missingName) throw new TypeError(`找不到 Table 定义:${missingName}`);
39
39
  return selectedTables;
@@ -15,21 +15,28 @@ function targetDbType(fieldType) {
15
15
  return { integer: "bigint", number: "decimal", varchar: "varchar", text: "mediumtext" }[fieldType];
16
16
  }
17
17
 
18
+ const compatibleFieldTypeSources = {
19
+ integer: new Set(["integer", "tinyint", "smallint", "mediumint", "int"]),
20
+ number: new Set(["number"]),
21
+ varchar: new Set(["varchar", "char"]),
22
+ text: new Set(["text", "tinytext", "longtext", "json", "varchar", "char"])
23
+ };
24
+
18
25
  function compatibleFieldType(target, actual) {
19
- const sources = {
20
- integer: new Set(["integer", "tinyint", "smallint", "mediumint", "int"]),
21
- number: new Set(["number"]),
22
- varchar: new Set(["varchar", "char"]),
23
- text: new Set(["text", "tinytext", "longtext", "json", "varchar", "char"])
24
- };
25
- return sources[target]?.has(actual) === true;
26
+ return compatibleFieldTypeSources[target]?.has(actual) === true;
26
27
  }
27
28
 
28
29
  function columnChanges(target, actual) {
29
30
  const changes = [];
30
- for (const property of ["fieldType", "maxValue", "precision", "scale", "nullable", "name", "unsigned"]) {
31
- if (target[property] !== actual[property]) changes.push({ property: property, before: actual[property], after: target[property] });
31
+ if (target.fieldType !== actual.fieldType) changes.push({ property: "fieldType", before: actual.fieldType, after: target.fieldType });
32
+ if (target.fieldType === "varchar" && target.maxValue !== actual.maxValue) changes.push({ property: "maxValue", before: actual.maxValue, after: target.maxValue });
33
+ if (target.fieldType === "number") {
34
+ if (target.precision !== actual.precision) changes.push({ property: "precision", before: actual.precision, after: target.precision });
35
+ if (target.scale !== actual.scale) changes.push({ property: "scale", before: actual.scale, after: target.scale });
32
36
  }
37
+ if (target.nullable !== actual.nullable) changes.push({ property: "nullable", before: actual.nullable, after: target.nullable });
38
+ if (target.name !== actual.name) changes.push({ property: "name", before: actual.name, after: target.name });
39
+ if (target.unsigned !== actual.unsigned) changes.push({ property: "unsigned", before: actual.unsigned, after: target.unsigned });
33
40
  if (!sameDefault(target.default, actual.default)) changes.push({ property: "default", before: actual.default, after: target.default });
34
41
  return changes;
35
42
  }
@@ -68,8 +75,7 @@ export function updatePlanHash(plan) {
68
75
  }
69
76
 
70
77
  export function buildSchemaPlan(targetTables, actualTables, { tableNames = [], singleTable = false } = {}) {
71
- const selectedTableNames = Array.isArray(tableNames) ? tableNames : tableNames ? [tableNames] : [];
72
- const restrictUnmanagedTables = selectedTableNames.length > 0 || singleTable;
78
+ const restrictUnmanagedTables = singleTable || (Array.isArray(tableNames) ? tableNames.length > 0 : Boolean(tableNames));
73
79
  const plan = { operations: [], notices: [], blocked: [], counts: {} };
74
80
 
75
81
  for (const target of Object.values(targetTables)) {
@@ -2,6 +2,8 @@ import { mkdir } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
3
 
4
4
  function formatValue(value) {
5
+ if (value === true) return "是";
6
+ if (value === false) return "否";
5
7
  if (value === "") return '""';
6
8
  if (value === null || value === undefined) return "无";
7
9
  if (value && typeof value === "object") {
@@ -39,18 +41,55 @@ function colorText(text, color, enabled) {
39
41
  return ansi ? `${ansi}${text}\x1b[0m` : text;
40
42
  }
41
43
 
44
+ const fieldTypeProperties = new Set(["fieldType", "maxValue", "precision", "scale"]);
45
+
46
+ const propertyNames = {
47
+ nullable: "是否可空",
48
+ name: "注释",
49
+ default: "默认值",
50
+ unsigned: "无符号",
51
+ type: "索引类型",
52
+ fields: "索引字段"
53
+ };
54
+
55
+ const indexTypeNames = { index: "普通", unique: "唯一" };
56
+
42
57
  function targetFieldTypeText(field) {
43
58
  if (!field || typeof field !== "object") return "-";
44
59
  return field.fieldType === "integer" ? "BIGINT" : field.fieldType === "number" ? `DECIMAL(${field.precision},${field.scale})` : field.fieldType === "varchar" ? `VARCHAR(${field.maxValue})` : field.fieldType === "text" ? "MEDIUMTEXT" : "-";
45
60
  }
46
61
 
62
+ function actualFieldTypeText(field) {
63
+ if (!field || typeof field !== "object") return "-";
64
+ const dbType = String(field.dbType || "").toLowerCase();
65
+ if (["tinyint", "smallint", "mediumint", "int", "bigint"].includes(dbType)) return dbType.toUpperCase();
66
+ if (["decimal", "numeric"].includes(dbType)) return `${dbType.toUpperCase()}(${field.precision},${field.scale})`;
67
+ if (["varchar", "char"].includes(dbType)) return `${dbType.toUpperCase()}(${field.maxValue})`;
68
+ if (dbType) return dbType.toUpperCase();
69
+ return targetFieldTypeText(field);
70
+ }
71
+
47
72
  function fieldTypeText(operation) {
48
73
  if (!["addColumn", "alterColumn"].includes(operation.kind)) return "-";
49
- return targetFieldTypeText(operation.after);
74
+ const after = targetFieldTypeText(operation.after);
75
+ if (operation.kind === "addColumn" || !operation.before) return after;
76
+ const before = actualFieldTypeText(operation.before);
77
+ return before === "-" || before === after ? after : `${before} -> ${after}`;
78
+ }
79
+
80
+ function blockedFieldTypeText(blocked) {
81
+ const target = blocked.target;
82
+ if (target && typeof target === "object" && target.fieldType && typeof blocked.before === "string") {
83
+ const before = blocked.before.toUpperCase();
84
+ const after = targetFieldTypeText(target);
85
+ return before === after ? after : `${before} -> ${after}`;
86
+ }
87
+ return targetFieldTypeText(target || blocked.after);
50
88
  }
51
89
 
52
90
  function preflightResult(operation, colors) {
53
- return colorText(operation.preflight?.blocked ? "" : "是", operation.preflight?.blocked ? "red" : "limegreen", colors);
91
+ if (!operation.preflight) return "-";
92
+ return colorText(operation.preflight.blocked ? "否" : "是", operation.preflight.blocked ? "red" : "limegreen", colors);
54
93
  }
55
94
 
56
95
  function buildSyncRows(plan, colors) {
@@ -70,13 +109,14 @@ function buildSyncRows(plan, colors) {
70
109
  }));
71
110
  for (const notice of plan.notices.filter((item) => !createdTables.has(item.table))) rows.push({ 类型: colorText("保留", "deepskyblue", colors), 风险: "-", 表: notice.table, 对象: notice.object || "-", 字段类型: "-", 变更: notice.message, 预检结果: "-" });
72
111
  for (const blocked of plan.blocked.filter((item) => !createdTables.has(item.table))) {
73
- const change = blocked.before !== undefined || blocked.after !== undefined ? `${formatValue(blocked.before)} -> ${formatValue(blocked.after)}` : blocked.reason;
112
+ const hasBooleanCompare = typeof blocked.before === "boolean" || typeof blocked.after === "boolean";
113
+ const change = blocked.kind === "preflight" || hasBooleanCompare ? blocked.reason : blocked.before !== undefined || blocked.after !== undefined ? `${formatValue(blocked.before)} -> ${formatValue(blocked.after)}` : blocked.reason;
74
114
  rows.push({
75
115
  类型: colorText("禁止同步", "orangered", colors),
76
116
  风险: colorText("阻塞", "orangered", colors),
77
117
  表: blocked.table,
78
118
  对象: blocked.object || "-",
79
- 字段类型: targetFieldTypeText(blocked.target || blocked.after),
119
+ 字段类型: blockedFieldTypeText(blocked),
80
120
  变更: change,
81
121
  预检结果: blocked.kind === "preflight" ? colorText("否", "red", colors) : "-"
82
122
  });
@@ -139,11 +179,26 @@ function escapeTableValue(value) {
139
179
 
140
180
  function archiveChangeText(operation) {
141
181
  if (operation.kind === "createTable") return "创建全新表";
142
- if (operation.kind === "addColumn") return `新增字段:${operation.after.dbName},类型=${operation.after.fieldType},NULL=${operation.after.nullable ? "是" : "否"},默认值=${formatValue(operation.after.default)}`;
182
+ if (operation.kind === "addColumn") return `是否可空=${operation.after.nullable ? "是" : "否"},默认值=${formatValue(operation.after.default)}`;
143
183
  if (operation.kind === "addIndex") return `新增${operation.after.type === "unique" ? "唯一" : "普通"}索引:${operation.after.fields.join(", ")}`;
144
- if (operation.kind === "replaceIndex") return `${formatValue(operation.before)} -> ${formatValue(operation.after)}`;
184
+ if (operation.kind === "replaceIndex") {
185
+ return operation.changes
186
+ .map((change) => {
187
+ const label = propertyNames[change.property] || change.property;
188
+ if (change.property === "type") return `${label}: ${indexTypeNames[change.before] || change.before} -> ${indexTypeNames[change.after] || change.after}`;
189
+ const before = Array.isArray(change.before) ? change.before.join(", ") : formatValue(change.before);
190
+ const after = Array.isArray(change.after) ? change.after.join(", ") : formatValue(change.after);
191
+ return `${label}: ${before} -> ${after}`;
192
+ })
193
+ .join(";");
194
+ }
145
195
  if (operation.kind === "dropIndex") return `删除未在 Tables 中声明的索引:${operation.object}`;
146
- return operation.changes.map((change) => `${change.property}: ${formatValue(change.before)} -> ${formatValue(change.after)}`).join(";");
196
+ return (
197
+ operation.changes
198
+ .filter((change) => !fieldTypeProperties.has(change.property))
199
+ .map((change) => `${propertyNames[change.property] || change.property}: ${formatValue(change.before)} -> ${formatValue(change.after)}`)
200
+ .join(";") || "-"
201
+ );
147
202
  }
148
203
 
149
204
  function renderArchive(context) {