befly 3.20.8 → 3.20.9
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/dict/all.js +2 -2
- package/apis/dict/detail.js +2 -2
- package/apis/dict/list.js +2 -2
- package/hooks/permission.js +1 -2
- package/lib/cacheHelper.js +16 -17
- package/lib/dbHelper/builders.js +91 -207
- package/lib/dbHelper/dataOps.js +119 -123
- package/lib/dbHelper/validate.js +88 -20
- package/lib/sqlBuilder/batch.js +7 -8
- package/lib/sqlBuilder/check.js +19 -87
- package/lib/sqlBuilder/compiler.js +91 -90
- package/lib/sqlBuilder/parser.js +122 -103
- package/lib/sqlBuilder/util.js +66 -53
- package/package.json +2 -2
- package/lib/cacheKeys.js +0 -42
- package/lib/sqlBuilder/errors.js +0 -60
package/apis/dict/all.js
CHANGED
|
@@ -10,8 +10,8 @@ export default {
|
|
|
10
10
|
required: [],
|
|
11
11
|
handler: async (befly) => {
|
|
12
12
|
const result = await befly.mysql.getAll({
|
|
13
|
-
table: "beflyDict",
|
|
14
|
-
leftJoin: ["
|
|
13
|
+
table: ["beflyDict", "beflyDictType"],
|
|
14
|
+
leftJoin: ["beflyDict.typeCode beflyDictType.code"],
|
|
15
15
|
fields: ["beflyDict.id", "beflyDict.typeCode", "beflyDict.key", "beflyDict.label", "beflyDict.sort", "beflyDict.remark", "beflyDict.createdAt", "beflyDict.updatedAt", "beflyDictType.name AS typeName"],
|
|
16
16
|
orderBy: ["beflyDict.sort#ASC", "beflyDict.id#ASC"]
|
|
17
17
|
});
|
package/apis/dict/detail.js
CHANGED
|
@@ -9,8 +9,8 @@ export default {
|
|
|
9
9
|
required: ["id"],
|
|
10
10
|
handler: async (befly, ctx) => {
|
|
11
11
|
const dict = await befly.mysql.getOne({
|
|
12
|
-
table: "beflyDict",
|
|
13
|
-
leftJoin: ["
|
|
12
|
+
table: ["beflyDict", "beflyDictType"],
|
|
13
|
+
leftJoin: ["beflyDict.typeCode beflyDictType.code"],
|
|
14
14
|
fields: ["beflyDict.id", "beflyDict.typeCode", "beflyDict.key", "beflyDict.label", "beflyDict.sort", "beflyDict.remark", "beflyDict.createdAt", "beflyDict.updatedAt", "beflyDictType.name AS typeName"],
|
|
15
15
|
where: { "beflyDict.id": ctx.body.id }
|
|
16
16
|
});
|
package/apis/dict/list.js
CHANGED
|
@@ -14,8 +14,8 @@ export default {
|
|
|
14
14
|
required: [],
|
|
15
15
|
handler: async (befly, ctx) => {
|
|
16
16
|
const result = await befly.mysql.getList({
|
|
17
|
-
table: "beflyDict",
|
|
18
|
-
leftJoin: ["
|
|
17
|
+
table: ["beflyDict", "beflyDictType"],
|
|
18
|
+
leftJoin: ["beflyDict.typeCode beflyDictType.code"],
|
|
19
19
|
fields: ["beflyDict.id", "beflyDict.typeCode", "beflyDict.key", "beflyDict.label", "beflyDict.sort", "beflyDict.remark", "beflyDict.createdAt", "beflyDict.updatedAt", "beflyDictType.name AS typeName"],
|
|
20
20
|
where: {
|
|
21
21
|
"beflyDict.typeCode": ctx.body.typeCode
|
package/hooks/permission.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { CacheKeys } from "../lib/cacheKeys.js";
|
|
2
1
|
// 相对导入
|
|
3
2
|
import { ErrorResponse } from "../utils/response.js";
|
|
4
3
|
import { isValidPositiveInt } from "../utils/is.js";
|
|
@@ -50,7 +49,7 @@ export default {
|
|
|
50
49
|
// apiPath 在 apiHandler 中已统一生成并写入 ctx.apiPath
|
|
51
50
|
|
|
52
51
|
// 极简方案:每个角色一个 Set,直接判断成员是否存在
|
|
53
|
-
const hasPermission = await befly.redis.sismember(
|
|
52
|
+
const hasPermission = await befly.redis.sismember(`role:apis:${ctx.roleCode}`, ctx.apiPath);
|
|
54
53
|
|
|
55
54
|
if (!hasPermission) {
|
|
56
55
|
ctx.response = ErrorResponse(
|
package/lib/cacheHelper.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* 负责在服务器启动时缓存接口、菜单和角色权限到 Redis
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { CacheKeys } from "./cacheKeys.js";
|
|
7
6
|
import { Logger } from "./logger.js";
|
|
8
7
|
import { isNonEmptyString, isNullable, isString } from "../utils/is.js";
|
|
9
8
|
|
|
@@ -27,7 +26,7 @@ export class CacheHelper {
|
|
|
27
26
|
});
|
|
28
27
|
|
|
29
28
|
// 缓存到 Redis
|
|
30
|
-
const result = await this.redis.setObject(
|
|
29
|
+
const result = await this.redis.setObject("apis:all", apiList.data.lists);
|
|
31
30
|
|
|
32
31
|
if (result === null) {
|
|
33
32
|
Logger.warn("⚠️ 接口缓存失败", {
|
|
@@ -57,7 +56,7 @@ export class CacheHelper {
|
|
|
57
56
|
});
|
|
58
57
|
|
|
59
58
|
// 缓存到 Redis
|
|
60
|
-
const result = await this.redis.setObject(
|
|
59
|
+
const result = await this.redis.setObject("menus:all", menus.data.lists);
|
|
61
60
|
|
|
62
61
|
if (result === null) {
|
|
63
62
|
Logger.warn("⚠️ 菜单缓存失败", {
|
|
@@ -103,7 +102,7 @@ export class CacheHelper {
|
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
// 清理所有角色的缓存 key(保证幂等)
|
|
106
|
-
const roleKeys = roleCodes.map((code) =>
|
|
105
|
+
const roleKeys = roleCodes.map((code) => `role:apis:${code}`);
|
|
107
106
|
await this.redis.delBatch(roleKeys);
|
|
108
107
|
|
|
109
108
|
// 批量写入新缓存(只写入非空权限)
|
|
@@ -114,7 +113,7 @@ export class CacheHelper {
|
|
|
114
113
|
const members = Array.from(new Set(apiPaths)).sort();
|
|
115
114
|
|
|
116
115
|
if (members.length > 0) {
|
|
117
|
-
items.push({ key:
|
|
116
|
+
items.push({ key: `role:apis:${roleCode}`, members: members });
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
119
|
|
|
@@ -161,7 +160,7 @@ export class CacheHelper {
|
|
|
161
160
|
}
|
|
162
161
|
|
|
163
162
|
// 清理所有角色的缓存 key(保证幂等)
|
|
164
|
-
const roleKeys = roleCodes.map((code) =>
|
|
163
|
+
const roleKeys = roleCodes.map((code) => `role:menus:${code}`);
|
|
165
164
|
await this.redis.delBatch(roleKeys);
|
|
166
165
|
|
|
167
166
|
// 批量写入新缓存(只写入非空权限)
|
|
@@ -172,7 +171,7 @@ export class CacheHelper {
|
|
|
172
171
|
const members = Array.from(new Set(menuPaths)).sort();
|
|
173
172
|
|
|
174
173
|
if (members.length > 0) {
|
|
175
|
-
items.push({ key:
|
|
174
|
+
items.push({ key: `role:menus:${roleCode}`, members: members });
|
|
176
175
|
}
|
|
177
176
|
}
|
|
178
177
|
|
|
@@ -213,7 +212,7 @@ export class CacheHelper {
|
|
|
213
212
|
});
|
|
214
213
|
}
|
|
215
214
|
|
|
216
|
-
const roleKey =
|
|
215
|
+
const roleKey = `role:apis:${roleCode}`;
|
|
217
216
|
|
|
218
217
|
// 空数组短路:保证清理残留
|
|
219
218
|
if (apiPaths.length === 0) {
|
|
@@ -252,7 +251,7 @@ export class CacheHelper {
|
|
|
252
251
|
});
|
|
253
252
|
}
|
|
254
253
|
|
|
255
|
-
const roleKey =
|
|
254
|
+
const roleKey = `role:menus:${roleCode}`;
|
|
256
255
|
|
|
257
256
|
// 空数组短路:保证清理残留
|
|
258
257
|
if (menuPaths.length === 0) {
|
|
@@ -291,7 +290,7 @@ export class CacheHelper {
|
|
|
291
290
|
*/
|
|
292
291
|
async getApis() {
|
|
293
292
|
try {
|
|
294
|
-
const apis = await this.redis.getObject(
|
|
293
|
+
const apis = await this.redis.getObject("apis:all");
|
|
295
294
|
return apis || [];
|
|
296
295
|
} catch (error) {
|
|
297
296
|
Logger.error("获取接口缓存失败", error);
|
|
@@ -310,7 +309,7 @@ export class CacheHelper {
|
|
|
310
309
|
*/
|
|
311
310
|
async getMenus() {
|
|
312
311
|
try {
|
|
313
|
-
const menus = await this.redis.getObject(
|
|
312
|
+
const menus = await this.redis.getObject("menus:all");
|
|
314
313
|
return menus || [];
|
|
315
314
|
} catch (error) {
|
|
316
315
|
Logger.error("获取菜单缓存失败", error);
|
|
@@ -330,7 +329,7 @@ export class CacheHelper {
|
|
|
330
329
|
*/
|
|
331
330
|
async getRoleApis(roleCode) {
|
|
332
331
|
try {
|
|
333
|
-
const permissions = await this.redis.smembers(
|
|
332
|
+
const permissions = await this.redis.smembers(`role:apis:${roleCode}`);
|
|
334
333
|
return permissions || [];
|
|
335
334
|
} catch (error) {
|
|
336
335
|
Logger.error("获取角色权限缓存失败", error, { roleCode: roleCode });
|
|
@@ -351,7 +350,7 @@ export class CacheHelper {
|
|
|
351
350
|
*/
|
|
352
351
|
async getRoleMenus(roleCode) {
|
|
353
352
|
try {
|
|
354
|
-
const permissions = await this.redis.smembers(
|
|
353
|
+
const permissions = await this.redis.smembers(`role:menus:${roleCode}`);
|
|
355
354
|
return permissions || [];
|
|
356
355
|
} catch (error) {
|
|
357
356
|
Logger.error("获取角色菜单权限缓存失败", error, { roleCode: roleCode });
|
|
@@ -375,7 +374,7 @@ export class CacheHelper {
|
|
|
375
374
|
try {
|
|
376
375
|
if (isNullable(apiPath)) return false;
|
|
377
376
|
const value = isString(apiPath) ? apiPath : String(apiPath);
|
|
378
|
-
return await this.redis.sismember(
|
|
377
|
+
return await this.redis.sismember(`role:apis:${roleCode}`, value);
|
|
379
378
|
} catch (error) {
|
|
380
379
|
Logger.error("检查角色权限失败", error, { roleCode: roleCode });
|
|
381
380
|
throw new Error("检查角色权限失败", {
|
|
@@ -398,7 +397,7 @@ export class CacheHelper {
|
|
|
398
397
|
try {
|
|
399
398
|
if (isNullable(menuPath)) return false;
|
|
400
399
|
const value = isString(menuPath) ? menuPath : String(menuPath);
|
|
401
|
-
return await this.redis.sismember(
|
|
400
|
+
return await this.redis.sismember(`role:menus:${roleCode}`, value);
|
|
402
401
|
} catch (error) {
|
|
403
402
|
Logger.error("检查角色菜单权限失败", error, { roleCode: roleCode });
|
|
404
403
|
throw new Error("检查角色菜单权限失败", {
|
|
@@ -418,7 +417,7 @@ export class CacheHelper {
|
|
|
418
417
|
*/
|
|
419
418
|
async deleteRoleApis(roleCode) {
|
|
420
419
|
try {
|
|
421
|
-
const result = await this.redis.del(
|
|
420
|
+
const result = await this.redis.del(`role:apis:${roleCode}`);
|
|
422
421
|
if (result > 0) {
|
|
423
422
|
Logger.info(`✅ 已删除角色 ${roleCode} 的权限缓存`);
|
|
424
423
|
return true;
|
|
@@ -443,7 +442,7 @@ export class CacheHelper {
|
|
|
443
442
|
*/
|
|
444
443
|
async deleteRoleMenus(roleCode) {
|
|
445
444
|
try {
|
|
446
|
-
const result = await this.redis.del(
|
|
445
|
+
const result = await this.redis.del(`role:menus:${roleCode}`);
|
|
447
446
|
if (result > 0) {
|
|
448
447
|
Logger.info(`✅ 已删除角色 ${roleCode} 的菜单权限缓存`);
|
|
449
448
|
return true;
|
package/lib/dbHelper/builders.js
CHANGED
|
@@ -156,6 +156,24 @@ export async function fieldsToSnake(table, fields, getTableColumns) {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
export function orderByToSnake(orderBy) {
|
|
159
|
+
return normalizeOrderBy(orderBy, (field) => snakeCase(field));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function normalizeQualifierField(field) {
|
|
163
|
+
const parts = field.split(".");
|
|
164
|
+
if (parts.length === 1) {
|
|
165
|
+
return snakeCase(field);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const fieldName = parts[parts.length - 1].trim();
|
|
169
|
+
const qualifier = parts
|
|
170
|
+
.slice(0, parts.length - 1)
|
|
171
|
+
.map((item) => snakeCase(item.trim()))
|
|
172
|
+
.join(".");
|
|
173
|
+
return `${qualifier}.${snakeCase(fieldName)}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function normalizeOrderBy(orderBy, mapField) {
|
|
159
177
|
if (!orderBy || !Array.isArray(orderBy)) {
|
|
160
178
|
return orderBy;
|
|
161
179
|
}
|
|
@@ -170,14 +188,35 @@ export function orderByToSnake(orderBy) {
|
|
|
170
188
|
return item;
|
|
171
189
|
}
|
|
172
190
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
191
|
+
return `${mapField(parts[0].trim())}#${parts[1].trim()}`;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function mapWhereKeys(where, mapKey) {
|
|
196
|
+
if (!where || typeof where !== "object") {
|
|
197
|
+
return where;
|
|
198
|
+
}
|
|
199
|
+
if (Array.isArray(where)) {
|
|
200
|
+
return where.map((item) => mapWhereKeys(item, mapKey));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const result = {};
|
|
204
|
+
for (const [key, value] of Object.entries(where)) {
|
|
205
|
+
if (key === "$or" || key === "$and") {
|
|
206
|
+
result[key] = Array.isArray(value) ? value.map((item) => mapWhereKeys(item, mapKey)) : [];
|
|
207
|
+
continue;
|
|
177
208
|
}
|
|
178
209
|
|
|
179
|
-
|
|
180
|
-
|
|
210
|
+
const normalizedKey = mapKey(key);
|
|
211
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
212
|
+
result[normalizedKey] = mapWhereKeys(value, mapKey);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
result[normalizedKey] = value;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return result;
|
|
181
220
|
}
|
|
182
221
|
|
|
183
222
|
export function processJoinField(field) {
|
|
@@ -196,36 +235,7 @@ export function processJoinField(field) {
|
|
|
196
235
|
return `${processJoinField(fieldPart.trim())} AS ${aliasPart.trim()}`;
|
|
197
236
|
}
|
|
198
237
|
|
|
199
|
-
|
|
200
|
-
const parts = field.split(".");
|
|
201
|
-
if (parts.length < 2) {
|
|
202
|
-
return snakeCase(field);
|
|
203
|
-
}
|
|
204
|
-
if (parts.some((p) => !isNonEmptyString(p))) {
|
|
205
|
-
return field;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const fieldName = parts[parts.length - 1];
|
|
209
|
-
const tableName = parts.slice(0, parts.length - 1).join(".");
|
|
210
|
-
if (!isString(fieldName) || !isString(tableName)) {
|
|
211
|
-
return snakeCase(field);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const normalizedTableName = tableName
|
|
215
|
-
.split(".")
|
|
216
|
-
.map((item) => {
|
|
217
|
-
const trimmed = item.trim();
|
|
218
|
-
if (!trimmed) {
|
|
219
|
-
return trimmed;
|
|
220
|
-
}
|
|
221
|
-
return /[A-Z]/.test(trimmed) ? snakeCase(trimmed) : trimmed;
|
|
222
|
-
})
|
|
223
|
-
.join(".");
|
|
224
|
-
|
|
225
|
-
return `${normalizedTableName}.${snakeCase(fieldName)}`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return snakeCase(field);
|
|
238
|
+
return normalizeQualifierField(field);
|
|
229
239
|
}
|
|
230
240
|
|
|
231
241
|
function processJoinWhereKey(key) {
|
|
@@ -239,136 +249,26 @@ function processJoinWhereKey(key) {
|
|
|
239
249
|
const fieldPart = key.substring(0, lastDollarIndex);
|
|
240
250
|
const operator = key.substring(lastDollarIndex);
|
|
241
251
|
|
|
242
|
-
|
|
243
|
-
const parts = fieldPart.split(".");
|
|
244
|
-
if (parts.length < 2) {
|
|
245
|
-
return `${snakeCase(fieldPart)}${operator}`;
|
|
246
|
-
}
|
|
247
|
-
if (parts.some((p) => !isNonEmptyString(p))) {
|
|
248
|
-
return `${snakeCase(fieldPart)}${operator}`;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const fieldName = parts[parts.length - 1];
|
|
252
|
-
const tableName = parts.slice(0, parts.length - 1).join(".");
|
|
253
|
-
if (!isString(fieldName) || !isString(tableName)) {
|
|
254
|
-
return `${snakeCase(fieldPart)}${operator}`;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const normalizedTableName = tableName
|
|
258
|
-
.split(".")
|
|
259
|
-
.map((item) => {
|
|
260
|
-
const trimmed = item.trim();
|
|
261
|
-
if (!trimmed) {
|
|
262
|
-
return trimmed;
|
|
263
|
-
}
|
|
264
|
-
return /[A-Z]/.test(trimmed) ? snakeCase(trimmed) : trimmed;
|
|
265
|
-
})
|
|
266
|
-
.join(".");
|
|
267
|
-
return `${normalizedTableName}.${snakeCase(fieldName)}${operator}`;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return `${snakeCase(fieldPart)}${operator}`;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (key.includes(".")) {
|
|
274
|
-
const parts = key.split(".");
|
|
275
|
-
if (parts.length < 2) {
|
|
276
|
-
return snakeCase(key);
|
|
277
|
-
}
|
|
278
|
-
if (parts.some((p) => !isNonEmptyString(p))) {
|
|
279
|
-
return snakeCase(key);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const fieldName = parts[parts.length - 1];
|
|
283
|
-
const tableName = parts.slice(0, parts.length - 1).join(".");
|
|
284
|
-
if (!isString(fieldName) || !isString(tableName)) {
|
|
285
|
-
return snakeCase(key);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const normalizedTableName = tableName
|
|
289
|
-
.split(".")
|
|
290
|
-
.map((item) => {
|
|
291
|
-
const trimmed = item.trim();
|
|
292
|
-
if (!trimmed) {
|
|
293
|
-
return trimmed;
|
|
294
|
-
}
|
|
295
|
-
return /[A-Z]/.test(trimmed) ? snakeCase(trimmed) : trimmed;
|
|
296
|
-
})
|
|
297
|
-
.join(".");
|
|
298
|
-
return `${normalizedTableName}.${snakeCase(fieldName)}`;
|
|
252
|
+
return `${normalizeQualifierField(fieldPart)}${operator}`;
|
|
299
253
|
}
|
|
300
254
|
|
|
301
|
-
return
|
|
255
|
+
return normalizeQualifierField(key);
|
|
302
256
|
}
|
|
303
257
|
|
|
304
258
|
export function processJoinWhere(where) {
|
|
305
|
-
|
|
306
|
-
return where;
|
|
307
|
-
}
|
|
308
|
-
if (Array.isArray(where)) {
|
|
309
|
-
return where.map((item) => processJoinWhere(item));
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const result = {};
|
|
313
|
-
for (const [key, value] of Object.entries(where)) {
|
|
314
|
-
const newKey = processJoinWhereKey(key);
|
|
315
|
-
if (key === "$or" || key === "$and") {
|
|
316
|
-
result[newKey] = Array.isArray(value) ? value.map((item) => processJoinWhere(item)) : [];
|
|
317
|
-
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
318
|
-
result[newKey] = processJoinWhere(value);
|
|
319
|
-
} else {
|
|
320
|
-
result[newKey] = value;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return result;
|
|
259
|
+
return mapWhereKeys(where, processJoinWhereKey);
|
|
325
260
|
}
|
|
326
261
|
|
|
327
262
|
export function processJoinOrderBy(orderBy) {
|
|
328
|
-
|
|
329
|
-
return orderBy;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return orderBy.map((item) => {
|
|
333
|
-
if (!isString(item) || !item.includes("#")) {
|
|
334
|
-
return item;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const parts = item.split("#");
|
|
338
|
-
if (parts.length !== 2) {
|
|
339
|
-
return item;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const field = parts[0];
|
|
343
|
-
const direction = parts[1];
|
|
344
|
-
if (!isString(field) || !isString(direction)) {
|
|
345
|
-
return item;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return `${processJoinField(field.trim())}#${direction.trim()}`;
|
|
349
|
-
});
|
|
263
|
+
return normalizeOrderBy(orderBy, processJoinField);
|
|
350
264
|
}
|
|
351
265
|
|
|
352
|
-
export function parseLeftJoinItem(joinItem) {
|
|
353
|
-
|
|
354
|
-
return null;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const parts = joinItem.split(/\s+ON\s+/i);
|
|
358
|
-
if (parts.length !== 2) {
|
|
359
|
-
return null;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const table = parts[0]?.trim();
|
|
363
|
-
const on = parts[1]?.trim();
|
|
364
|
-
if (!isNonEmptyString(table) || !isNonEmptyString(on)) {
|
|
365
|
-
return null;
|
|
366
|
-
}
|
|
367
|
-
|
|
266
|
+
export function parseLeftJoinItem(joinTable, joinItem) {
|
|
267
|
+
const parts = joinItem.split(" ");
|
|
368
268
|
return {
|
|
369
269
|
type: "left",
|
|
370
|
-
table: normalizeTableRef(
|
|
371
|
-
on: processJoinOn(
|
|
270
|
+
table: normalizeTableRef(joinTable),
|
|
271
|
+
on: processJoinOn(`${parts[0]} = ${parts[1]}`)
|
|
372
272
|
};
|
|
373
273
|
}
|
|
374
274
|
|
|
@@ -453,38 +353,17 @@ export function addDefaultStateFilter(where = {}, table, hasLeftJoin = false, be
|
|
|
453
353
|
}
|
|
454
354
|
|
|
455
355
|
export function whereKeysToSnake(where) {
|
|
456
|
-
|
|
457
|
-
return where;
|
|
458
|
-
}
|
|
459
|
-
if (Array.isArray(where)) {
|
|
460
|
-
return where.map((item) => whereKeysToSnake(item));
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const result = {};
|
|
464
|
-
for (const [key, value] of Object.entries(where)) {
|
|
465
|
-
if (key === "$or" || key === "$and") {
|
|
466
|
-
result[key] = Array.isArray(value) ? value.map((item) => whereKeysToSnake(item)) : [];
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
|
|
356
|
+
return mapWhereKeys(where, (key) => {
|
|
470
357
|
assertNoExprField(key);
|
|
471
358
|
if (key.includes("$")) {
|
|
472
359
|
const lastDollarIndex = key.lastIndexOf("$");
|
|
473
360
|
const fieldName = key.substring(0, lastDollarIndex);
|
|
474
361
|
const operator = key.substring(lastDollarIndex);
|
|
475
|
-
|
|
476
|
-
continue;
|
|
362
|
+
return `${snakeCase(fieldName)}${operator}`;
|
|
477
363
|
}
|
|
478
364
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
result[snakeKey] = whereKeysToSnake(value);
|
|
482
|
-
} else {
|
|
483
|
-
result[snakeKey] = value;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
return result;
|
|
365
|
+
return snakeCase(key);
|
|
366
|
+
});
|
|
488
367
|
}
|
|
489
368
|
|
|
490
369
|
function serializeArrayFields(data) {
|
|
@@ -537,6 +416,19 @@ function cleanAndSnakeAndSerializeWriteData(data, excludeValues = [null, undefin
|
|
|
537
416
|
return serializeArrayFields(snakeData);
|
|
538
417
|
}
|
|
539
418
|
|
|
419
|
+
function cloneRecord(record) {
|
|
420
|
+
const result = {};
|
|
421
|
+
for (const [key, value] of Object.entries(record)) {
|
|
422
|
+
result[key] = value;
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function prepareWriteUserData(data, allowState) {
|
|
428
|
+
const serializedData = cleanAndSnakeAndSerializeWriteData(data);
|
|
429
|
+
return stripSystemFieldsForWrite(serializedData, { allowState: allowState });
|
|
430
|
+
}
|
|
431
|
+
|
|
540
432
|
function stripSystemFieldsForWrite(data, options) {
|
|
541
433
|
const result = {};
|
|
542
434
|
for (const [key, value] of Object.entries(data)) {
|
|
@@ -551,13 +443,7 @@ function stripSystemFieldsForWrite(data, options) {
|
|
|
551
443
|
}
|
|
552
444
|
|
|
553
445
|
export function buildInsertRow(options) {
|
|
554
|
-
const
|
|
555
|
-
const userData = stripSystemFieldsForWrite(serializedData, { allowState: false });
|
|
556
|
-
|
|
557
|
-
const result = {};
|
|
558
|
-
for (const [key, value] of Object.entries(userData)) {
|
|
559
|
-
result[key] = value;
|
|
560
|
-
}
|
|
446
|
+
const result = cloneRecord(prepareWriteUserData(options.data, false));
|
|
561
447
|
|
|
562
448
|
if (options.beflyMode === "auto") {
|
|
563
449
|
validateTimeIdValue(options.id);
|
|
@@ -573,13 +459,7 @@ export function buildInsertRow(options) {
|
|
|
573
459
|
}
|
|
574
460
|
|
|
575
461
|
export function buildUpdateRow(options) {
|
|
576
|
-
const
|
|
577
|
-
const userData = stripSystemFieldsForWrite(serializedData, { allowState: options.allowState });
|
|
578
|
-
|
|
579
|
-
const result = {};
|
|
580
|
-
for (const [key, value] of Object.entries(userData)) {
|
|
581
|
-
result[key] = value;
|
|
582
|
-
}
|
|
462
|
+
const result = cloneRecord(prepareWriteUserData(options.data, options.allowState));
|
|
583
463
|
if (options.beflyMode !== "manual") {
|
|
584
464
|
result["updated_at"] = options.now;
|
|
585
465
|
}
|
|
@@ -587,8 +467,7 @@ export function buildUpdateRow(options) {
|
|
|
587
467
|
}
|
|
588
468
|
|
|
589
469
|
export function buildPartialUpdateData(options) {
|
|
590
|
-
|
|
591
|
-
return stripSystemFieldsForWrite(serializedData, { allowState: options.allowState });
|
|
470
|
+
return prepareWriteUserData(options.data, options.allowState);
|
|
592
471
|
}
|
|
593
472
|
|
|
594
473
|
export const builderMethods = {
|
|
@@ -649,15 +528,11 @@ export const builderMethods = {
|
|
|
649
528
|
|
|
650
529
|
normalizeLeftJoinOptions(options, cleanWhere) {
|
|
651
530
|
const processedFields = (options.fields || []).map((field) => processJoinField(field));
|
|
652
|
-
const
|
|
653
|
-
const
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
if (parsedJoin) {
|
|
658
|
-
leftJoins.push(parsedJoin);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
531
|
+
const mainTableRef = options.table[0];
|
|
532
|
+
const joinTableRefs = options.table.slice(1);
|
|
533
|
+
const normalizedTableRef = normalizeTableRef(mainTableRef);
|
|
534
|
+
const mainQualifier = getJoinMainQualifier(mainTableRef);
|
|
535
|
+
const leftJoins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
|
|
661
536
|
|
|
662
537
|
return {
|
|
663
538
|
table: normalizedTableRef,
|
|
@@ -672,7 +547,8 @@ export const builderMethods = {
|
|
|
672
547
|
},
|
|
673
548
|
|
|
674
549
|
async normalizeSingleTableOptions(options, cleanWhere) {
|
|
675
|
-
const
|
|
550
|
+
const tableRef = Array.isArray(options.table) ? options.table[0] : options.table;
|
|
551
|
+
const snakeTable = snakeCase(tableRef);
|
|
676
552
|
const processedFields = await fieldsToSnake(snakeTable, options.fields || [], this.getTableColumns.bind(this));
|
|
677
553
|
|
|
678
554
|
return {
|
|
@@ -680,7 +556,7 @@ export const builderMethods = {
|
|
|
680
556
|
tableQualifier: snakeTable,
|
|
681
557
|
fields: processedFields,
|
|
682
558
|
where: whereKeysToSnake(cleanWhere),
|
|
683
|
-
leftJoins:
|
|
559
|
+
leftJoins: [],
|
|
684
560
|
orderBy: orderByToSnake(options.orderBy || []),
|
|
685
561
|
page: options.page || 1,
|
|
686
562
|
limit: options.limit || 10
|
|
@@ -750,7 +626,7 @@ export const builderMethods = {
|
|
|
750
626
|
async prepareQueryOptions(options, label = "queryOptions") {
|
|
751
627
|
validateQueryOptions(options, label);
|
|
752
628
|
const cleanWhere = clearDeep(options.where || {});
|
|
753
|
-
const hasLeftJoin = options.leftJoin && options.leftJoin.length > 0;
|
|
629
|
+
const hasLeftJoin = Array.isArray(options.leftJoin) && options.leftJoin.length > 0;
|
|
754
630
|
|
|
755
631
|
if (hasLeftJoin) {
|
|
756
632
|
return this.normalizeLeftJoinOptions(options, cleanWhere);
|
|
@@ -759,6 +635,14 @@ export const builderMethods = {
|
|
|
759
635
|
return await this.normalizeSingleTableOptions(options, cleanWhere);
|
|
760
636
|
},
|
|
761
637
|
|
|
638
|
+
async prepareReadContext(options, label = "queryOptions") {
|
|
639
|
+
const prepared = await this.prepareQueryOptions(options, label);
|
|
640
|
+
return {
|
|
641
|
+
prepared: prepared,
|
|
642
|
+
whereFiltered: addDefaultStateFilter(prepared.where, prepared.tableQualifier, prepared.leftJoins.length > 0, this.beflyMode)
|
|
643
|
+
};
|
|
644
|
+
},
|
|
645
|
+
|
|
762
646
|
applyLeftJoins(builder, leftJoins) {
|
|
763
647
|
if (!leftJoins || leftJoins.length === 0) return;
|
|
764
648
|
|