@vantreeseba/drizzle-graphql 15.1.2 → 15.1.4

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.cjs CHANGED
@@ -1802,6 +1802,11 @@ const warnUnresolvedJoin = (relEntry, tableName, relationName, omitted) => {
1802
1802
  * Returns the JS property name of the local column on the parent table and the
1803
1803
  * Column object for the foreign column on the target table, or undefined if the
1804
1804
  * relation internals are not accessible.
1805
+ *
1806
+ * Only the relation's *first* column pair is reported. A relation joined on several columns
1807
+ * (`from: [a, b], to: [x, y]`) is not described fully by it, so anything that reads rows by
1808
+ * this pair alone mixes up parents that share the first column. Read paths use
1809
+ * {@link extractRelationJoinColumnPairs}, which returns every pair.
1805
1810
  */
1806
1811
  const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
1807
1812
  const rel = relEntry.relation ?? relEntry;
@@ -1820,6 +1825,48 @@ const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
1820
1825
  };
1821
1826
  };
1822
1827
  /**
1828
+ * Every column pair the relation joins on, in declaration order — one for an ordinary relation,
1829
+ * several for one declared `from: [a, b], to: [x, y]`. A row of the target belongs to a parent
1830
+ * only when *all* pairs match, so a read path has to correlate on every one of them.
1831
+ *
1832
+ * `undefined` when the relation's columns cannot be matched to the two tables, or when its two
1833
+ * sides list a different number of columns.
1834
+ */
1835
+ const extractRelationJoinColumnPairs = (relEntry, parentTable, targetTable) => {
1836
+ const rel = relEntry.relation ?? relEntry;
1837
+ const sourceCols = relationColumns(rel, "source");
1838
+ const targetCols = relationColumns(rel, "target");
1839
+ if (!sourceCols?.length || sourceCols.length !== targetCols?.length) return;
1840
+ const parentEntries = Object.entries((0, drizzle_orm.getColumns)(parentTable));
1841
+ const targetEntries = Object.entries((0, drizzle_orm.getColumns)(targetTable));
1842
+ const pairs = [];
1843
+ for (let i = 0; i < sourceCols.length; i++) {
1844
+ const localCol = sourceCols[i];
1845
+ const foreignCol = targetCols[i];
1846
+ const localColPropName = parentEntries.find(([, c]) => c === localCol)?.[0];
1847
+ const foreignColPropName = targetEntries.find(([, c]) => c === foreignCol)?.[0];
1848
+ if (!localColPropName || !foreignColPropName) return;
1849
+ pairs.push({
1850
+ localColPropName,
1851
+ localCol,
1852
+ foreignColPropName,
1853
+ foreignCol
1854
+ });
1855
+ }
1856
+ return pairs;
1857
+ };
1858
+ /**
1859
+ * A string identifying one combination of join-column values, for grouping a batch's rows by
1860
+ * parent. Values are compared by their string form, as a single-column key always has been.
1861
+ */
1862
+ const joinKeyOf = (values) => values.length === 1 ? String(values[0]) : JSON.stringify(values.map((value) => String(value)));
1863
+ /**
1864
+ * The rows whose `cols` hold one of `keys`: `col IN (…)` for a single join column, and an `OR`
1865
+ * of per-parent equalities for several — `(a = 1 AND b = 1) OR (a = 1 AND b = 2)` — which
1866
+ * every dialect accepts, unlike a row-value `IN` list.
1867
+ */
1868
+ const parentKeyCondition = (cols, keys) => cols.length === 1 ? (0, drizzle_orm.inArray)(cols[0], keys.map((key) => key[0])) : (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)(...cols.map((col, i) => (0, drizzle_orm.eq)(col, key[i])))));
1869
+ /**
1823
1870
  * The junction table and its two columns for a `.through()` (many-to-many) relation:
1824
1871
  * `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
1825
1872
  * with no junction; `null` for one whose junction cannot be used here — more than one column
@@ -1850,16 +1897,18 @@ const extractJunctionColumns = (relEntry) => {
1850
1897
  *
1851
1898
  * A reversed filter (inherited from the relation on the other side) is written against the
1852
1899
  * parent table, so it is applied as "the row's parent key belongs to a parent that passes it".
1853
- * `parentKeyCol` is the parent's join column and `groupCol` the column in the query that holds
1854
- * the parent key for each row — the target's foreign key, or the junction's source column.
1900
+ * `parentKeyCols` are the parent's join columns and `groupCols` the columns in the query that
1901
+ * hold the parent key for each row — the target's foreign key, or the junction's source
1902
+ * column — in the same order. A key of several columns is compared as a row value.
1855
1903
  */
