befly 3.20.8 → 3.20.10

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 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: ["beflyDictType ON beflyDict.typeCode = beflyDictType.code"],
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
  });
@@ -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: ["beflyDictType ON beflyDict.typeCode = beflyDictType.code"],
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: ["beflyDictType ON beflyDict.typeCode = beflyDictType.code"],
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
@@ -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(CacheKeys.roleApis(ctx.roleCode), ctx.apiPath);
52
+ const hasPermission = await befly.redis.sismember(`role:apis:${ctx.roleCode}`, ctx.apiPath);
54
53
 
55
54
  if (!hasPermission) {
56
55
  ctx.response = ErrorResponse(
@@ -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(CacheKeys.apisAll(), apiList.data.lists);
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(CacheKeys.menusAll(), menus.data.lists);
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) => CacheKeys.roleApis(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: CacheKeys.roleApis(roleCode), members: members });
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) => CacheKeys.roleMenus(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: CacheKeys.roleMenus(roleCode), members: members });
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 = CacheKeys.roleApis(roleCode);
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 = CacheKeys.roleMenus(roleCode);
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(CacheKeys.apisAll());
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(CacheKeys.menusAll());
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(CacheKeys.roleApis(roleCode));
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(CacheKeys.roleMenus(roleCode));
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(CacheKeys.roleApis(roleCode), value);
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(CacheKeys.roleMenus(roleCode), value);
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(CacheKeys.roleApis(roleCode));
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(CacheKeys.roleMenus(roleCode));
445
+ const result = await this.redis.del(`role:menus:${roleCode}`);
447
446
  if (result > 0) {
448
447
  Logger.info(`✅ 已删除角色 ${roleCode} 的菜单权限缓存`);
449
448
  return true;
@@ -156,6 +156,29 @@ 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
+
174
+ if (fieldName === "*") {
175
+ return `${qualifier}.*`;
176
+ }
177
+
178
+ return `${qualifier}.${snakeCase(fieldName)}`;
179
+ }
180
+
181
+ function normalizeOrderBy(orderBy, mapField) {
159
182
  if (!orderBy || !Array.isArray(orderBy)) {
160
183
  return orderBy;
161
184
  }
@@ -170,14 +193,35 @@ export function orderByToSnake(orderBy) {
170
193
  return item;
171
194
  }
172
195
 
173
- const field = parts[0];
174
- const direction = parts[1];
175
- if (!isString(field) || !isString(direction)) {
176
- return item;
196
+ return `${mapField(parts[0].trim())}#${parts[1].trim()}`;
197
+ });
198
+ }
199
+
200
+ function mapWhereKeys(where, mapKey) {
201
+ if (!where || typeof where !== "object") {
202
+ return where;
203
+ }
204
+ if (Array.isArray(where)) {
205
+ return where.map((item) => mapWhereKeys(item, mapKey));
206
+ }
207
+
208
+ const result = {};
209
+ for (const [key, value] of Object.entries(where)) {
210
+ if (key === "$or" || key === "$and") {
211
+ result[key] = Array.isArray(value) ? value.map((item) => mapWhereKeys(item, mapKey)) : [];
212
+ continue;
177
213
  }
178
214
 
179
- return `${snakeCase(field.trim())}#${direction.trim()}`;
180
- });
215
+ const normalizedKey = mapKey(key);
216
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
217
+ result[normalizedKey] = mapWhereKeys(value, mapKey);
218
+ continue;
219
+ }
220
+
221
+ result[normalizedKey] = value;
222
+ }
223
+
224
+ return result;
181
225
  }
182
226
 
183
227
  export function processJoinField(field) {
@@ -196,36 +240,7 @@ export function processJoinField(field) {
196
240
  return `${processJoinField(fieldPart.trim())} AS ${aliasPart.trim()}`;
197
241
  }
198
242
 
199
- if (field.includes(".")) {
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);
243
+ return normalizeQualifierField(field);
229
244
  }
230
245
 
