befly 3.15.21 → 3.15.23
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/dist/befly.js +70 -42
- package/dist/befly.min.js +9 -10
- package/dist/index.js +15 -3
- package/dist/sync/syncTable.d.ts +2 -1
- package/dist/sync/syncTable.js +66 -45
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import { syncCache } from "./sync/syncCache";
|
|
|
26
26
|
import { syncDev } from "./sync/syncDev";
|
|
27
27
|
import { syncMenu } from "./sync/syncMenu";
|
|
28
28
|
import { SyncTable } from "./sync/syncTable";
|
|
29
|
-
import { isCoreError } from "./types/coreError";
|
|
29
|
+
import { CoreError, isCoreError } from "./types/coreError";
|
|
30
30
|
// 工具
|
|
31
31
|
import { calcPerfTime } from "./utils/calcPerfTime";
|
|
32
32
|
import { getProcessRole } from "./utils/processInfo";
|
|
@@ -226,10 +226,22 @@ export class Befly {
|
|
|
226
226
|
catch {
|
|
227
227
|
// ignore
|
|
228
228
|
}
|
|
229
|
-
|
|
229
|
+
// 重要:避免宿主入口再打印一遍相同错误。
|
|
230
|
+
// 约定:start() 失败后向上抛出的异常应可被识别为“已在 core 侧输出过日志”。
|
|
231
|
+
// - 若本身已是 CoreError:标记 logged=true 后原样抛出
|
|
232
|
+
// - 否则:包装成 CoreError(logged=true, cause=原始错误)
|
|
233
|
+
if (isCoreError(error)) {
|
|
234
|
+
error.logged = true;
|
|
230
235
|
throw error;
|
|
231
236
|
}
|
|
232
|
-
throw new
|
|
237
|
+
throw new CoreError({
|
|
238
|
+
kind: kind,
|
|
239
|
+
message: errMessage,
|
|
240
|
+
logged: true,
|
|
241
|
+
noLog: noLog,
|
|
242
|
+
meta: { subsystem: "start", operation: "start" },
|
|
243
|
+
cause: error
|
|
244
|
+
});
|
|
233
245
|
}
|
|
234
246
|
}
|
|
235
247
|
}
|
package/dist/sync/syncTable.d.ts
CHANGED
|
@@ -85,8 +85,9 @@ export declare class SyncTable {
|
|
|
85
85
|
private static buildFieldPlan;
|
|
86
86
|
private static buildSystemFieldPlan;
|
|
87
87
|
private static buildIndexPlan;
|
|
88
|
-
private static
|
|
88
|
+
private static getIncompatibleTypeChange;
|
|
89
89
|
private static collectIncompatibleTypeChanges;
|
|
90
|
+
private static throwIfIncompatibleTypeChanges;
|
|
90
91
|
private static truncateForLog;
|
|
91
92
|
static stripAlgorithmAndLock(stmt: string): string;
|
|
92
93
|
static buildDdlFallbackCandidates(stmt: string): Array<{
|
package/dist/sync/syncTable.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - core 仅支持 MySQL 8.0+
|
|
8
8
|
*/
|
|
9
9
|
import { Logger } from "../lib/logger";
|
|
10
|
+
import { CoreError, isCoreError } from "../types/coreError";
|
|
10
11
|
import { normalizeFieldDefinition } from "../utils/normalizeFieldDefinition";
|
|
11
12
|
import { escapeComment, normalizeColumnDefaultValue } from "../utils/sqlUtil";
|
|
12
13
|
import { snakeCase } from "../utils/util";
|
|
@@ -139,23 +140,8 @@ export class SyncTable {
|
|
|
139
140
|
}
|
|
140
141
|
tableTasks.push(task);
|
|
141
142
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
for (const change of incompatibleTypeChanges) {
|
|
145
|
-
lines.push(`- ${change.tableName}.${change.dbFieldName}: ${change.currentType} -> ${change.expectedType}`);
|
|
146
|
-
}
|
|
147
|
-
const msgLines = [];
|
|
148
|
-
msgLines.push("禁止字段类型变更(检测到不兼容/收缩变更):");
|
|
149
|
-
for (const line of lines) {
|
|
150
|
-
msgLines.push(line);
|
|
151
|
-
}
|
|
152
|
-
msgLines.push("说明: 仅允许同类型的宽化变更(如 TINYINT->SMALLINT->INT->BIGINT),以及部分兼容变更(如 VARCHAR->TEXT、CHAR/VARCHAR 互转、float->double)。");
|
|
153
|
-
msgLines.push("提示: 若确需收缩,请先手工迁移/清洗数据后再执行同步。");
|
|
154
|
-
// 预检/校验类错误:不在 syncTable 内部重复打印 error,让上层启动流程统一输出一次即可。
|
|
155
|
-
const err = new Error(msgLines.join("\n"));
|
|
156
|
-
err.__syncTableNoLog = true;
|
|
157
|
-
throw err;
|
|
158
|
-
}
|
|
143
|
+
// 预检阶段若已检测到不兼容/收缩变更:直接汇总抛错(不在 syncTable 内部重复打印)。
|
|
144
|
+
SyncTable.throwIfIncompatibleTypeChanges(incompatibleTypeChanges);
|
|
159
145
|
// 预检通过后:基于预检阶段缓存的元信息为每张“已存在的表”构建 plan。
|
|
160
146
|
// 说明:这里构建 plan 不会执行 DDL;仅用于后续 applyTablePlan。
|
|
161
147
|
for (const task of tableTasks) {
|
|
@@ -176,7 +162,13 @@ export class SyncTable {
|
|
|
176
162
|
task.plan = built.plan;
|
|
177
163
|
task.planSummary = built.summary;
|
|
178
164
|
task.planDetails = built.details;
|
|
165
|
+
// 双保险:若 plan 构建阶段发现不兼容/收缩变更,也统一汇总到同一列表。
|
|
166
|
+
for (const change of built.incompatibleTypeChanges) {
|
|
167
|
+
incompatibleTypeChanges.push(change);
|
|
168
|
+
}
|
|
179
169
|
}
|
|
170
|
+
// plan 阶段补充的兼容性检查:保证“遇到第一条 throw”不会中止汇总。
|
|
171
|
+
SyncTable.throwIfIncompatibleTypeChanges(incompatibleTypeChanges);
|
|
180
172
|
// 预检通过后,再执行实际同步(DDL)。
|
|
181
173
|
for (const task of tableTasks) {
|
|
182
174
|
const item = task.item;
|
|
@@ -254,6 +246,10 @@ export class SyncTable {
|
|
|
254
246
|
throw error;
|
|
255
247
|
}
|
|
256
248
|
// 预检/校验类错误:不在 syncTable 打印 error,直接抛给上层(例如 start())统一处理。
|
|
249
|
+
if (isCoreError(error) && error.noLog === true) {
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
// 预检/校验类错误:不在 syncTable 打印 error,直接抛给上层(例如 start())统一处理。
|
|
257
253
|
if (error?.__syncTableNoLog === true) {
|
|
258
254
|
throw error;
|
|
259
255
|
}
|
|
@@ -521,6 +517,7 @@ export class SyncTable {
|
|
|
521
517
|
const addedBusiness = fieldPlan.addedBusiness;
|
|
522
518
|
const modified = fieldPlan.modified;
|
|
523
519
|
const addedSystem = systemPlan.addedSystem;
|
|
520
|
+
const incompatibleTypeChanges = fieldPlan.incompatibleTypeChanges;
|
|
524
521
|
const plan = {
|
|
525
522
|
changed: alterClauses.length > 0 || indexActions.length > 0,
|
|
526
523
|
alterClauses: alterClauses,
|
|
@@ -537,7 +534,8 @@ export class SyncTable {
|
|
|
537
534
|
details: {
|
|
538
535
|
fieldChanges: fieldPlan.changeDetails,
|
|
539
536
|
indexChanges: indexPlan.indexActions
|
|
540
|
-
}
|
|
537
|
+
},
|
|
538
|
+
incompatibleTypeChanges: incompatibleTypeChanges
|
|
541
539
|
};
|
|
542
540
|
}
|
|
543
541
|
static buildFieldPlan(options) {
|
|
@@ -545,6 +543,7 @@ export class SyncTable {
|
|
|
545
543
|
let addedBusiness = 0;
|
|
546
544
|
let modified = 0;
|
|
547
545
|
const changeDetails = [];
|
|
546
|
+
const incompatibleTypeChanges = [];
|
|
548
547
|
for (const [fieldKey, fieldDef] of Object.entries(options.fields)) {
|
|
549
548
|
const dbFieldName = snakeCase(fieldKey);
|
|
550
549
|
if (options.existingColumns[dbFieldName]) {
|
|
@@ -552,7 +551,16 @@ export class SyncTable {
|
|
|
552
551
|
if (comparison.length > 0) {
|
|
553
552
|
modified = modified + 1;
|
|
554
553
|
changeDetails.push({ fieldKey: fieldKey, dbFieldName: dbFieldName, changes: comparison });
|
|
555
|
-
|
|
554
|
+
// 兼容性检查:不在这里 throw,避免“遇到第一条就中止导致无法汇总”。
|
|
555
|
+
// 统一由 run() 收集所有不兼容项后再一次性抛错。
|
|
556
|
+
const typeChange = comparison.find((c) => c.type === "datatype");
|
|
557
|
+
if (typeChange) {
|
|
558
|
+
const expectedType = SyncTable.getSqlType(fieldDef.type, fieldDef.max ?? null, fieldDef.unsigned ?? false, fieldDef.precision ?? null, fieldDef.scale ?? null);
|
|
559
|
+
const incompatible = SyncTable.getIncompatibleTypeChange(options.tableName, dbFieldName, String(typeChange.current ?? ""), expectedType);
|
|
560
|
+
if (incompatible) {
|
|
561
|
+
incompatibleTypeChanges.push(incompatible);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
556
564
|
// 简化实现:无论变更类型(含仅默认值变更),统一走 MODIFY COLUMN。
|
|
557
565
|
alterClauses.push(SyncTable.generateDDLClause(fieldKey, fieldDef, false));
|
|
558
566
|
}
|
|
@@ -562,7 +570,7 @@ export class SyncTable {
|
|
|
562
570
|
alterClauses.push(SyncTable.generateDDLClause(fieldKey, fieldDef, true));
|
|
563
571
|
}
|
|
564
572
|
}
|
|
565
|
-
return { alterClauses: alterClauses, addedBusiness: addedBusiness, modified: modified, changeDetails: changeDetails };
|
|
573
|
+
return { alterClauses: alterClauses, addedBusiness: addedBusiness, modified: modified, changeDetails: changeDetails, incompatibleTypeChanges: incompatibleTypeChanges };
|
|
566
574
|
}
|
|
567
575
|
static buildSystemFieldPlan(options) {
|
|
568
576
|
const alterClauses = [];
|
|
@@ -602,12 +610,9 @@ export class SyncTable {
|
|
|
602
610
|
];
|
|
603
611
|
return { indexActions: indexActions };
|
|
604
612
|
}
|
|
605
|
-
static
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
return;
|
|
609
|
-
const currentType = String(typeChange.current || "").toLowerCase();
|
|
610
|
-
const expectedType = SyncTable.getSqlType(fieldDef.type, fieldDef.max ?? null, fieldDef.unsigned ?? false, fieldDef.precision ?? null, fieldDef.scale ?? null).toLowerCase();
|
|
613
|
+
static getIncompatibleTypeChange(tableName, dbFieldName, currentTypeRaw, expectedTypeRaw) {
|
|
614
|
+
const currentType = String(currentTypeRaw || "").toLowerCase();
|
|
615
|
+
const expectedType = String(expectedTypeRaw || "").toLowerCase();
|
|
611
616
|
const currentBase = currentType
|
|
612
617
|
.replace(/\s*unsigned/gi, "")
|
|
613
618
|
.replace(/\([^)]*\)/g, "")
|
|
@@ -616,8 +621,15 @@ export class SyncTable {
|
|
|
616
621
|
.replace(/\s*unsigned/gi, "")
|
|
617
622
|
.replace(/\([^)]*\)/g, "")
|
|
618
623
|
.trim();
|
|
619
|
-
if (currentBase !== expectedBase && !SyncTable.isCompatibleTypeChange(currentType, expectedType))
|
|
620
|
-
|
|
624
|
+
if (currentBase !== expectedBase && !SyncTable.isCompatibleTypeChange(currentType, expectedType)) {
|
|
625
|
+
return {
|
|
626
|
+
tableName: tableName,
|
|
627
|
+
dbFieldName: dbFieldName,
|
|
628
|
+
currentType: String(currentTypeRaw ?? ""),
|
|
629
|
+
expectedType: String(expectedTypeRaw ?? "")
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
return null;
|
|
621
633
|
}
|
|
622
634
|
static collectIncompatibleTypeChanges(tableName, existingColumns, fields) {
|
|
623
635
|
const out = [];
|
|
@@ -630,27 +642,36 @@ export class SyncTable {
|
|
|
630
642
|
const typeChange = comparison.find((c) => c.type === "datatype");
|
|
631
643
|
if (!typeChange)
|
|
632
644
|
continue;
|
|
633
|
-
const
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
.
|
|
637
|
-
.replace(/\([^)]*\)/g, "")
|
|
638
|
-
.trim();
|
|
639
|
-
const expectedBase = expectedType
|
|
640
|
-
.replace(/\s*unsigned/gi, "")
|
|
641
|
-
.replace(/\([^)]*\)/g, "")
|
|
642
|
-
.trim();
|
|
643
|
-
if (currentBase !== expectedBase && !SyncTable.isCompatibleTypeChange(currentType, expectedType)) {
|
|
644
|
-
out.push({
|
|
645
|
-
tableName: tableName,
|
|
646
|
-
dbFieldName: dbFieldName,
|
|
647
|
-
currentType: String(typeChange.current ?? ""),
|
|
648
|
-
expectedType: String(typeChange.expected ?? "")
|
|
649
|
-
});
|
|
645
|
+
const expectedType = SyncTable.getSqlType(fieldDef.type, fieldDef.max ?? null, fieldDef.unsigned ?? false, fieldDef.precision ?? null, fieldDef.scale ?? null);
|
|
646
|
+
const incompatible = SyncTable.getIncompatibleTypeChange(tableName, dbFieldName, String(typeChange.current ?? ""), expectedType);
|
|
647
|
+
if (incompatible) {
|
|
648
|
+
out.push(incompatible);
|
|
650
649
|
}
|
|
651
650
|
}
|
|
652
651
|
return out;
|
|
653
652
|
}
|
|
653
|
+
static throwIfIncompatibleTypeChanges(incompatibleTypeChanges) {
|
|
654
|
+
if (incompatibleTypeChanges.length === 0) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const lines = [];
|
|
658
|
+
for (const change of incompatibleTypeChanges) {
|
|
659
|
+
lines.push(`- ${change.tableName}.${change.dbFieldName}: ${change.currentType} -> ${change.expectedType}`);
|
|
660
|
+
}
|
|
661
|
+
const msgLines = [];
|
|
662
|
+
msgLines.push("禁止字段类型变更(检测到不兼容/收缩变更):");
|
|
663
|
+
for (const line of lines) {
|
|
664
|
+
msgLines.push(line);
|
|
665
|
+
}
|
|
666
|
+
msgLines.push("说明: 仅允许同类型的宽化变更(如 TINYINT->SMALLINT->INT->BIGINT),以及部分兼容变更(如 VARCHAR->TEXT、CHAR/VARCHAR 互转、float->double)。");
|
|
667
|
+
msgLines.push("提示: 若确需收缩,请先手工迁移/清洗数据后再执行同步。");
|
|
668
|
+
throw new CoreError({
|
|
669
|
+
kind: "policy",
|
|
670
|
+
message: msgLines.join("\n"),
|
|
671
|
+
noLog: true,
|
|
672
|
+
meta: { subsystem: "syncTable", operation: "precheck" }
|
|
673
|
+
});
|
|
674
|
+
}
|
|
654
675
|
/* ---------------------------------------------------------------------- */
|
|
655
676
|
/* DDL 执行(失败时降级 algorithm/lock) */
|
|
656
677
|
/* ---------------------------------------------------------------------- */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.15.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.15.23",
|
|
4
|
+
"gitHead": "d1f9f15baf452a71fcebc220a9b2a20376616628",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|