1856
- const relationOwnCondition = (relEntry, parentTable, targetTable, parentKeyCol, groupCol) => {
1904
+ const relationOwnCondition = (relEntry, parentTable, targetTable, parentKeyCols, groupCols) => {
1857
1905
  const rel = relEntry.relation ?? relEntry;
1858
1906
  const ownWhere = relationOwnWhere(rel);
1859
1907
  if (!ownWhere) return;
1860
1908
  if (!isRelationFilterReversed(rel)) return (0, drizzle_orm.relationsFilterToSQL)(targetTable, ownWhere);
1861
1909
  const parentFilter = (0, drizzle_orm.relationsFilterToSQL)(parentTable, ownWhere);
1862
- return drizzle_orm.sql`${groupCol} in (select ${parentKeyCol} from ${parentTable} where ${parentFilter})`;
1910
+ if (groupCols.length === 1) return drizzle_orm.sql`${groupCols[0]} in (select ${parentKeyCols[0]} from ${parentTable} where ${parentFilter})`;
1911
+ return drizzle_orm.sql`(${drizzle_orm.sql.join([...groupCols], drizzle_orm.sql`, `)}) in (select ${drizzle_orm.sql.join([...parentKeyCols], drizzle_orm.sql`, `)} from ${parentTable} where ${parentFilter})`;
1863
1912
  };
1864
1913
  /**
1865
1914
  * Whether a to-one relation can back a relation orderBy hop. `.through()` (junction)
@@ -2007,6 +2056,13 @@ const extractUniqueKeyFilter = (table, tableName, fieldName, members, value) =>
2007
2056
  */
2008
2057
  const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
2009
2058
  /**
2059
+ * One alias counter per build, shared by every table's narrowed context. A statement can
2060
+ * combine subqueries compiled from different contexts — a relation filter on one table whose
2061
+ * target's scope is a filter object reaching through another relation, say — and nested
2062
+ * subqueries that drew the same alias from two separate counters would shadow each other.
2063
+ */
2064
+ const aliasCounters = /* @__PURE__ */ new WeakMap();
2065
+ /**
2010
2066
  * Narrows the build-scoped relation filter context to the table a resolver is filtering.
2011
2067
  *
2012
2068
  * Cached rather than rebuilt: the base carries the build's whole `tables` and `relationMap`,
@@ -2020,7 +2076,7 @@ const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
2020
2076
  * aliases only ever have to be unique *within* one statement, which a number that never
2021
2077
  * repeats gives more surely than one that restarts.
2022
2078
  */
2023
- const relationFilterCtx = (base, tableKey) => {
2079
+ const relationFilterCtx = (base, tableKey, scope) => {
2024
2080
  if (!base) return;
2025
2081
  let byTable = narrowedCtxCache.get(base);
2026
2082
  if (!byTable) {
@@ -2029,13 +2085,33 @@ const relationFilterCtx = (base, tableKey) => {
2029
2085
  }
2030
2086
  let ctx = byTable.get(tableKey);
2031
2087
  if (!ctx) {
2088
+ let aliases = aliasCounters.get(base);
2089
+ if (!aliases) {
2090
+ aliases = { n: 0 };
2091
+ aliasCounters.set(base, aliases);
2092
+ }
2032
2093
  ctx = {
2033
2094
  ...base,
2034
- tableKey
2095
+ tableKey,
2096
+ aliases
2035
2097
  };
2036
2098
  byTable.set(tableKey, ctx);
2037
2099
  }
2038
- return ctx;
2100
+ return scope ? {
2101
+ ...ctx,
2102
+ scope
2103
+ } : ctx;
2104
+ };
2105
+ /**
2106
+ * The row-policy predicate a subquery over a relation's target applies: the target's scope
2107
+ * and soft-delete condition, in the `deleted` mode the relation field defaults to — so a
2108
+ * relation filter or a relation ordering sees exactly the rows the relation field returns.
2109
+ */
2110
+ const relationTargetPolicy = (ctx, relation, targetTableName, aliasedTarget) => {
2111
+ const scope = ctx.scope;
2112
+ if (!scope) return;
2113
+ const mode = scope.relationDefault(targetTableName, (0, drizzle_orm.is)(relation, drizzle_orm.One) && relation.optional === false);
2114
+ return scope.has(targetTableName, mode) ? scope.on(targetTableName, aliasedTarget, mode) : void 0;
2039
2115
  };
2040
2116
  /**
2041
2117
  * Correlates the parent row with the aliased target table using the relation's own join
@@ -2126,8 +2202,11 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2126
2202
  const throughTable = relation.throughTable;
2127
2203
  let fromClause;
2128
2204
  let joinCondition;
2205
+ let junctionPolicy;
2129
2206
  if (throughTable) {
2130
2207
  const aliasedThrough = (0, drizzle_orm.aliasedTable)(throughTable, `dgql_rel_${aliases.n++}`);
2208
+ const throughTableName = ctx.scope ? Object.keys(ctx.tables).find((name) => ctx.tables[name] === throughTable) : void 0;
2209
+ if (throughTableName && ctx.scope.has(throughTableName)) junctionPolicy = ctx.scope.on(throughTableName, aliasedThrough);
2131
2210
  const { correlation, junctionJoin } = buildThroughJoinConditions(parentTable, relation, aliasedThrough, aliasedTarget, relationName);
2132
2211
  fromClause = drizzle_orm.sql`${(0, drizzle_orm.getTableAsAliasSQL)(aliasedTarget)} inner join ${(0, drizzle_orm.getTableAsAliasSQL)(aliasedThrough)} on ${junctionJoin}`;
2133
2212
  joinCondition = correlation;
@@ -2137,6 +2216,7 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2137
2216
  }
2138
2217
  const ownWhere = relationOwnWhere(relation);
2139
2218
  const relationWhere = ownWhere ? (0, drizzle_orm.relationsFilterToSQL)(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
2219
+ const policy = (0, drizzle_orm.and)(relationTargetPolicy(ctx, relation, targetTableName, aliasedTarget), junctionPolicy);
2140
2220
  const inner = innerFilters ? extractFilters(aliasedTarget, targetTableName, innerFilters, {
2141
2221
  ...ctx,
2142
2222
  tableKey: targetTableName,
@@ -2144,9 +2224,9 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2144
2224
  }) : void 0;
2145
2225
  if (mode === "every") {
2146
2226
  if (!inner) return;
2147
- return drizzle_orm.sql`not exists (select 1 from ${fromClause} where ${(0, drizzle_orm.and)(joinCondition, relationWhere, (0, drizzle_orm.not)(inner))})`;
2227
+ return drizzle_orm.sql`not exists (select 1 from ${fromClause} where ${(0, drizzle_orm.and)(joinCondition, relationWhere, policy, (0, drizzle_orm.not)(inner))})`;
2148
2228
  }
2149
- const condition = (0, drizzle_orm.and)(joinCondition, relationWhere, inner);
2229
+ const condition = (0, drizzle_orm.and)(joinCondition, relationWhere, policy, inner);
2150
2230
  return mode === "none" ? drizzle_orm.sql`not exists (select 1 from ${fromClause} where ${condition})` : drizzle_orm.sql`exists (select 1 from ${fromClause} where ${condition})`;
2151
2231
  };
2152
2232
  /**
@@ -2672,12 +2752,14 @@ const extendRelationOrderChain = (parentTable, relationName, relEntry, ctx, chai
2672
2752
  const joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
2673
2753
  const ownWhere = relationOwnWhere(relation);
2674
2754
  const relationWhere = ownWhere ? (0, drizzle_orm.relationsFilterToSQL)(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
2755
+ const policy = relationTargetPolicy(ctx, relation, relEntry.targetTableName, aliasedTarget);
2675
2756
  return {
2676
2757
  fromParts: [...chain?.fromParts ?? [], (0, drizzle_orm.getTableAsAliasSQL)(aliasedTarget)],
2677
2758
  conditions: [
2678
2759
  ...chain?.conditions ?? [],
2679
2760
  ...joinCondition ? [joinCondition] : [],
2680
- ...relationWhere ? [relationWhere] : []
2761
+ ...relationWhere ? [relationWhere] : [],
2762
+ ...policy ? [policy] : []
2681
2763
  ],
2682
2764
  table: aliasedTarget
2683
2765
  };
@@ -3812,8 +3894,7 @@ const relationJoinColumns = (tree, table, selectionCtx) => {
3812
3894
  const relEntry = fieldData.name.endsWith(AGGREGATE_FIELD_SUFFIX) ? relations[fieldData.name.slice(0, -9)] : relations[fieldData.name];
3813
3895
  const targetTable = relEntry ? selectionCtx.tables[relEntry.targetTableName] : void 0;
3814
3896
  if (!relEntry || !targetTable) continue;
3815
- const joinCols = extractRelationJoinColumns(relEntry, table, targetTable);
3816
- if (joinCols) needed.push(joinCols.localColPropName);
3897
+ for (const pair of extractRelationJoinColumnPairs(relEntry, table, targetTable) ?? []) needed.push(pair.localColPropName);
3817
3898
  }
3818
3899
  return needed;
3819
3900
  };
@@ -3907,10 +3988,10 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
3907
3988
  });
3908
3989
  const relWhere = relationArgs?.where;
3909
3990
  const relDeleted = relationArgs?.deleted ?? scope?.relationDefault(targetTableName, (0, drizzle_orm.is)(relEntry.relation, drizzle_orm.One) && relEntry.relation.optional === false);
3910
- thisRecord.where = relWhere || scope?.has(targetTableName, relDeleted) ? { RAW: (aliasedTable) => withScope(scope, targetTableName, aliasedTable, relWhere ? extractFilters(aliasedTable, relName, relWhere, relationFilterCtx(filterCtx, targetTableName)) : void 0, relDeleted) } : void 0;
3991
+ thisRecord.where = relWhere || scope?.has(targetTableName, relDeleted) ? { RAW: (aliasedTable) => withScope(scope, targetTableName, aliasedTable, relWhere ? extractFilters(aliasedTable, relName, relWhere, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, relDeleted) } : void 0;
3911
3992
  const hasPagination = offset != null || limit != null;
3912
3993
  const pkNames = targetPkNames ?? [];
3913
- thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
3994
+ thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName, scope), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
3914
3995
  thisRecord.offset = offset;
3915
3996
  thisRecord.limit = limit;
3916
3997
  thisRecord.with = extractRelationsParams(relationMap, tables, targetTableName, field, relTypeName, options);
@@ -3968,7 +4049,7 @@ const runRelationalSelect = async (opts) => {
3968
4049
  tableName,
3969
4050
  distinct,
3970
4051
  pkNames: pkNames ?? [],
3971
- where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
4052
+ where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, deleted),
3972
4053
  orderBy,
3973
4054
  limit: single ? 1 : opts.limit,
3974
4055
  offset
@@ -3983,8 +4064,8 @@ const runRelationalSelect = async (opts) => {
3983
4064
  allRelations: filterCtx?.relationMap
3984
4065
  }),
3985
4066
  offset: distinctKeys ? void 0 : offset,
3986
- orderBy: distinctKeys ? (aliasedTable) => [...orderBy ? extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : [], ...primaryKeyOrderExprs(aliasedTable, pkNames)] : cursorEntries ? (aliasedTable) => cursorOrderExprs(aliasedTable, cursorEntries) : orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : needsDefaultOrder && pkNames?.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0,
3987
- where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, (0, drizzle_orm.and)(where ? extractFilters(aliasedTable, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, cursorValues ? buildCursorCondition(aliasedTable, cursorEntries, cursorValues, opts.nullOrdering ?? "nulls-smallest") : void 0), deleted) } : void 0,
4067
+ orderBy: distinctKeys ? (aliasedTable) => [...orderBy ? extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName, scope), where) : [], ...primaryKeyOrderExprs(aliasedTable, pkNames)] : cursorEntries ? (aliasedTable) => cursorOrderExprs(aliasedTable, cursorEntries) : orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName, scope), where) : needsDefaultOrder && pkNames?.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0,
4068
+ where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, (0, drizzle_orm.and)(where ? extractFilters(aliasedTable, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, cursorValues ? buildCursorCondition(aliasedTable, cursorEntries, cursorValues, opts.nullOrdering ?? "nulls-smallest") : void 0), deleted) } : void 0,
3988
4069
  with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
3989
4070
  typeNameMapper,
3990
4071
  filterCtx,
@@ -4566,7 +4647,7 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
4566
4647
  tableName,
4567
4648
  table,
4568
4649
  where,
4569
- relationCtx: relationFilterCtx(filterCtx, tableName),
4650
+ relationCtx: relationFilterCtx(filterCtx, tableName, scope),
4570
4651
  required: requireWhere,
4571
4652
  deleted: hard ? "INCLUDE" : void 0
4572
4653
  });
@@ -4834,13 +4915,12 @@ const identifyRows = (field, args) => {
4834
4915
  * Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
4835
4916
  * Returns raw rows (NOT remapped); the caller groups + remaps them.
4836
4917
  */
4837
- const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
4918
+ const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, partitionCols, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
4838
4919
  const cols = columns ?? (0, drizzle_orm.getColumns)(targetTable);
4839
4920
  const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
4840
4921
  const orderClause = orderExprs.length ? drizzle_orm.sql` order by ${drizzle_orm.sql.join(orderExprs, drizzle_orm.sql`, `)}` : drizzle_orm.sql``;
4841
4922
  const RN = "__drizzle_graphql_rn";
4842
- const partitionCol = junction ? junction.sourceCol : foreignCol;
4843
- const rowNumber = drizzle_orm.sql`row_number() over (partition by ${partitionCol}${orderClause})`.as(RN);
4923
+ const rowNumber = drizzle_orm.sql`row_number() over (partition by ${drizzle_orm.sql.join([...partitionCols], drizzle_orm.sql`, `)}${orderClause})`.as(RN);
4844
4924
  const sub = withJunction(db.select({
4845
4925
  ...cols,
4846
4926
  [RN]: rowNumber
@@ -4879,13 +4959,14 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4879
4959
  const targetTableName = relEntry.targetTableName;
4880
4960
  const targetTable = tables[targetTableName];
4881
4961
  if (!parentTable || !targetTable) return;
4882
- const joinCols = extractRelationJoinColumns(relEntry, parentTable, targetTable);
4883
- if (!joinCols) return;
4884
- const { localColPropName, foreignCol, foreignColPropName } = joinCols;
4962
+ const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
4963
+ if (!joinPairs) return;
4964
+ const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
4965
+ const foreignCol = joinPairs[0].foreignCol;
4885
4966
  const junction = extractJunctionColumns(relEntry);
4886
- const groupCol = junction ? junction.sourceCol : foreignCol;
4887
- const groupKey = junction ? PARENT_KEY : foreignColPropName;
4888
- const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, (0, drizzle_orm.getColumns)(parentTable)[localColPropName], groupCol);
4967
+ const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
4968
+ const groupKeys = junction ? [PARENT_KEY] : joinPairs.map((pair) => pair.foreignColPropName);
4969
+ const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
4889
4970
  const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, isOne && relEntry.relation?.optional === false);
4890
4971
  const limitPolicy = isOne ? void 0 : limits?.(targetTableName);
4891
4972
  const targetPkNames = relEntry.targetPkNames ?? [];
@@ -4914,7 +4995,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4914
4995
  allRelations: filterCtx?.relationMap
4915
4996
  });
4916
4997
  const targetColumns = (0, drizzle_orm.getColumns)(targetTable);
4917
- columns[foreignColPropName] = foreignCol;
4998
+ for (const pair of joinPairs) columns[pair.foreignColPropName] = pair.foreignCol;
4918
4999
  for (const [columnName] of cursorEntries ?? []) {
4919
5000
  const column = targetColumns[columnName];
4920
5001
  if (column) columns[columnName] = column;
@@ -4926,8 +5007,8 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4926
5007
  };
4927
5008
  const resolve = async (parent, args, context, info) => {
4928
5009
  if (parent[relationName] !== void 0) return parent[relationName];
4929
- const localValue = parent[localColPropName];
4930
- if (localValue == null) return isOne ? null : [];
5010
+ const localValues = localColPropNames.map((name) => parent[name]);
5011
+ if (localValues.some((value) => value == null)) return isOne ? null : [];
4931
5012
  const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
4932
5013
  const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
4933
5014
  const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
@@ -4968,11 +5049,11 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4968
5049
  };
4969
5050
  });
