@vantreeseba/drizzle-graphql 15.0.0 → 15.1.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/dist/index.d.cts CHANGED
@@ -458,7 +458,9 @@ type DrizzleErrorCode =
458
458
  /** A stored value could not be represented by the scalar that transports it. */
459
459
  'DRIZZLE_UNREPRESENTABLE_VALUE' |
460
460
  /** The request's shared mutation transaction was rolled back, so this field never ran. */
461
- 'DRIZZLE_TRANSACTION_ABORTED';
461
+ 'DRIZZLE_TRANSACTION_ABORTED' |
462
+ /** A relation was read on a path it cannot be loaded through (see the message). */
463
+ 'DRIZZLE_UNSUPPORTED_RELATION';
462
464
  /**
463
465
  * What an error was about, published as `extensions.drizzle` beside the code — the runtime
464
466
  * half of the `extensions.drizzle` markers a generated field carries.
@@ -607,8 +609,11 @@ type ColumnDescriber = (column: Column, info: ColumnDocInfo) => string | undefin
607
609
  * `BuildSchemaConfig.deprecateColumn`.
608
610
  */
609
611
  type ColumnDeprecator = (column: Column, info: ColumnDocInfo) => string | undefined;
610
- /** Supplies the GraphQL `description` for a table's object type. See `BuildSchemaConfig.describeTable`. */
611
- type TableDescriber = (tableName: string) => string | undefined;
612
+ /**
613
+ * Supplies the GraphQL `description` for a table's object type. Receives the Drizzle table
614
+ * itself as well as its schema key. See `BuildSchemaConfig.describeTable`.
615
+ */
616
+ type TableDescriber = (tableName: string, table: Table) => string | undefined;
612
617
  /**
613
618
  * Supplies the GraphQL `description` for a relation field. See
614
619
  * `BuildSchemaConfig.describeRelation`.
@@ -2444,6 +2449,17 @@ type BuildSchemaConfig = {
2444
2449
  * ```ts
2445
2450
  * buildSchema(db, { describeTable: (tableName) => tableDocs[tableName] });
2446
2451
  * ```
2452
+ *
2453
+ * The second argument is the Drizzle table itself, so documentation attached to the table
2454
+ * where it is declared can be read straight off it:
2455
+ *
2456
+ * ```ts
2457
+ * const tableDocs = new WeakMap<object, string>();
2458
+ * export const Users = pgTable('users', { ... });
2459
+ * tableDocs.set(Users, 'A person who can sign in.');
2460
+ *
2461
+ * buildSchema(db, { describeTable: (_tableName, table) => tableDocs.get(table) });
2462
+ * ```
2447
2463
  */
2448
2464
  describeTable?: TableDescriber;
2449
2465
  /**
package/dist/index.d.ts CHANGED
@@ -458,7 +458,9 @@ type DrizzleErrorCode =
458
458
  /** A stored value could not be represented by the scalar that transports it. */
459
459
  'DRIZZLE_UNREPRESENTABLE_VALUE' |
460
460
  /** The request's shared mutation transaction was rolled back, so this field never ran. */
461
- 'DRIZZLE_TRANSACTION_ABORTED';
461
+ 'DRIZZLE_TRANSACTION_ABORTED' |
462
+ /** A relation was read on a path it cannot be loaded through (see the message). */
463
+ 'DRIZZLE_UNSUPPORTED_RELATION';
462
464
  /**
463
465
  * What an error was about, published as `extensions.drizzle` beside the code — the runtime
464
466
  * half of the `extensions.drizzle` markers a generated field carries.
@@ -607,8 +609,11 @@ type ColumnDescriber = (column: Column, info: ColumnDocInfo) => string | undefin
607
609
  * `BuildSchemaConfig.deprecateColumn`.
608
610
  */
609
611
  type ColumnDeprecator = (column: Column, info: ColumnDocInfo) => string | undefined;
610
- /** Supplies the GraphQL `description` for a table's object type. See `BuildSchemaConfig.describeTable`. */
611
- type TableDescriber = (tableName: string) => string | undefined;
612
+ /**
613
+ * Supplies the GraphQL `description` for a table's object type. Receives the Drizzle table
614
+ * itself as well as its schema key. See `BuildSchemaConfig.describeTable`.
615
+ */
616
+ type TableDescriber = (tableName: string, table: Table) => string | undefined;
612
617
  /**
613
618
  * Supplies the GraphQL `description` for a relation field. See
614
619
  * `BuildSchemaConfig.describeRelation`.
@@ -2444,6 +2449,17 @@ type BuildSchemaConfig = {
2444
2449
  * ```ts
2445
2450
  * buildSchema(db, { describeTable: (tableName) => tableDocs[tableName] });
2446
2451
  * ```
2452
+ *
2453
+ * The second argument is the Drizzle table itself, so documentation attached to the table
2454
+ * where it is declared can be read straight off it:
2455
+ *
2456
+ * ```ts
2457
+ * const tableDocs = new WeakMap<object, string>();
2458
+ * export const Users = pgTable('users', { ... });
2459
+ * tableDocs.set(Users, 'A person who can sign in.');
2460
+ *
2461
+ * buildSchema(db, { describeTable: (_tableName, table) => tableDocs.get(table) });
2462
+ * ```
2447
2463
  */