231
246
  function processJoinWhereKey(key) {
@@ -239,136 +254,26 @@ function processJoinWhereKey(key) {
239
254
  const fieldPart = key.substring(0, lastDollarIndex);
240
255
  const operator = key.substring(lastDollarIndex);
241
256
 
242
- if (fieldPart.includes(".")) {
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)}`;
257
+ return `${normalizeQualifierField(fieldPart)}${operator}`;
299
258
  }
300
259
 
301
- return snakeCase(key);
260
+ return normalizeQualifierField(key);
302
261
  }
303
262
 
304
263
  export function processJoinWhere(where) {
305
- if (!where || typeof where !== "object") {
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;
264
+ return mapWhereKeys(where, processJoinWhereKey);
325
265
  }
326
266
 
327
267
  export function processJoinOrderBy(orderBy) {
328
- if (!orderBy || !Array.isArray(orderBy)) {
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
- });
268
+ return normalizeOrderBy(orderBy, processJoinField);
350
269
  }
351
270
 
352
- export function parseLeftJoinItem(joinItem) {
353
- if (!isString(joinItem)) {
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
-
271
+ export function parseLeftJoinItem(joinTable, joinItem) {
272
+ const parts = joinItem.split(" ");
368
273
  return {
369
274
  type: "left",
370
- table: normalizeTableRef(table),
371
- on: processJoinOn(on)
275
+ table: normalizeTableRef(joinTable),
276
+ on: processJoinOn(`${parts[0]} = ${parts[1]}`)
372
277
  };
373
278
  }
374
279
 
@@ -453,38 +358,17 @@ export function addDefaultStateFilter(where = {}, table, hasLeftJoin = false, be
453
358
  }
454
359
 
455
360
  export function whereKeysToSnake(where) {
456
- if (!where || typeof where !== "object") {
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
-
361
+ return mapWhereKeys(where, (key) => {
470
362
  assertNoExprField(key);
471
363
  if (key.includes("$")) {
472
364
  const lastDollarIndex = key.lastIndexOf("$");
473
365
  const fieldName = key.substring(0, lastDollarIndex);
474
366
  const operator = key.substring(lastDollarIndex);
475
- result[snakeCase(fieldName) + operator] = value;
476
- continue;
367
+ return `${snakeCase(fieldName)}${operator}`;
477
368
  }
478
369
 
479
- const snakeKey = snakeCase(key);
480
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
481
- result[snakeKey] = whereKeysToSnake(value);
482
- } else {
483
- result[snakeKey] = value;
484
- }
485
- }
486
-
487
- return result;
370
+ return snakeCase(key);
371
+ });
488
372
  }
489
373
 
490
374
  function serializeArrayFields(data) {
@@ -537,6 +421,19 @@ function cleanAndSnakeAndSerializeWriteData(data, excludeValues = [null, undefin
537
421
  return serializeArrayFields(snakeData);
538
422
  }
539
423
 
424
+ function cloneRecord(record) {
425
+ const result = {};
426
+ for (const [key, value] of Object.entries(record)) {
427
+ result[key] = value;
428
+ }
429
+ return result;
430
+ }
431
+
432
+ function prepareWriteUserData(data, allowState) {
433
+ const serializedData = cleanAndSnakeAndSerializeWriteData(data);
434
+ return stripSystemFieldsForWrite(serializedData, { allowState: allowState });
435
+ }
436
+
540
437
  function stripSystemFieldsForWrite(data, options) {
541
438
  const result = {};
542
439
  for (const [key, value] of Object.entries(data)) {
@@ -551,13 +448,7 @@ function stripSystemFieldsForWrite(data, options) {
551
448
  }
552
449
 
553
450
  export function buildInsertRow(options) {
554
- const serializedData = cleanAndSnakeAndSerializeWriteData(options.data);
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
- }
451
+ const result = cloneRecord(prepareWriteUserData(options.data, false));
561
452
 
562
453
  if (options.beflyMode === "auto") {
563
454
  validateTimeIdValue(options.id);
@@ -573,13 +464,7 @@ export function buildInsertRow(options) {
573
464
  }
574
465
 
575
466
  export function buildUpdateRow(options) {
576
- const serializedData = cleanAndSnakeAndSerializeWriteData(options.data);
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
- }
467
+ const result = cloneRecord(prepareWriteUserData(options.data, options.allowState));
583
468
  if (options.beflyMode !== "manual") {
584
469
  result["updated_at"] = options.now;
585
470
  }
@@ -587,8 +472,7 @@ export function buildUpdateRow(options) {
587
472
  }
588
473
 
589
474
  export function buildPartialUpdateData(options) {
590
- const serializedData = cleanAndSnakeAndSerializeWriteData(options.data);
591
- return stripSystemFieldsForWrite(serializedData, { allowState: options.allowState });
475
+ return prepareWriteUserData(options.data, options.allowState);
592
476
  }
593
477
 
594
478
  export const builderMethods = {
@@ -649,15 +533,11 @@ export const builderMethods = {
649
533
 
650
534
  normalizeLeftJoinOptions(options, cleanWhere) {
651
535
  const processedFields = (options.fields || []).map((field) => processJoinField(field));
652
- const normalizedTableRef = normalizeTableRef(options.table);
653
- const mainQualifier = getJoinMainQualifier(options.table);
654
- const leftJoins = [];
655
- for (const joinItem of options.leftJoin || []) {
656
- const parsedJoin = parseLeftJoinItem(joinItem);
657
- if (parsedJoin) {
658
- leftJoins.push(parsedJoin);
659
- }
660
- }
536
+ const mainTableRef = options.table[0];
537
+ const joinTableRefs = options.table.slice(1);
538
+ const normalizedTableRef = normalizeTableRef(mainTableRef);
539
+ const mainQualifier = getJoinMainQualifier(mainTableRef);
540
+ const leftJoins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
661
541
 
662
542
  return {
663
543
  table: normalizedTableRef,
@@ -672,7 +552,8 @@ export const builderMethods = {
672
552
  },
673
553
 
674
554
  async normalizeSingleTableOptions(options, cleanWhere) {
675
- const snakeTable = snakeCase(options.table);
555
+ const tableRef = Array.isArray(options.table) ? options.table[0] : options.table;
556
+ const snakeTable = snakeCase(tableRef);
676
557
  const processedFields = await fieldsToSnake(snakeTable, options.fields || [], this.getTableColumns.bind(this));
677
558
 
678
559
  return {
@@ -680,7 +561,7 @@ export const builderMethods = {
680
561
  tableQualifier: snakeTable,
681
562
  fields: processedFields,
682
563
  where: whereKeysToSnake(cleanWhere),
683
- leftJoins: undefined,
564
+ leftJoins: [],
684
565
  orderBy: orderByToSnake(options.orderBy || []),
685
566
  page: options.page || 1,
686
567
  limit: options.limit || 10
@@ -750,7 +631,7 @@ export const builderMethods = {
750
631
  async prepareQueryOptions(options, label = "queryOptions") {
751
632
  validateQueryOptions(options, label);
752
633
  const cleanWhere = clearDeep(options.where || {});
753
- const hasLeftJoin = options.leftJoin && options.leftJoin.length > 0;
634
+ const hasLeftJoin = Array.isArray(options.leftJoin) && options.leftJoin.length > 0;
754
635
 
755
636
  if (hasLeftJoin) {
756
637
  return this.normalizeLeftJoinOptions(options, cleanWhere);
@@ -759,6 +640,14 @@ export const builderMethods = {
759
640
  return await this.normalizeSingleTableOptions(options, cleanWhere);
760
641
  },
761
642
 
643
+ async prepareReadContext(options, label = "queryOptions") {
644
+ const prepared = await this.prepareQueryOptions(options, label);
645
+ return {
646
+ prepared: prepared,
647
+ whereFiltered: addDefaultStateFilter(prepared.where, prepared.tableQualifier, prepared.leftJoins.length > 0, this.beflyMode)
648
+ };
649
+ },
650
+
762
651
  applyLeftJoins(builder, leftJoins) {
763
652
  if (!leftJoins || leftJoins.length === 0) return;
764
653