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 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,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
- const field = parts[0];
174
- const direction = parts[1];
175
- if (!isString(field) || !isString(direction)) {
176
- return item;
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
- return `${snakeCase(field.trim())}#${direction.trim()}`;
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
- 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);
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
- 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)}`;
252
+ return `${normalizeQualifierField(fieldPart)}${operator}`;
299
253
  }
300
254
 
301
- return snakeCase(key);
255
+ return normalizeQualifierField(key);
302
256
  }
303
257
 
304
258
  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;
259
+ return mapWhereKeys(where, processJoinWhereKey);
325
260
  }
326
261
 
327
262
  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
- });
263
+ return normalizeOrderBy(orderBy, processJoinField);
350
264
  }
351
265
 
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
-
266
+ export function parseLeftJoinItem(joinTable, joinItem) {
267
+ const parts = joinItem.split(" ");
368
268
  return {
369
269
  type: "left",
370
- table: normalizeTableRef(table),
371
- on: processJoinOn(on)
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
- 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
-
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
- result[snakeCase(fieldName) + operator] = value;
476
- continue;
362
+ return `${snakeCase(fieldName)}${operator}`;
477
363
  }
478
364
 
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;
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 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
- }
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 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
- }
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
- const serializedData = cleanAndSnakeAndSerializeWriteData(options.data);
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 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
- }
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 snakeTable = snakeCase(options.table);
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: undefined,
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