2448
2464
  describeTable?: TableDescriber;
2449
2465
  /**
package/dist/index.js CHANGED
@@ -1789,6 +1789,48 @@ const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
1789
1789
  };
1790
1790
  };
1791
1791
  /**
1792
+ * The junction table and its two columns for a `.through()` (many-to-many) relation:
1793
+ * `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
1794
+ * with no junction; `null` for one whose junction cannot be used here — more than one column
1795
+ * on either side, or columns that are not on the junction table.
1796
+ */
1797
+ const extractJunctionColumns = (relEntry) => {
1798
+ const rel = relEntry.relation ?? relEntry;
1799
+ const junction = rel?.throughTable;
1800
+ if (!junction) return;
1801
+ const sourceEntries = rel.through?.source;
1802
+ const targetEntries = rel.through?.target;
1803
+ if (sourceEntries?.length !== 1 || targetEntries?.length !== 1) return null;
1804
+ const junctionCols = Object.values(getColumns(junction));
1805
+ const sourceCol = unwrapRelationColumn(sourceEntries[0]);
1806
+ const targetCol = unwrapRelationColumn(targetEntries[0]);
1807
+ if (!sourceCol || !targetCol || !junctionCols.includes(sourceCol) || !junctionCols.includes(targetCol)) return null;
1808
+ return {
1809
+ table: junction,
1810
+ sourceCol,
1811
+ targetCol
1812
+ };
1813
+ };
1814
+ /**
1815
+ * The relation's own declared `where` as a condition on a query that reads the target table,
1816
+ * or `undefined` when it has none. The eager path gets this from drizzle's `with:`; the batched
1817
+ * field resolver and `<relation>Aggregate` read the target table directly, so they have to add
1818
+ * it themselves or they return rows the relation hides.
1819
+ *
1820
+ * A reversed filter (inherited from the relation on the other side) is written against the
1821
+ * parent table, so it is applied as "the row's parent key belongs to a parent that passes it".
1822
+ * `parentKeyCol` is the parent's join column and `groupCol` the column in the query that holds
1823
+ * the parent key for each row — the target's foreign key, or the junction's source column.
1824
+ */
1825
+ const relationOwnCondition = (relEntry, parentTable, targetTable, parentKeyCol, groupCol) => {
1826
+ const rel = relEntry.relation ?? relEntry;
1827
+ const ownWhere = relationOwnWhere(rel);
1828
+ if (!ownWhere) return;
1829
+ if (!isRelationFilterReversed(rel)) return relationsFilterToSQL(targetTable, ownWhere);
1830
+ const parentFilter = relationsFilterToSQL(parentTable, ownWhere);
1831
+ return sql`${groupCol} in (select ${parentKeyCol} from ${parentTable} where ${parentFilter})`;
1832
+ };
1833
+ /**
1792
1834
  * Whether a to-one relation can back a relation orderBy hop. `.through()` (junction)
1793
1835
  * relations are excluded — the ordering subquery only joins the direct target, never the
1794
1836
  * junction table. (Relation *filters* do support `.through()` — this guard is orderBy-only.)
@@ -2952,6 +2994,11 @@ const getUniqueColumnSets = (table, getTableConfig) => {
2952
2994
  //#endregion
2953
2995
  //#region src/util/builders/common/distinct.ts
2954
2996
  /**
2997
+ * Alias the parent key comes back under when the pass runs through a junction table. See
2998
+ * `selectDistinctKeys`' `junction` parameter.
2999
+ */
3000
+ const DISTINCT_PARENT = "__dgql_distinct_parent";
3001
+ /**
2955
3002
  * Keeps the first row of each distinct combination of the requested columns, following the
2956
3003
  * query's own ordering, then applies `limit`/`offset` to what survives — and returns the
2957
3004
  * surviving rows' primary key values in that order.
@@ -2964,9 +3011,14 @@ const getUniqueColumnSets = (table, getTableConfig) => {
2964
3011
  * `partitionBy` prepends extra columns to the window's partition. A to-many relation field
2965
3012
  * uses it to pass the foreign key, which makes the pass distinct *within each parent* rather
2966
3013
  * than across the whole batch — the same meaning `distinct` has on a root list.
3014
+ *
3015
+ * `junction` is for a many-to-many relation: the target is joined to the junction `on` the
3016
+ * given condition, and each returned key also carries the parent it was kept for, under
3017
+ * {@link DISTINCT_PARENT}. A target row linked to several parents can be kept for one and
3018
+ * dropped for another, so a key alone cannot narrow such a batch.
2967
3019
  */
