befly 3.22.8 → 3.23.0
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/apis/auth/login.js +1 -1
- package/checks/api.js +11 -8
- package/checks/config.js +12 -9
- package/checks/hook.js +9 -5
- package/checks/menu.js +11 -8
- package/checks/plugin.js +9 -5
- package/checks/table.js +39 -10
- package/index.js +13 -22
- package/lib/connect.js +37 -37
- package/lib/dbHelper.js +43 -102
- package/lib/dbParse.js +49 -49
- package/lib/dbUtil.js +16 -30
- package/lib/logger.js +16 -18
- package/lib/sqlBuilder.js +39 -66
- package/package.json +5 -5
- package/scripts/syncDb/diff.js +25 -10
- package/scripts/syncDb/transform.js +2 -2
- package/sync/api.js +30 -47
- package/sync/menu.js +24 -44
- package/sync/syncUtil.js +35 -0
- package/utils/util.js +13 -0
package/lib/dbParse.js
CHANGED
|
@@ -39,10 +39,6 @@ function validateWhereObject(where, label, required = false) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const visitWhereMeta = (currentWhere, currentLabel) => {
|
|
42
|
-
if (!isPlainObject(currentWhere)) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
42
|
for (const [key, value] of Object.entries(currentWhere)) {
|
|
47
43
|
if (key === "$exclude") {
|
|
48
44
|
if (isNullable(value)) {
|
|
@@ -198,42 +194,44 @@ function validateQueryOptions(options, label) {
|
|
|
198
194
|
});
|
|
199
195
|
}
|
|
200
196
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
197
|
+
const tableList = Array.isArray(options.table) ? options.table : [options.table];
|
|
198
|
+
|
|
199
|
+
if (Array.isArray(options.table) && tableList.length === 0) {
|
|
200
|
+
throw new Error(`${label}.table 不能为空数组`, {
|
|
201
|
+
cause: null,
|
|
202
|
+
code: "validation"
|
|
203
|
+
});
|
|
204
|
+
}
|
|
208
205
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
206
|
+
for (let i = 0; i < tableList.length; i++) {
|
|
207
|
+
if (Array.isArray(options.table)) {
|
|
208
|
+
assertNonEmptyString(tableList[i], `${label}.table[${i}]`);
|
|
209
|
+
} else {
|
|
210
|
+
assertNonEmptyString(tableList[i], `${label}.table`);
|
|
212
211
|
}
|
|
213
212
|
|
|
213
|
+
parseTableRef(tableList[i]);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (Array.isArray(options.table)) {
|
|
214
217
|
if (Array.isArray(options.leftJoin) && options.leftJoin.length > 0) {
|
|
215
|
-
if (
|
|
218
|
+
if (tableList.length !== options.leftJoin.length + 1) {
|
|
216
219
|
throw new Error(`${label}.table 与 ${label}.leftJoin 数量不匹配,要求 table.length = leftJoin.length + 1`, {
|
|
217
220
|
cause: null,
|
|
218
221
|
code: "validation"
|
|
219
222
|
});
|
|
220
223
|
}
|
|
221
|
-
} else if (
|
|
224
|
+
} else if (tableList.length > 1) {
|
|
222
225
|
throw new Error(`${label}.table 为数组时必须配合 leftJoin 使用`, {
|
|
223
226
|
cause: null,
|
|
224
227
|
code: "validation"
|
|
225
228
|
});
|
|
226
229
|
}
|
|
227
|
-
} else {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
throw new Error(`${label}.leftJoin 启用时,${label}.table 必须是数组`, {
|
|
233
|
-
cause: null,
|
|
234
|
-
code: "validation"
|
|
235
|
-
});
|
|
236
|
-
}
|
|
230
|
+
} else if (Array.isArray(options.leftJoin) && options.leftJoin.length > 0) {
|
|
231
|
+
throw new Error(`${label}.leftJoin 启用时,${label}.table 必须是数组`, {
|
|
232
|
+
cause: null,
|
|
233
|
+
code: "validation"
|
|
234
|
+
});
|
|
237
235
|
}
|
|
238
236
|
|
|
239
237
|
if (!isNullable(options.where)) {
|
|
@@ -739,6 +737,18 @@ function applyDefaultStateFilter(where = {}, table, hasLeftJoin = false, beflyMo
|
|
|
739
737
|
return result;
|
|
740
738
|
}
|
|
741
739
|
|
|
740
|
+
function isBeflySystemTable(table) {
|
|
741
|
+
return parseTableRef(table).table.startsWith("befly");
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function resolveDeleteMode(table, beflyMode = "auto") {
|
|
745
|
+
if (beflyMode === "manual" && isBeflySystemTable(table)) {
|
|
746
|
+
return "auto";
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return beflyMode;
|
|
750
|
+
}
|
|
751
|
+
|
|
742
752
|
function parseLeftJoinItem(joinTable, joinItem) {
|
|
743
753
|
const parts = joinItem.split(" ");
|
|
744
754
|
return {
|
|
@@ -834,25 +844,6 @@ export class DbParse {
|
|
|
834
844
|
return this._prepareSingleTableWhere(options.table, options.where, true, false, "exists");
|
|
835
845
|
}
|
|
836
846
|
|
|
837
|
-
async parseFieldValue(options) {
|
|
838
|
-
validateNoLeftJoinReadOptions(options, "getFieldValue", "getFieldValue 不支持 leftJoin(请使用 getOne/getList 并自行取字段)");
|
|
839
|
-
assertSafeFieldName(options.field);
|
|
840
|
-
|
|
841
|
-
const readOptions = {
|
|
842
|
-
table: options.table,
|
|
843
|
-
fields: [options.field]
|
|
844
|
-
};
|
|
845
|
-
if (options.where !== undefined) readOptions.where = options.where;
|
|
846
|
-
if (options.orderBy !== undefined) readOptions.orderBy = options.orderBy;
|
|
847
|
-
if (options.page !== undefined) readOptions.page = options.page;
|
|
848
|
-
if (options.limit !== undefined) readOptions.limit = options.limit;
|
|
849
|
-
|
|
850
|
-
return {
|
|
851
|
-
field: options.field,
|
|
852
|
-
prepared: await this.parseRead(readOptions, "one")
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
|
|
856
847
|
parseInsert(options) {
|
|
857
848
|
assertNonEmptyString(options.table, "insData.table");
|
|
858
849
|
assertPlainObjectValue(options.data, "insData.data");
|
|
@@ -889,11 +880,20 @@ export class DbParse {
|
|
|
889
880
|
const label = force ? "delForce" : "delData";
|
|
890
881
|
assertNonEmptyString(options.table, `${label}.table`);
|
|
891
882
|
validateWhereObject(options.where, `${label}.where`, true);
|
|
892
|
-
|
|
883
|
+
const deleteMode = resolveDeleteMode(options.table, this.beflyMode);
|
|
884
|
+
const result = {
|
|
893
885
|
table: options.table,
|
|
894
886
|
snakeTable: snakeCase(options.table),
|
|
895
|
-
where: this._prepareSingleTableWhere(options.table, options.where, !force, true, label).where
|
|
887
|
+
where: this._prepareSingleTableWhere(options.table, options.where, !force, true, label, deleteMode).where,
|
|
888
|
+
deleteMode: deleteMode
|
|
896
889
|
};
|
|
890
|
+
|
|
891
|
+
if (!force && deleteMode === "manual") {
|
|
892
|
+
assertPlainObjectValue(options.data, "delData.data");
|
|
893
|
+
result.data = options.data;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return result;
|
|
897
897
|
}
|
|
898
898
|
|
|
899
899
|
parseDelForceBatch(table, ids) {
|
|
@@ -989,13 +989,13 @@ export class DbParse {
|
|
|
989
989
|
};
|
|
990
990
|
}
|
|
991
991
|
|
|
992
|
-
_prepareSingleTableWhere(table, where, useDefaultStateFilter, required, label) {
|
|
992
|
+
_prepareSingleTableWhere(table, where, useDefaultStateFilter, required, label, beflyMode = this.beflyMode) {
|
|
993
993
|
const snakeTable = snakeCase(table);
|
|
994
994
|
const normalizedWhere = whereKeysToSnake(clearDeep(where || {}));
|
|
995
995
|
if (required) {
|
|
996
996
|
assertEffectiveWhere(normalizedWhere, label);
|
|
997
997
|
}
|
|
998
|
-
const whereWithDefault = useDefaultStateFilter ? applyDefaultStateFilter(normalizedWhere, snakeTable, false,
|
|
998
|
+
const whereWithDefault = useDefaultStateFilter ? applyDefaultStateFilter(normalizedWhere, snakeTable, false, beflyMode) : normalizedWhere;
|
|
999
999
|
return {
|
|
1000
1000
|
snakeTable: snakeTable,
|
|
1001
1001
|
where: parseWhereObject(whereWithDefault)
|
package/lib/dbUtil.js
CHANGED
|
@@ -34,7 +34,7 @@ export function parseTableRef(tableRef) {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const parts = trimmed.split(/\s+/)
|
|
37
|
+
const parts = trimmed.split(/\s+/);
|
|
38
38
|
if (parts.length > 2) {
|
|
39
39
|
throw new Error(`不支持的表引用格式(包含过多片段)。请使用最简形式:table 或 table alias 或 schema.table 或 schema.table alias (tableRef: ${trimmed})`, {
|
|
40
40
|
cause: null,
|
|
@@ -42,27 +42,7 @@ export function parseTableRef(tableRef) {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const
|
|
46
|
-
if (!isNonEmptyString(namePart)) {
|
|
47
|
-
throw new Error(`tableRef 解析失败:缺少表名 (tableRef: ${trimmed})`, {
|
|
48
|
-
cause: null,
|
|
49
|
-
code: "validation"
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let aliasPart = null;
|
|
54
|
-
if (parts.length === 2) {
|
|
55
|
-
const alias = parts[1];
|
|
56
|
-
if (!isNonEmptyString(alias)) {
|
|
57
|
-
throw new Error(`tableRef 解析失败:缺少 alias (tableRef: ${trimmed})`, {
|
|
58
|
-
cause: null,
|
|
59
|
-
code: "validation"
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
aliasPart = alias;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const nameSegments = namePart.split(".");
|
|
45
|
+
const nameSegments = parts[0].split(".");
|
|
66
46
|
if (nameSegments.length > 2) {
|
|
67
47
|
throw new Error(`不支持的表引用格式(schema 层级过深) (tableRef: ${trimmed})`, {
|
|
68
48
|
cause: null,
|
|
@@ -71,32 +51,38 @@ export function parseTableRef(tableRef) {
|
|
|
71
51
|
}
|
|
72
52
|
|
|
73
53
|
if (nameSegments.length === 2) {
|
|
74
|
-
|
|
75
|
-
const table = nameSegments[1];
|
|
76
|
-
if (!isNonEmptyString(schema)) {
|
|
54
|
+
if (!nameSegments[0]) {
|
|
77
55
|
throw new Error(`tableRef 解析失败:schema 为空 (tableRef: ${trimmed})`, {
|
|
78
56
|
cause: null,
|
|
79
57
|
code: "validation"
|
|
80
58
|
});
|
|
81
59
|
}
|
|
82
|
-
if (!
|
|
60
|
+
if (!nameSegments[1]) {
|
|
83
61
|
throw new Error(`tableRef 解析失败:table 为空 (tableRef: ${trimmed})`, {
|
|
84
62
|
cause: null,
|
|
85
63
|
code: "validation"
|
|
86
64
|
});
|
|
87
65
|
}
|
|
88
|
-
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
schema: nameSegments[0],
|
|
69
|
+
table: nameSegments[1],
|
|
70
|
+
alias: parts[1] || null
|
|
71
|
+
};
|
|
89
72
|
}
|
|
90
73
|
|
|
91
|
-
|
|
92
|
-
if (!isNonEmptyString(table)) {
|
|
74
|
+
if (!nameSegments[0]) {
|
|
93
75
|
throw new Error(`tableRef 解析失败:table 为空 (tableRef: ${trimmed})`, {
|
|
94
76
|
cause: null,
|
|
95
77
|
code: "validation"
|
|
96
78
|
});
|
|
97
79
|
}
|
|
98
80
|
|
|
99
|
-
return {
|
|
81
|
+
return {
|
|
82
|
+
schema: null,
|
|
83
|
+
table: nameSegments[0],
|
|
84
|
+
alias: parts[1] || null
|
|
85
|
+
};
|
|
100
86
|
}
|
|
101
87
|
|
|
102
88
|
function isQuotedIdentPaired(value) {
|
package/lib/logger.js
CHANGED
|
@@ -100,12 +100,12 @@ class LogFileSink {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
async flushNow() {
|
|
103
|
-
this.
|
|
103
|
+
this.clearScheduledFlush();
|
|
104
104
|
await this.flush();
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
async shutdown() {
|
|
108
|
-
this.
|
|
108
|
+
this.clearScheduledFlush();
|
|
109
109
|
await this.flush();
|
|
110
110
|
await this.closeStream();
|
|
111
111
|
}
|
|
@@ -120,24 +120,22 @@ class LogFileSink {
|
|
|
120
120
|
}, this.flushDelayMs);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
clearScheduledFlush() {
|
|
124
124
|
if (!this.scheduledTimer) return;
|
|
125
125
|
clearTimeout(this.scheduledTimer);
|
|
126
126
|
this.scheduledTimer = null;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
async flush() {
|
|
130
|
-
if (this.flushing) return;
|
|
130
|
+
if (this.flushing || this.pending.length === 0) return;
|
|
131
131
|
this.flushing = true;
|
|
132
132
|
|
|
133
133
|
try {
|
|
134
134
|
while (this.pending.length > 0) {
|
|
135
135
|
const batch = shiftBatchFromPending(this.pending, this.maxBatchBytes);
|
|
136
|
-
|
|
137
|
-
const chunkBytes = Buffer.byteLength(chunk);
|
|
138
|
-
this.pendingBytes = this.pendingBytes - chunkBytes;
|
|
136
|
+
this.pendingBytes = this.pendingBytes - batch.bytes;
|
|
139
137
|
|
|
140
|
-
const ok = await this.writeChunk(chunk,
|
|
138
|
+
const ok = await this.writeChunk(batch.chunk, batch.bytes);
|
|
141
139
|
if (!ok) {
|
|
142
140
|
// writer 已禁用/失败:清空剩余 pending
|
|
143
141
|
this.pending = [];
|
|
@@ -267,13 +265,13 @@ export async function flush() {
|
|
|
267
265
|
// 测试场景:mock logger 不需要 flush
|
|
268
266
|
if (mockInstance) return;
|
|
269
267
|
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
268
|
+
for (const sink of [appFileSink, errorFileSink]) {
|
|
269
|
+
if (!sink) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
273
272
|
|
|
274
|
-
for (const item of sinks) {
|
|
275
273
|
try {
|
|
276
|
-
await
|
|
274
|
+
await sink.flushNow();
|
|
277
275
|
} catch {
|
|
278
276
|
// ignore
|
|
279
277
|
}
|
|
@@ -289,13 +287,13 @@ export async function shutdown() {
|
|
|
289
287
|
const currentAppFileSink = appFileSink;
|
|
290
288
|
const currentErrorFileSink = errorFileSink;
|
|
291
289
|
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
290
|
+
for (const sink of [currentAppFileSink, currentErrorFileSink]) {
|
|
291
|
+
if (!sink) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
295
294
|
|
|
296
|
-
for (const item of sinks) {
|
|
297
295
|
try {
|
|
298
|
-
await
|
|
296
|
+
await sink.shutdown();
|
|
299
297
|
} catch {
|
|
300
298
|
// ignore
|
|
301
299
|
}
|
package/lib/sqlBuilder.js
CHANGED
|
@@ -45,73 +45,46 @@ export class SqlBuilder {
|
|
|
45
45
|
_compileOperatorNode(node) {
|
|
46
46
|
const escapedField = escapeField(node.field, this._quoteIdent);
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
switch (node.operator) {
|
|
49
|
+
case "$not":
|
|
50
|
+
return { sql: `${escapedField} != ?`, params: [node.value] };
|
|
51
|
+
case "$in":
|
|
52
|
+
return {
|
|
53
|
+
sql: `${escapedField} IN (${node.value.map(() => "?").join(",")})`,
|
|
54
|
+
params: node.value.slice()
|
|
55
|
+
};
|
|
56
|
+
case "$notIn":
|
|
57
|
+
return {
|
|
58
|
+
sql: `${escapedField} NOT IN (${node.value.map(() => "?").join(",")})`,
|
|
59
|
+
params: node.value.slice()
|
|
60
|
+
};
|
|
61
|
+
case "$like":
|
|
62
|
+
return { sql: `${escapedField} LIKE ?`, params: [`%${String(node.value)}%`] };
|
|
63
|
+
case "$leftLike":
|
|
64
|
+
return { sql: `${escapedField} LIKE ?`, params: [`%${String(node.value)}`] };
|
|
65
|
+
case "$rightLike":
|
|
66
|
+
return { sql: `${escapedField} LIKE ?`, params: [`${String(node.value)}%`] };
|
|
67
|
+
case "$notLike":
|
|
68
|
+
return { sql: `${escapedField} NOT LIKE ?`, params: [node.value] };
|
|
69
|
+
case "$gt":
|
|
70
|
+
return { sql: `${escapedField} > ?`, params: [node.value] };
|
|
71
|
+
case "$gte":
|
|
72
|
+
return { sql: `${escapedField} >= ?`, params: [node.value] };
|
|
73
|
+
case "$lt":
|
|
74
|
+
return { sql: `${escapedField} < ?`, params: [node.value] };
|
|
75
|
+
case "$lte":
|
|
76
|
+
return { sql: `${escapedField} <= ?`, params: [node.value] };
|
|
77
|
+
case "$between":
|
|
78
|
+
return { sql: `${escapedField} BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
|
|
79
|
+
case "$notBetween":
|
|
80
|
+
return { sql: `${escapedField} NOT BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
|
|
81
|
+
case "$null":
|
|
82
|
+
return { sql: `${escapedField} IS NULL`, params: [] };
|
|
83
|
+
case "$notNull":
|
|
84
|
+
return { sql: `${escapedField} IS NOT NULL`, params: [] };
|
|
85
|
+
default:
|
|
86
|
+
return { sql: `${escapedField} = ?`, params: [node.value] };
|
|
50
87
|
}
|
|
51
|
-
|
|
52
|
-
if (node.operator === "$in") {
|
|
53
|
-
return {
|
|
54
|
-
sql: `${escapedField} IN (${node.value.map(() => "?").join(",")})`,
|
|
55
|
-
params: node.value.slice()
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (node.operator === "$notIn") {
|
|
60
|
-
return {
|
|
61
|
-
sql: `${escapedField} NOT IN (${node.value.map(() => "?").join(",")})`,
|
|
62
|
-
params: node.value.slice()
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (node.operator === "$like") {
|
|
67
|
-
return { sql: `${escapedField} LIKE ?`, params: [`%${String(node.value)}%`] };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (node.operator === "$leftLike") {
|
|
71
|
-
return { sql: `${escapedField} LIKE ?`, params: [`%${String(node.value)}`] };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (node.operator === "$rightLike") {
|
|
75
|
-
return { sql: `${escapedField} LIKE ?`, params: [`${String(node.value)}%`] };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (node.operator === "$notLike") {
|
|
79
|
-
return { sql: `${escapedField} NOT LIKE ?`, params: [node.value] };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (node.operator === "$gt") {
|
|
83
|
-
return { sql: `${escapedField} > ?`, params: [node.value] };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (node.operator === "$gte") {
|
|
87
|
-
return { sql: `${escapedField} >= ?`, params: [node.value] };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (node.operator === "$lt") {
|
|
91
|
-
return { sql: `${escapedField} < ?`, params: [node.value] };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (node.operator === "$lte") {
|
|
95
|
-
return { sql: `${escapedField} <= ?`, params: [node.value] };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (node.operator === "$between") {
|
|
99
|
-
return { sql: `${escapedField} BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (node.operator === "$notBetween") {
|
|
103
|
-
return { sql: `${escapedField} NOT BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (node.operator === "$null") {
|
|
107
|
-
return { sql: `${escapedField} IS NULL`, params: [] };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (node.operator === "$notNull") {
|
|
111
|
-
return { sql: `${escapedField} IS NOT NULL`, params: [] };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return { sql: `${escapedField} = ?`, params: [node.value] };
|
|
115
88
|
}
|
|
116
89
|
|
|
117
90
|
_compileWhereNode(node) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.23.0",
|
|
4
|
+
"gitHead": "323ac9336929e2beaab435e9865194e22a884c82",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
"homepage": "https://chensuiyi.me",
|
|
19
19
|
"license": "Apache-2.0",
|
|
20
20
|
"author": "chensuiyi <bimostyle@qq.com>",
|
|
21
|
-
"main": "./index.js",
|
|
22
21
|
"files": [
|
|
23
22
|
"apis",
|
|
24
23
|
"checks",
|
|
@@ -38,6 +37,7 @@
|
|
|
38
37
|
"README.md"
|
|
39
38
|
],
|
|
40
39
|
"type": "module",
|
|
40
|
+
"main": "./index.js",
|
|
41
41
|
"exports": {
|
|
42
42
|
".": {
|
|
43
43
|
"import": "./index.js",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"test": "bun test"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"fast-xml-parser": "^5.
|
|
56
|
-
"nodemailer": "^8.0.
|
|
55
|
+
"fast-xml-parser": "^5.7.2",
|
|
56
|
+
"nodemailer": "^8.0.6",
|
|
57
57
|
"pathe": "^2.0.3",
|
|
58
58
|
"ua-parser-js": "^2.0.9",
|
|
59
59
|
"zod": "^4.0.0"
|
package/scripts/syncDb/diff.js
CHANGED
|
@@ -7,7 +7,7 @@ import { toSyncDbFieldDef } from "./transform.js";
|
|
|
7
7
|
|
|
8
8
|
const SYNC_DB_MANAGED_FIELD_NAMES = new Set(["id", "created_at", "updated_at", "deleted_at", "state"]);
|
|
9
9
|
const SYNC_DB_HEAD_FIELD_NAME = "id";
|
|
10
|
-
const SYNC_DB_TAIL_FIELD_NAMES = ["
|
|
10
|
+
const SYNC_DB_TAIL_FIELD_NAMES = ["created_at", "updated_at", "deleted_at", "state"];
|
|
11
11
|
const SYNC_DB_TAIL_FIELD_NAME_SET = new Set(SYNC_DB_TAIL_FIELD_NAMES);
|
|
12
12
|
|
|
13
13
|
function shouldSkipSyncDbColumn(columnMeta, beflyMode) {
|
|
@@ -38,7 +38,7 @@ function sortSyncDbObjectKeys(source) {
|
|
|
38
38
|
if (key === SYNC_DB_HEAD_FIELD_NAME) {
|
|
39
39
|
continue;
|
|
40
40
|
}
|
|
41
|
-
if (SYNC_DB_TAIL_FIELD_NAME_SET.has(key)) {
|
|
41
|
+
if (SYNC_DB_TAIL_FIELD_NAME_SET.has(snakeCase(key))) {
|
|
42
42
|
continue;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -46,11 +46,13 @@ function sortSyncDbObjectKeys(source) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
for (const fieldName of SYNC_DB_TAIL_FIELD_NAMES) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
for (const [key, value] of Object.entries(source)) {
|
|
50
|
+
if (snakeCase(key) !== fieldName) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
output[key] = value;
|
|
55
|
+
}
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
return output;
|
|
@@ -118,19 +120,32 @@ export function buildSyncDbDiff(groupedDbColumns, existingTableMap, beflyMode =
|
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
const existingFields = isPlainObject(existing.tableDef) ? existing.tableDef : {};
|
|
123
|
+
const existingFieldNameByDbColumnName = new Map();
|
|
124
|
+
|
|
125
|
+
for (const fieldName of Object.keys(existingFields)) {
|
|
126
|
+
if (shouldSkipSyncDbFieldName(fieldName, beflyMode)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
existingFieldNameByDbColumnName.set(snakeCase(fieldName), fieldName);
|
|
131
|
+
}
|
|
132
|
+
|
|
121
133
|
const missingFields = [];
|
|
122
134
|
const dbFieldNameSet = new Set();
|
|
123
135
|
for (const columnMeta of filteredColumns) {
|
|
124
136
|
const fieldInfo = toSyncDbFieldDef(columnMeta);
|
|
125
|
-
|
|
126
|
-
|
|
137
|
+
const dbColumnName = String(columnMeta.columnName || "");
|
|
138
|
+
const normalizedDbColumnName = dbColumnName.toLowerCase();
|
|
139
|
+
|
|
140
|
+
dbFieldNameSet.add(normalizedDbColumnName);
|
|
141
|
+
if (existingFieldNameByDbColumnName.has(normalizedDbColumnName)) {
|
|
127
142
|
continue;
|
|
128
143
|
}
|
|
129
144
|
|
|
130
145
|
missingFields.push({
|
|
131
146
|
tableName: tableName,
|
|
132
147
|
tableFileName: tableFileName,
|
|
133
|
-
dbColumnName:
|
|
148
|
+
dbColumnName: dbColumnName,
|
|
134
149
|
dbDataType: columnMeta.dataType,
|
|
135
150
|
dbColumnType: columnMeta.columnType,
|
|
136
151
|
dbCharLength: columnMeta.charLength,
|
|
@@ -154,7 +169,7 @@ export function buildSyncDbDiff(groupedDbColumns, existingTableMap, beflyMode =
|
|
|
154
169
|
if (shouldSkipSyncDbFieldName(fieldName, beflyMode)) {
|
|
155
170
|
continue;
|
|
156
171
|
}
|
|
157
|
-
if (dbFieldNameSet.has(fieldName)) {
|
|
172
|
+
if (dbFieldNameSet.has(snakeCase(fieldName))) {
|
|
158
173
|
continue;
|
|
159
174
|
}
|
|
160
175
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DECIMAL_KIND_TYPES, ENUM_KIND_TYPES, FLOAT_KIND_TYPES, INT_KIND_TYPES, JSON_KIND_TYPES, STRING_KIND_TYPES, TEXT_KIND_TYPES } from "../../configs/constConfig.js";
|
|
2
2
|
import { isNonEmptyString, isNumber } from "../../utils/is.js";
|
|
3
|
-
import {
|
|
3
|
+
import { camelCaseKeepUnderscore } from "../../utils/util.js";
|
|
4
4
|
|
|
5
5
|
function resolveSyncDbEnumInput(columnType) {
|
|
6
6
|
const matched = /^enum\((.*)\)$/.exec(columnType);
|
|
@@ -90,7 +90,7 @@ function resolveSyncDbMaxByColumn(columnMeta) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export function toSyncDbFieldDef(columnMeta) {
|
|
93
|
-
const fieldName =
|
|
93
|
+
const fieldName = camelCaseKeepUnderscore(columnMeta.columnName);
|
|
94
94
|
const fieldDisplayName = isNonEmptyString(columnMeta.columnComment) ? String(columnMeta.columnComment).trim() : fieldName;
|
|
95
95
|
const fieldInput = resolveSyncDbInputByColumn(columnMeta);
|
|
96
96
|
const fieldMax = resolveSyncDbMaxByColumn(columnMeta);
|
package/sync/api.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { buildPathSyncLists } from "./syncUtil.js";
|
|
2
|
+
|
|
1
3
|
const API_TABLE_NAME = "beflyApi";
|
|
2
4
|
|
|
3
5
|
const getApiParentPath = (apiPath) => {
|
|
@@ -22,64 +24,45 @@ export async function syncApi(ctx, apis) {
|
|
|
22
24
|
where: { state$gte: 0 }
|
|
23
25
|
});
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
const dbLists = allDbApis.data.lists || [];
|
|
27
|
-
const existingApiMap = new Map();
|
|
28
|
-
for (const item of dbLists) {
|
|
29
|
-
if (!existingApiMap.has(item.path)) {
|
|
30
|
-
existingApiMap.set(item.path, item);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const insList = [];
|
|
35
|
-
const updList = [];
|
|
36
|
-
const scannedPathSet = new Set();
|
|
27
|
+
const apiDefs = [];
|
|
37
28
|
for (const api of apis) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const auth = api.auth === false ? "否" : Array.isArray(api.auth) ? api.auth.join(",") : "是";
|
|
41
|
-
const parentPath = getApiParentPath(api.apiPath);
|
|
42
|
-
const existing = existingApiMap.get(api.apiPath);
|
|
43
|
-
|
|
44
|
-
if (existing) {
|
|
45
|
-
updList.push({
|
|
46
|
-
id: existing.id,
|
|
47
|
-
data: {
|
|
48
|
-
name: api.name,
|
|
49
|
-
path: api.apiPath,
|
|
50
|
-
method: api.method,
|
|
51
|
-
parentPath: parentPath,
|
|
52
|
-
auth: auth
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
insList.push({
|
|
29
|
+
apiDefs.push({
|
|
59
30
|
name: api.name,
|
|
60
31
|
path: api.apiPath,
|
|
61
32
|
method: api.method,
|
|
62
|
-
parentPath:
|
|
63
|
-
auth: auth
|
|
33
|
+
parentPath: getApiParentPath(api.apiPath),
|
|
34
|
+
auth: api.auth === false ? "否" : Array.isArray(api.auth) ? api.auth.join(",") : "是"
|
|
64
35
|
});
|
|
65
36
|
}
|
|
66
37
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
38
|
+
const syncLists = buildPathSyncLists(
|
|
39
|
+
allDbApis.data.lists || [],
|
|
40
|
+
apiDefs,
|
|
41
|
+
(def) => ({
|
|
42
|
+
name: def.name,
|
|
43
|
+
path: def.path,
|
|
44
|
+
method: def.method,
|
|
45
|
+
parentPath: def.parentPath,
|
|
46
|
+
auth: def.auth
|
|
47
|
+
}),
|
|
48
|
+
(def) => ({
|
|
49
|
+
name: def.name,
|
|
50
|
+
path: def.path,
|
|
51
|
+
method: def.method,
|
|
52
|
+
parentPath: def.parentPath,
|
|
53
|
+
auth: def.auth
|
|
54
|
+
})
|
|
55
|
+
);
|
|
73
56
|
|
|
74
|
-
if (updList.length > 0) {
|
|
75
|
-
await ctx.mysql.updBatch(API_TABLE_NAME, updList);
|
|
57
|
+
if (syncLists.updList.length > 0) {
|
|
58
|
+
await ctx.mysql.updBatch(API_TABLE_NAME, syncLists.updList);
|
|
76
59
|
}
|
|
77
60
|
|
|
78
|
-
if (insList.length > 0) {
|
|
79
|
-
await ctx.mysql.insBatch(API_TABLE_NAME, insList);
|
|
61
|
+
if (syncLists.insList.length > 0) {
|
|
62
|
+
await ctx.mysql.insBatch(API_TABLE_NAME, syncLists.insList);
|
|
80
63
|
}
|
|
81
64
|
|
|
82
|
-
if (
|
|
83
|
-
await ctx.mysql.delForceBatch(API_TABLE_NAME,
|
|
65
|
+
if (syncLists.delIds.length > 0) {
|
|
66
|
+
await ctx.mysql.delForceBatch(API_TABLE_NAME, syncLists.delIds);
|
|
84
67
|
}
|
|
85
68
|
}
|