4970
5051
  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" });
4971
- return getOrCreateLoader(context, loaderKey, async (parentIds) => {
5052
+ return getOrCreateLoader(context, loaderKey, async (parentKeys) => {
4972
5053
  const executor = resolveExecutor(db, context);
4973
- const uniqueIds = [...new Set(parentIds)];
5054
+ const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
4974
5055
  const scope = resolveScope(policies, context, filterCtx);
4975
- let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
5056
+ let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)(parentKeyCondition(groupCols, uniqueKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
4976
5057
  if (distinct) {
4977
5058
  const keys = await selectDistinctKeys({
4978
5059
  db: executor,
@@ -4982,44 +5063,44 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4982
5063
  pkNames: targetPkNames,
4983
5064
  where: whereCondition,
4984
5065
  orderBy: orderByArg,
4985
- partitionBy: [groupCol],
5066
+ partitionBy: groupCols,
4986
5067
  junction: junction ? {
4987
5068
  table: junction.table,
4988
5069
  on: (0, drizzle_orm.eq)(junction.targetCol, foreignCol),
4989
- parentKey: groupCol
5070
+ parentKey: junction.sourceCol
4990
5071
  } : void 0
4991
5072
  });
4992
- if (!keys.length) return parentIds.map(() => isOne ? null : []);
4993
- whereCondition = (0, drizzle_orm.and)(whereCondition, junction ? (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)((0, drizzle_orm.eq)(groupCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
5073
+ if (!keys.length) return parentKeys.map(() => isOne ? null : []);
5074
+ whereCondition = (0, drizzle_orm.and)(whereCondition, junction ? (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)((0, drizzle_orm.eq)(junction.sourceCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
4994
5075
  }
4995
5076
  const selection = junction ? {
4996
5077
  ...columns ?? (0, drizzle_orm.getColumns)(targetTable),
4997
- [PARENT_KEY]: groupCol
5078
+ [PARENT_KEY]: junction.sourceCol
4998
5079
  } : columns;
4999
5080
  let rows;
5000
- if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName), whereArg, junction);
5081
+ if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, groupCols, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName, scope), whereArg, junction);
5001
5082
  else {
5002
5083
  let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
5003
- const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
5084
+ const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
5004
5085
  if (orderExprs.length) q = q.orderBy(...orderExprs);
5005
5086
  rows = await q;
5006
5087
  }
5007
5088
  if (cursorEntries) attachRowCursors(rows, cursorEntries);
5008
5089
  const keyOf = (row) => {
5009
- const key = String(row[groupKey]);
5090
+ const key = joinKeyOf(groupKeys.map((name) => row[name]));
5010
5091
  if (junction) delete row[PARENT_KEY];
5011
5092
  return key;
5012
5093
  };
5013
5094
  if (isOne) {
5014
5095
  const byKey = new Map(rows.map((row) => [keyOf(row), row]));
5015
5096
  remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
5016
- return parentIds.map((id) => byKey.get(String(id)) ?? null);
5097
+ return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? null);
5017
5098
  }
5018
- const grouped = new Map(uniqueIds.map((id) => [String(id), []]));
5099
+ const grouped = new Map(uniqueKeys.map((key) => [joinKeyOf(key), []]));
5019
5100
  for (const row of rows) grouped.get(keyOf(row))?.push(row);
5020
5101
  remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
5021
- return parentIds.map((id) => grouped.get(String(id)) ?? []);
5022
- }).load(localValue);
5102
+ return parentKeys.map((key) => grouped.get(joinKeyOf(key)) ?? []);
5103
+ }).load(localValues);
5023
5104
  };
5024
5105
  return async (parent, args, context, info) => {
5025
5106
  try {
@@ -5680,6 +5761,7 @@ const resolveBuildConfig = (config, schema) => {
5680
5761
  const normalized = {};
5681
5762
  for (const [columnName, entry] of Object.entries(orderBy)) {
5682
5763
  if (!columns[columnName]) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which is not a column of '${tableName}'.`);
5764
+ if (config?.exclude?.columns?.[tableName]?.includes(columnName)) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which config.exclude.columns hides. A default ordering is encoded into every row's cursor, so it can only use columns the schema exposes.`);
5683
5765
  const direction = typeof entry === "string" ? entry : entry?.direction;
5684
5766
  if (direction !== "asc" && direction !== "desc") throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy.${columnName} must be 'asc', 'desc', or { direction, priority? }.`);
5685
5767
  const priority = typeof entry === "string" ? void 0 : entry?.priority;
@@ -6060,7 +6142,8 @@ const generateAggregate = (db, tableName, table, typeName, fieldName, filterArgs
6060
6142
  const request = parseAggregateRequest(info, target);
6061
6143
  if (!Object.keys(request.selection).length) return {};
6062
6144
  let query = resolveExecutor(db, context).select(request.selection).from(table);
6063
- const where = withScope(resolveScope(policies, context, filterCtx), tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName)) : void 0, args.deleted);
6145
+ const scope = resolveScope(policies, context, filterCtx);
6146
+ const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
6064
6147
  if (where) query = query.where(where);
6065
6148
  const rows = await query;
6066
6149
  return assembleAggregateRow(rows[0] ?? {}, request, target);
@@ -6093,8 +6176,8 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6093
6176
  const targetTableName = relEntry.targetTableName;
6094
6177
  const targetTable = tables[targetTableName];
6095
6178
  if (!parentTable || !targetTable) return;
6096
- const joinCols = extractRelationJoinColumns(relEntry, parentTable, targetTable);
6097
- if (!joinCols) {
6179
+ const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
6180
+ if (!joinPairs) {
6098
6181
  const key = `${tableName}.${relationName}`;
6099
6182
  if (!warned.has(key)) {
6100
6183
  warned.add(key);
@@ -6102,11 +6185,13 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6102
6185
  }
6103
6186
  return;
6104
6187
  }
6105
- const { localColPropName, foreignCol } = joinCols;
6188
+ const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
6189
+ const foreignCol = joinPairs[0].foreignCol;
6106
6190
  const junction = extractJunctionColumns(relEntry);
6107
6191
  if (junction === null) return;
6108
- const groupCol = junction ? junction.sourceCol : foreignCol;
6109
- const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, (0, drizzle_orm.getColumns)(parentTable)[localColPropName], groupCol);
6192
+ const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
6193
+ const groupAliases = groupCols.map((_, i) => `${GROUP_KEY}${i}`);
6194
+ const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
6110
6195
  const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, false);
6111
6196
  const targetTypeName = resolveTypeName(targetTableName, typeNameMapper);
6112
6197
  const type = generateAggregateTypes(targetTable, targetTableName, targetTypeName, cacheCtx);
@@ -6130,23 +6215,25 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6130
6215
  };
6131
6216
  });
6132
6217
  if (!Object.keys(request.selection).length) return {};
6133
- const localValue = parent[localColPropName];
6134
- if (localValue == null) return assembleAggregateRow({}, request, target);
6218
+ const localValues = localColPropNames.map((name) => parent[name]);
6219
+ if (localValues.some((value) => value == null)) return assembleAggregateRow({}, request, target);
6135
6220
  const whereArg = args?.where;
6136
6221
  const deleted = args?.deleted;
6137
- const loader = getOrCreateLoader(context, loaderKey, async (parentIds) => {
6222
+ const loader = getOrCreateLoader(context, loaderKey, async (parentKeys) => {
6138
6223
  const executor = resolveExecutor(db, context);
6139
- const uniqueIds = [...new Set(parentIds)];
6140
- const whereCondition = withScope(resolveScope(policies, context, filterCtx), targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName)) : void 0), deleted ?? defaultDeleted);
6224
+ const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
6225
+ const scope = resolveScope(policies, context, filterCtx);
6226
+ const whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)(parentKeyCondition(groupCols, uniqueKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
6227
+ const groupSelection = Object.fromEntries(groupAliases.map((alias, i) => [alias, groupCols[i]]));
6141
6228
  const selected = executor.select({
6142
- [GROUP_KEY]: groupCol,
6229
+ ...groupSelection,
6143
6230
  ...request.selection
6144
6231
  }).from(targetTable);
6145
- const rows = await (junction ? selected.innerJoin(junction.table, (0, drizzle_orm.eq)(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(groupCol);
6146
- const byKey = new Map(rows.map((row) => [row[GROUP_KEY], row]));
6147
- return parentIds.map((id) => byKey.get(id) ?? {});
6232
+ const rows = await (junction ? selected.innerJoin(junction.table, (0, drizzle_orm.eq)(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(...groupCols);
6233
+ const byKey = new Map(rows.map((row) => [joinKeyOf(groupAliases.map((alias) => row[alias])), row]));
6234
+ return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? {});
6148
6235
  });
6149
- return assembleAggregateRow(await loader.load(localValue), request, target);
6236
+ return assembleAggregateRow(await loader.load(localValues), request, target);
6150
6237
  } catch (e) {
6151
6238
  throw withErrorContext(toGraphQLError(e), errorCtx);
6152
6239
  }
@@ -6353,7 +6440,8 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
6353
6440
  ...request.selection
6354
6441
  };
6355
6442
  let query = resolveExecutor(db, context).select(selection).from(table);
6356
- const where = withScope(resolveScope(policies, context, filterCtx), tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName)) : void 0, args.deleted);
6443
+ const scope = resolveScope(policies, context, filterCtx);
6444
+ const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
6357
6445
  if (where) query = query.where(where);
6358
6446
  query = query.groupBy(...keyColumns.map(([, column]) => column));
6359
6447
  const havingCondition = buildHavingCondition(args.having, columns);
@@ -6392,6 +6480,54 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
6392
6480
  };
6393
6481
  };
6394
6482
  //#endregion
6483
+ //#region src/util/builders/common/build-registry.ts
6484
+ /**
6485
+ * Keyed by the `extensions.drizzle` object of every object type the build generated, rather
6486
+ * than by the type: a schema composed from `entities.types` keeps the types, and one rebuilt
6487
+ * by a schema-transforming tool (`mapSchema`, stitching) keeps their extensions.
6488
+ */
6489
+ const runtimeByTypeMeta = /* @__PURE__ */ new WeakMap();
6490
+ /**
6491
+ * What is known about the builds of one drizzle instance, for a caller with no resolve info to
6492
+ * look its build up by. Only a lone build is kept — with two or more there is no telling which
6493
+ * one a selection belongs to — so rebuilding a schema repeatedly holds on to nothing extra.
6494
+ */
6495
+ const buildsByDb = /* @__PURE__ */ new WeakMap();
6496
+ /** Whether a build changes what a read returns at all: a row policy, a limit or a default ordering. */
6497
+ const hasPolicies = (runtime) => !!(runtime.policies?.scope || runtime.policies?.defaultOrderBy || runtime.limits);
6498
+ /** Records a finished build against the object types it generated and the instance it read. */
6499
+ const registerBuildRuntime = (runtime, types) => {
6500
+ for (const type of types) {
6501
+ const meta = drizzleExtension(type);
6502
+ if (meta) runtimeByTypeMeta.set(meta, runtime);
6503
+ }
6504
+ const previous = buildsByDb.get(runtime.db);
6505
+ buildsByDb.set(runtime.db, previous ? {
6506
+ count: previous.count + 1,
6507
+ anyPolicies: previous.anyPolicies || hasPolicies(runtime)
6508
+ } : {
6509
+ count: 1,
6510
+ only: runtime,
6511
+ anyPolicies: hasPolicies(runtime)
6512
+ });
6513
+ };
6514
+ /** The build that generated `type`, when one did. */
6515
+ const runtimeForType = (type) => {
6516
+ const meta = type && typeof type === "object" ? drizzleExtension(type) : void 0;
6517
+ return meta ? runtimeByTypeMeta.get(meta) : void 0;
6518
+ };
6519
+ /**
6520
+ * The build of `db` to use when no generated type identifies one: the only build, or none
6521
+ * when no build applies any policy. Throws when several builds exist and one of them would
6522
+ * change the read, since picking either could apply the wrong policies — or none.
6523
+ */
6524
+ const runtimeForDb = (db, helper) => {
6525
+ const builds = buildsByDb.get(db);
6526
+ if (!builds) return;
6527
+ if (builds.count === 1) return builds.only;
6528
+ if (builds.anyPolicies) throw new Error(`Drizzle-GraphQL Error: ${helper} cannot tell which of the ${builds.count} schemas built over this database the selection belongs to, and at least one applies row policies (scope, softDelete, limits or defaults). Pass the resolver's \`info\` rather than a parsed selection tree, from a field whose type the build generated.`);
6529
+ };
6530
+ //#endregion
6395
6531
  //#region src/util/builders/nested-writes.ts
6396
6532
  const ALL_OPS = [
6397
6533
  "create",
@@ -6884,7 +7020,7 @@ const createNestedWriteRuntime = (params) => {
6884
7020
  * nothing is rejected: `connect: {}` would otherwise attach every row in the table.
6885
7021
  */
6886
7022
  const conditionOf = (plan, filter, operation, scope) => {
6887
- const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName));
7023
+ const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
6888
7024
  if (!condition) throw drizzleError(`Drizzle-GraphQL Error: '${operation}' on '${plan.relationName}' needs a filter that selects rows — it was given one that matches everything.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
6889
7025
  return withScope(scope, plan.targetTableName, plan.targetTable, condition);
6890
7026
  };
@@ -7495,6 +7631,14 @@ const createSchemaBuilder = (adapter) => {
7495
7631
  }
7496
7632
  if (Object.keys(relResolvers).length > 0) fieldResolvers[tableName] = relResolvers;
7497
7633
  }
7634
+ registerBuildRuntime({
7635
+ db: ctx.db,
7636
+ filterCtx: ctx.filterCtx,
7637
+ policies: ctx.policies,
7638
+ scoped: !!ctx.tablePolicies?.scope,
7639
+ limits: ctx.limits,
7640
+ nullOrdering: adapter.nullOrdering
7641
+ }, Object.values(ctx.outputs));
7498
7642
  return {
7499
7643
  queries: ctx.queries,
7500
7644
  mutations: ctx.mutations,
@@ -7617,7 +7761,7 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
7617
7761
  const input = stripContextValues(remapUpdateInput(set, table, tableName), policies?.contextValues?.(tableName));
7618
7762
  if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
7619
7763
  await before();
7620
- const relationCtx = relationFilterCtx(filterCtx, tableName);
7764
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
7621
7765
  const filters = scopedWhere({
7622
7766
  scope,
7623
7767
  tableName,
@@ -7663,7 +7807,7 @@ const generateUpdateMany$2 = (db, tableName, table, updateManyInput, fieldName,
7663
7807
  if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
7664
7808
  return {
7665
7809
  set: input,
7666
- filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
7810
+ filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
7667
7811
  };
7668
7812
  });
7669
7813
  await executor.transaction(async (tx) => {
@@ -7706,7 +7850,7 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
7706
7850
  const { where } = args;
7707
7851
  const hard = canHardDelete && args.hard === true;
7708
7852
  const scope = policies?.scope?.(context);
7709
- const relationCtx = relationFilterCtx(filterCtx, tableName);
7853
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
7710
7854
  const filters = scopedWhere({
7711
7855
  scope,
7712
7856
  tableName,
@@ -8048,7 +8192,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8048
8192
  excludedRef: excludedColumnRef,
8049
8193
  withTarget: true,
8050
8194
  serverOwned: Object.keys(contextColumns ?? {}),
8051
- buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
8195
+ buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
8052
8196
  });
8053
8197
  const runBatch = async (executor, { values: rows, plan }) => {
8054
8198
  let query = executor.insert(table).values(rows).returning(returning);
@@ -8130,7 +8274,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8130
8274
  const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
8131
8275
  const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
8132
8276
  if (!Object.keys(input).length && !nestedOps) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
8133
- const relationCtx = relationFilterCtx(filterCtx, tableName);
8277
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
8134
8278
  const filters = scopedWhere({
8135
8279
  scope,
8136
8280
  tableName,
@@ -8209,7 +8353,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8209
8353
  await before();
8210
8354
  const parsedInfo = parseResolveInfo(info, { deep: true });
8211
8355
  const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
8212
- const relationCtx = relationFilterCtx(filterCtx, tableName);
8356
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
8213
8357
  const filters = scopedWhere({
8214
8358
  scope,
8215
8359
  tableName,
@@ -8660,7 +8804,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
8660
8804
  return {
8661
8805
  set: input,
8662
8806
  ops,
8663
- filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
8807
+ filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
8664
8808
  };
8665
8809
  });
8666
8810
  const anyNested = entries.some((entry) => entry.ops);
@@ -8830,6 +8974,36 @@ const namingRuleOf = (options) => resolveGeneratedTypeNames({
8830
8974
  typeNameSuffix: options.typeNameSuffix
8831
8975
  });
8832
8976
  /**
8977
+ * The build whose policies the read applies. Looked up through the generated types the
8978
+ * resolve info reaches — the field's return type, the root table's type in the executing
8979
+ * schema, the parent type — so a resolver over a generated type finds its own build. Without
8980
+ * resolve info (a parsed tree was passed), the instance's only build is used.
8981
+ */
8982
+ const runtimeFor = (info, db, typeName, helper) => {
8983
+ if (!("fieldsByTypeName" in info)) {
8984
+ const candidates = [
8985
+ info.returnType ? (0, graphql.getNamedType)(info.returnType) : void 0,
8986
+ info.schema?.getType(typeName),
8987
+ info.parentType
8988
+ ];
8989
+ for (const candidate of candidates) {
8990
+ const runtime = runtimeForType(candidate);
8991
+ if (runtime && runtime.db === db) return runtime;
8992
+ }
8993
+ }
8994
+ return runtimeForDb(db, helper);
8995
+ };
8996
+ /**
8997
+ * The request's row policies under `runtime`, bound to the context the caller passed. A build
8998
+ * with `scope` hooks cannot be read without that context: the hooks would be called with
8999
+ * nothing, and running the read unscoped is the leak the option exists to prevent.
9000
+ */
9001
+ const scopeFor = (runtime, options, helper) => {
9002
+ if (!runtime?.policies?.scope) return;
9003
+ if (runtime.scoped && !("context" in options)) throw new Error(`Drizzle-GraphQL Error: the schema this selection belongs to configures \`scope\`, so ${helper} needs the request context to apply it. Pass the resolver's context as the \`context\` option.`);
9004
+ return runtime.policies.scope(options.context);
9005
+ };
9006
+ /**
8833
9007
  * Reads the parsed selection tree out of a resolver's `info`, resolving fragments and
8834
9008
  * inline fragments. Accepts an already-parsed tree so a caller that has one (or is chaining
8835
9009
  * these helpers) does not pay for the walk twice.
@@ -8856,6 +9030,10 @@ const parseInfo = (info) => {
8856
9030
  * const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
8857
9031
  * ```
8858
9032
  *
9033
+ * Every nested entry carries the build's row policies — `scope`, `softDelete`, `limits` and
9034
+ * `defaults.orderBy` — exactly as the generated `with:` clause does. The build is found
9035
+ * through the resolve info; pass `context` when it configures `scope`.
9036
+ *
8859
9037
  * Returns `undefined` when the selection asks for no relations, which is the value the
8860
9038
  * query builder wants for "no `with` clause".
8861
9039
  */
@@ -8865,11 +9043,16 @@ const selectionToWith = (info, options) => {
8865
9043
  const { tables, relationMap, filterCtx } = contextFor(db);
8866
9044
  if (!tables[table]) throw new Error(`Drizzle-GraphQL Error: '${table}' is not a table in the Drizzle schema.`);
8867
9045
  if (!relationMap[table]) return;
9046
+ const typeName = resolveObjectTypeName(table, typeNameMapper, resolveName);
9047
+ const runtime = runtimeFor(info, db, typeName, "selectionToWith");
8868
9048
  const parsed = parseInfo(info);
8869
- const params = extractRelationsParams(relationMap, tables, table, parsed, resolveObjectTypeName(table, typeNameMapper, resolveName), {
9049
+ const params = extractRelationsParams(relationMap, tables, table, parsed, typeName, {
8870
9050
  typeNameMapper,
8871
- filterCtx,
8872
- resolveName
9051
+ filterCtx: runtime?.filterCtx ?? filterCtx,
9052
+ resolveName,
9053
+ scope: scopeFor(runtime, options, "selectionToWith"),
9054
+ limits: runtime?.limits,
9055
+ defaultOrderBy: runtime?.policies?.defaultOrderBy
8873
9056
  });
8874
9057
  return params && Object.keys(params).length ? params : void 0;
8875
9058
  };
@@ -8881,14 +9064,16 @@ const selectionToWith = (info, options) => {
8881
9064
  * This is what an override usually wants after it has done its own work:
8882
9065
  *
8883
9066
  * ```ts
8884
- * const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
9067
+ * const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
8885
9068
  * ```
8886
9069
  *
8887
9070
  * Relation arguments and aggregate fields keep behaving as they do in the generated schema,
8888
- * because it is the generated schema's own code path.
9071
+ * because it is the generated schema's own code path. So do the build's row policies: the
9072
+ * root table and every nested relation are scoped and soft-delete-filtered, and the limit
9073
+ * policy and default ordering apply. Pass `context` when the build configures `scope`.
8889
9074
  */
8890
9075
  const resolveSelection = async (info, options) => {
8891
- const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after } = options;
9076
+ const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after, deleted } = options;
8892
9077
  const resolveName = namingRuleOf(options);
8893
9078
  const { tables, relationMap, filterCtx, primaryKeyOf } = contextFor(db);
8894
9079
  const table = tables[tableName];
@@ -8898,25 +9083,34 @@ const resolveSelection = async (info, options) => {
8898
9083
  const source = executor ?? db;
8899
9084
  const queryBase = source.query?.[tableName];
8900
9085
  if (!queryBase) throw new Error(`Drizzle-GraphQL Error: the executor passed to resolveSelection has no relational query builder for '${tableName}'.`);
9086
+ const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
9087
+ const runtime = runtimeFor(info, db, typeName, "resolveSelection");
9088
+ const scope = scopeFor(runtime, options, "resolveSelection");
9089
+ const defaultOrderBy = runtime?.policies?.defaultOrderBy;
8901
9090
  return runRelationalSelect({
8902
9091
  queryBase,
8903
9092
  tables,
8904
9093
  tableName,
8905
9094
  table,
8906
9095
  relationMap,
8907
- typeName: resolveObjectTypeName(tableName, typeNameMapper, resolveName),
9096
+ typeName,
8908
9097
  typeNameMapper,
8909
9098
  resolveName,
8910
9099
  parsedInfo,
8911
9100
  single,
8912
9101
  where,
8913
- orderBy,
8914
- limit,
9102
+ orderBy: withDefaultOrderBy({ orderBy }, tableName, defaultOrderBy).orderBy,
9103
+ limit: single ? limit : applyLimitPolicy(limit, runtime?.limits?.(tableName)),
8915
9104
  offset,
8916
9105
  after,
8917
- filterCtx,
9106
+ filterCtx: runtime?.filterCtx ?? filterCtx,
8918
9107
  pkNames: primaryKeyOf(tableName),
8919
- db: source
9108
+ db: source,
9109
+ scope,
9110
+ deleted,
9111
+ limits: runtime?.limits,
9112
+ defaultOrderBy,
9113
+ nullOrdering: runtime?.nullOrdering
8920
9114
  });
8921
9115
  };
8922
9116
  //#endregion