2968
3020
  const selectDistinctKeys = async (params) => {
2969
- const { db, table, tableName, distinct, pkNames, where, orderBy, limit, offset, partitionBy } = params;
3021
+ const { db, table, tableName, distinct, pkNames, where, orderBy, limit, offset, partitionBy, junction } = params;
2970
3022
  const cols = getColumns(table);
2971
3023
  if (!pkNames.length) throw drizzleError(`Table ${tableName} has no primary key, so 'distinct' cannot be applied to it.`, { code: "DRIZZLE_INVALID_DISTINCT" });
2972
3024
  const requestedCols = distinct.map((name) => cols[name]).filter(Boolean);
@@ -2975,12 +3027,17 @@ const selectDistinctKeys = async (params) => {
2975
3027
  const orderEntries = orderBy ? orderByEntries(orderBy) : [];
2976
3028
  const windowOrder = [...orderEntries.flatMap(([column, direction, nulls]) => orderExpressions(cols[column], direction, nulls, cols[column])), ...primaryKeyOrderExprs(table, pkNames)];
2977
3029
  const rowNumber = sql`row_number() over (partition by ${sql.join(partitionCols, sql`, `)} order by ${sql.join(windowOrder, sql`, `)})`.as(DISTINCT_RN);
2978
- const sub = db.select({
3030
+ const selected = db.select({
2979
3031
  ...cols,
3032
+ ...junction ? { [DISTINCT_PARENT]: junction.parentKey } : {},
2980
3033
  [DISTINCT_RN]: rowNumber
2981
- }).from(table).where(where).as("__dgql_distinct");
3034
+ }).from(table);
3035
+ const sub = (junction ? selected.innerJoin(junction.table, junction.on) : selected).where(where).as("__dgql_distinct");
2982
3036
  const outerOrder = [...orderEntries.flatMap(([column, direction, nulls]) => orderExpressions(sub[column], direction, nulls, cols[column])), ...pkNames.filter((name) => sub[name]).map((name) => asc(sub[name]))];
2983
- let query = db.select(Object.fromEntries(pkNames.map((name) => [name, sub[name]]))).from(sub).where(eq(sub[DISTINCT_RN], 1)).orderBy(...outerOrder);
3037
+ let query = db.select({
3038
+ ...Object.fromEntries(pkNames.map((name) => [name, sub[name]])),
3039
+ ...junction ? { [DISTINCT_PARENT]: sub[DISTINCT_PARENT] } : {}
3040
+ }).from(sub).where(eq(sub[DISTINCT_RN], 1)).orderBy(...outerOrder);
2984
3041
  if (offset) query = query.offset(offset);
2985
3042
  if (limit != null) query = query.limit(limit);
2986
3043
  return await query;
@@ -4601,16 +4658,17 @@ const identifyRows = (field, args) => {
4601
4658
  * Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
4602
4659
  * Returns raw rows (NOT remapped); the caller groups + remaps them.
4603
4660
  */
4604
- const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs) => {
4661
+ const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
4605
4662
  const cols = columns ?? getColumns(targetTable);
4606
4663
  const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
4607
4664
  const orderClause = orderExprs.length ? sql` order by ${sql.join(orderExprs, sql`, `)}` : sql``;
4608
4665
  const RN = "__drizzle_graphql_rn";
4609
- const rowNumber = sql`row_number() over (partition by ${foreignCol}${orderClause})`.as(RN);
4610
- const sub = db.select({
4666
+ const partitionCol = junction ? junction.sourceCol : foreignCol;
4667
+ const rowNumber = sql`row_number() over (partition by ${partitionCol}${orderClause})`.as(RN);
4668
+ const sub = withJunction(db.select({
4611
4669
  ...cols,
4612
4670
  [RN]: rowNumber
4613
- }).from(targetTable).where(whereCondition).as("__paginated");
4671
+ }).from(targetTable), junction, foreignCol).where(whereCondition).as("__paginated");
4614
4672
  const lower = offset ?? 0;
4615
4673
  const windowConds = [gt(sub[RN], lower)];
4616
4674
  if (limit != null) windowConds.push(lte(sub[RN], lower + limit));
@@ -4619,6 +4677,14 @@ const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereC
4619
4677
  return rows;
4620
4678
  };
4621
4679
  /**
4680
+ * Alias the parent key is selected under when a relation goes through a junction table. The
4681
+ * key lives on the junction, not the target, so it has to be selected explicitly to group the
4682
+ * rows by parent — and a target row linked to several parents comes back once per parent.
4683
+ */
4684
+ const PARENT_KEY = "__drizzle_graphql_parent";
4685
+ /** `from(target)`, joined to the junction table when the relation has one. */
4686
+ const withJunction = (query, junction, foreignCol) => junction ? query.innerJoin(junction.table, eq(junction.targetCol, foreignCol)) : query;
4687
+ /**
4622
4688
  * Creates a RelationResolverFactory that generates field-level resolvers for each relation.
4623
4689
  * Each resolver:
4624
4690
  * 1. Returns pre-fetched data if the parent resolver already included it (eager path, zero cost).
@@ -4640,6 +4706,10 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4640
4706
  const joinCols = extractRelationJoinColumns(relEntry, parentTable, targetTable);
4641
4707
  if (!joinCols) return;
4642
4708
  const { localColPropName, foreignCol, foreignColPropName } = joinCols;
4709
+ const junction = extractJunctionColumns(relEntry);
4710
+ const groupCol = junction ? junction.sourceCol : foreignCol;
4711
+ const groupKey = junction ? PARENT_KEY : foreignColPropName;
4712
+ const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, getColumns(parentTable)[localColPropName], groupCol);
4643
4713
  const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, isOne && relEntry.relation?.optional === false);
4644
4714
  const limitPolicy = isOne ? void 0 : limits?.(targetTableName);
4645
4715
  const targetPkNames = relEntry.targetPkNames ?? [];
@@ -4717,11 +4787,12 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4717
4787
  loaderKey: `${tableName}::${relationName}::${argsKey}`
4718
4788
  };
4719
4789
  });
4790
+ if (junction === null) throw drizzleError(`Relation '${tableName}.${relationName}' goes through a junction with a composite key, which can only be loaded together with its parent. Leave it to eager loading (eagerLoadRelations).`, { code: "DRIZZLE_UNSUPPORTED_RELATION" });
4720
4791
  return getOrCreateLoader(context, loaderKey, async (parentIds) => {
4721
4792
  const executor = resolveExecutor(db, context);
4722
4793
  const uniqueIds = [...new Set(parentIds)];
4723
4794
  const scope = resolveScope(policies, context, filterCtx);
4724
- let whereCondition = withScope(scope, targetTableName, targetTable, and(inArray(foreignCol, uniqueIds), whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
4795
+ let whereCondition = withScope(scope, targetTableName, targetTable, and(inArray(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
4725
4796
  if (distinct) {
4726
4797
  const keys = await selectDistinctKeys({
4727
4798
  db: executor,
@@ -4731,27 +4802,41 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4731
4802
  pkNames: targetPkNames,
4732
4803
  where: whereCondition,
4733
4804
  orderBy: orderByArg,
4734
- partitionBy: [foreignCol]
4805
+ partitionBy: [groupCol],
4806
+ junction: junction ? {
4807
+ table: junction.table,
4808
+ on: eq(junction.targetCol, foreignCol),
4809
+ parentKey: groupCol
4810
+ } : void 0
4735
4811
  });
4736
4812
  if (!keys.length) return parentIds.map(() => isOne ? null : []);
4737
- whereCondition = and(whereCondition, primaryKeyRestriction(targetTable, targetPkNames, keys));
4813
+ whereCondition = and(whereCondition, junction ? or(...keys.map((key) => and(eq(groupCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
4738
4814
  }
4815
+ const selection = junction ? {
4816
+ ...columns ?? getColumns(targetTable),
4817
+ [PARENT_KEY]: groupCol
4818
+ } : columns;
4739
4819
  let rows;
4740
- if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, columns, relationFilterCtx(filterCtx, targetTableName), whereArg);
4820
+ if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName), whereArg, junction);
4741
4821
  else {
4742
- let q = (columns ? executor.select(columns) : executor.select()).from(targetTable).where(whereCondition);
4822
+ let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
4743
4823
  const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
4744
4824
  if (orderExprs.length) q = q.orderBy(...orderExprs);
4745
4825
  rows = await q;
4746
4826
  }
4747
4827
  if (cursorEntries) attachRowCursors(rows, cursorEntries);
4828
+ const keyOf = (row) => {
4829
+ const key = String(row[groupKey]);
4830
+ if (junction) delete row[PARENT_KEY];
4831
+ return key;
4832
+ };
4748
4833
  if (isOne) {
4749
- const byKey = new Map(rows.map((row) => [String(row[foreignColPropName]), row]));
4834
+ const byKey = new Map(rows.map((row) => [keyOf(row), row]));
4750
4835
  remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
4751
4836
  return parentIds.map((id) => byKey.get(String(id)) ?? null);
4752
4837
  }
4753
4838
  const grouped = new Map(uniqueIds.map((id) => [String(id), []]));
4754
- for (const row of rows) grouped.get(String(row[foreignColPropName]))?.push(row);
4839
+ for (const row of rows) grouped.get(keyOf(row))?.push(row);
4755
4840
  remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
4756
4841
  return parentIds.map((id) => grouped.get(String(id)) ?? []);
4757
4842
  }).load(localValue);
@@ -5086,7 +5171,7 @@ const generateSelectFields = (tables, tableName, relationMap, fromTableName, fro
5086
5171
  });
5087
5172
  const shell = new GraphQLObjectType({
5088
5173
  name: typeName,
5089
- description: cacheCtx.docs.describeTable?.(tableName),
5174
+ description: cacheCtx.docs.describeTable?.(tableName, table),
5090
5175
  fields: () => ({
5091
5176
  ...tableFields,
5092
5177
  ...container.fields
@@ -5120,7 +5205,7 @@ const generateSelectFields = (tables, tableName, relationMap, fromTableName, fro
5120
5205
  defaultName: resolveTypeName(targetTableName, typeNameMapper),
5121
5206
  table: targetTableName
5122
5207
  }),
5123
- description: cacheCtx.docs.describeTable?.(targetTableName),
5208
+ description: cacheCtx.docs.describeTable?.(targetTableName, targetTable),
5124
5209
  fields: () => ({
5125
5210
  ...targetTableFields,
5126
5211
  ...capturedTargetContainer.fields
@@ -5271,7 +5356,7 @@ const generateTableTypes = (tableName, tables, relationMap, withReturning, relat
5271
5356
  defaultName: resolveTypeName(tableName, typeNameMapper),
5272
5357
  table: tableName
5273
5358
  }),
5274
- description: cacheCtx.docs.describeTable?.(tableName),
5359
+ description: cacheCtx.docs.describeTable?.(tableName, tables[tableName]),
5275
5360
  fields: {
5276
5361
  ...tableFields,
5277
5362
  ...relationFields
@@ -5838,6 +5923,10 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
5838
5923
  return;
5839
5924
  }
5840
5925
  const { localColPropName, foreignCol } = joinCols;
5926
+ const junction = extractJunctionColumns(relEntry);
5927
+ if (junction === null) return;
5928
+ const groupCol = junction ? junction.sourceCol : foreignCol;
5929
+ const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, getColumns(parentTable)[localColPropName], groupCol);
5841
5930
  const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, false);
5842
5931
  const targetTypeName = resolveTypeName(targetTableName, typeNameMapper);
5843
5932
  const type = generateAggregateTypes(targetTable, targetTableName, targetTypeName, cacheCtx);
@@ -5868,11 +5957,12 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
5868
5957
  const loader = getOrCreateLoader(context, loaderKey, async (parentIds) => {
5869
5958
  const executor = resolveExecutor(db, context);
5870
5959
  const uniqueIds = [...new Set(parentIds)];
5871
- const whereCondition = withScope(resolveScope(policies, context, filterCtx), targetTableName, targetTable, and(inArray(foreignCol, uniqueIds), whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName)) : void 0), deleted ?? defaultDeleted);
5872
- const rows = await executor.select({
5873
- [GROUP_KEY]: foreignCol,
5960
+ const whereCondition = withScope(resolveScope(policies, context, filterCtx), targetTableName, targetTable, and(inArray(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName)) : void 0), deleted ?? defaultDeleted);
5961
+ const selected = executor.select({
5962
+ [GROUP_KEY]: groupCol,
5874
5963
  ...request.selection
5875
- }).from(targetTable).where(whereCondition).groupBy(foreignCol);
5964
+ }).from(targetTable);
5965
+ const rows = await (junction ? selected.innerJoin(junction.table, eq(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(groupCol);
5876
5966
  const byKey = new Map(rows.map((row) => [row[GROUP_KEY], row]));
5877
5967
  return parentIds.map((id) => byKey.get(id) ?? {});
5878
5968
  });