@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.js CHANGED
@@ -2,7 +2,7 @@ import { Column, One, SQL, Table, aliasedTable, and, arrayContains, arrayOverlap
2
2
  import { MySqlAsyncDatabase, MySqlDate, MySqlDateString, MySqlDateTime, MySqlDateTimeString, MySqlInt, MySqlSerial, MySqlTable, MySqlTimestamp, MySqlTimestampString, getTableConfig } from "drizzle-orm/mysql-core";
3
3
  import { PgAsyncDatabase, PgDate, PgDateString, PgInteger, PgSerial, PgTable, PgTimestamp, PgTimestampString, PgUUID, getTableConfig as getTableConfig$1 } from "drizzle-orm/pg-core";
4
4
  import { SQLiteAsyncDatabase, SQLiteInteger, SQLiteTable, SQLiteTimestamp, getTableConfig as getTableConfig$2 } from "drizzle-orm/sqlite-core";
5
- import { GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, Kind, getArgumentValues } from "graphql";
5
+ import { GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, Kind, getArgumentValues, getNamedType } from "graphql";
6
6
  import { GraphQLDateTime, GraphQLJSON, GraphQLUUID } from "graphql-scalars";
7
7
  import pluralize from "pluralize";
8
8
  //#region src/util/builders/common/errors.ts
@@ -1778,6 +1778,11 @@ const warnUnresolvedJoin = (relEntry, tableName, relationName, omitted) => {
1778
1778
  * Returns the JS property name of the local column on the parent table and the
1779
1779
  * Column object for the foreign column on the target table, or undefined if the
1780
1780
  * relation internals are not accessible.
1781
+ *
1782
+ * Only the relation's *first* column pair is reported. A relation joined on several columns
1783
+ * (`from: [a, b], to: [x, y]`) is not described fully by it, so anything that reads rows by
1784
+ * this pair alone mixes up parents that share the first column. Read paths use
1785
+ * {@link extractRelationJoinColumnPairs}, which returns every pair.
1781
1786
  */
1782
1787
  const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
1783
1788
  const rel = relEntry.relation ?? relEntry;
@@ -1796,6 +1801,48 @@ const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
1796
1801
  };
1797
1802
  };
1798
1803
  /**
1804
+ * Every column pair the relation joins on, in declaration order — one for an ordinary relation,
1805
+ * several for one declared `from: [a, b], to: [x, y]`. A row of the target belongs to a parent
1806
+ * only when *all* pairs match, so a read path has to correlate on every one of them.
1807
+ *
1808
+ * `undefined` when the relation's columns cannot be matched to the two tables, or when its two
1809
+ * sides list a different number of columns.
1810
+ */
1811
+ const extractRelationJoinColumnPairs = (relEntry, parentTable, targetTable) => {
1812
+ const rel = relEntry.relation ?? relEntry;
1813
+ const sourceCols = relationColumns(rel, "source");
1814
+ const targetCols = relationColumns(rel, "target");
1815
+ if (!sourceCols?.length || sourceCols.length !== targetCols?.length) return;
1816
+ const parentEntries = Object.entries(getColumns(parentTable));
1817
+ const targetEntries = Object.entries(getColumns(targetTable));
1818
+ const pairs = [];
1819
+ for (let i = 0; i < sourceCols.length; i++) {
1820
+ const localCol = sourceCols[i];
1821
+ const foreignCol = targetCols[i];
1822
+ const localColPropName = parentEntries.find(([, c]) => c === localCol)?.[0];
1823
+ const foreignColPropName = targetEntries.find(([, c]) => c === foreignCol)?.[0];
1824
+ if (!localColPropName || !foreignColPropName) return;
1825
+ pairs.push({
1826
+ localColPropName,
1827
+ localCol,
1828
+ foreignColPropName,
1829
+ foreignCol
1830
+ });
1831
+ }
1832
+ return pairs;
1833
+ };
1834
+ /**
1835
+ * A string identifying one combination of join-column values, for grouping a batch's rows by
1836
+ * parent. Values are compared by their string form, as a single-column key always has been.
1837
+ */
1838
+ const joinKeyOf = (values) => values.length === 1 ? String(values[0]) : JSON.stringify(values.map((value) => String(value)));
1839
+ /**
1840
+ * The rows whose `cols` hold one of `keys`: `col IN (…)` for a single join column, and an `OR`
1841
+ * of per-parent equalities for several — `(a = 1 AND b = 1) OR (a = 1 AND b = 2)` — which
1842
+ * every dialect accepts, unlike a row-value `IN` list.
1843
+ */
1844
+ const parentKeyCondition = (cols, keys) => cols.length === 1 ? inArray(cols[0], keys.map((key) => key[0])) : or(...keys.map((key) => and(...cols.map((col, i) => eq(col, key[i])))));
1845
+ /**
1799
1846
  * The junction table and its two columns for a `.through()` (many-to-many) relation:
1800
1847
  * `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
1801
1848
  * with no junction; `null` for one whose junction cannot be used here — more than one column
@@ -1826,16 +1873,18 @@ const extractJunctionColumns = (relEntry) => {
1826
1873
  *
1827
1874
  * A reversed filter (inherited from the relation on the other side) is written against the
1828
1875
  * parent table, so it is applied as "the row's parent key belongs to a parent that passes it".
1829
- * `parentKeyCol` is the parent's join column and `groupCol` the column in the query that holds
1830
- * the parent key for each row — the target's foreign key, or the junction's source column.
1876
+ * `parentKeyCols` are the parent's join columns and `groupCols` the columns in the query that
1877
+ * hold the parent key for each row — the target's foreign key, or the junction's source
1878
+ * column — in the same order. A key of several columns is compared as a row value.
1831
1879
  */
1832
- const relationOwnCondition = (relEntry, parentTable, targetTable, parentKeyCol, groupCol) => {
1880
+ const relationOwnCondition = (relEntry, parentTable, targetTable, parentKeyCols, groupCols) => {
1833
1881
  const rel = relEntry.relation ?? relEntry;
1834
1882
  const ownWhere = relationOwnWhere(rel);
1835
1883
  if (!ownWhere) return;
1836
1884
  if (!isRelationFilterReversed(rel)) return relationsFilterToSQL(targetTable, ownWhere);
1837
1885
  const parentFilter = relationsFilterToSQL(parentTable, ownWhere);
1838
- return sql`${groupCol} in (select ${parentKeyCol} from ${parentTable} where ${parentFilter})`;
1886
+ if (groupCols.length === 1) return sql`${groupCols[0]} in (select ${parentKeyCols[0]} from ${parentTable} where ${parentFilter})`;
1887
+ return sql`(${sql.join([...groupCols], sql`, `)}) in (select ${sql.join([...parentKeyCols], sql`, `)} from ${parentTable} where ${parentFilter})`;
1839
1888
  };
1840
1889
  /**
1841
1890
  * Whether a to-one relation can back a relation orderBy hop. `.through()` (junction)
@@ -1983,6 +2032,13 @@ const extractUniqueKeyFilter = (table, tableName, fieldName, members, value) =>
1983
2032
  */
1984
2033
  const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
1985
2034
  /**
2035
+ * One alias counter per build, shared by every table's narrowed context. A statement can
2036
+ * combine subqueries compiled from different contexts — a relation filter on one table whose
2037
+ * target's scope is a filter object reaching through another relation, say — and nested
2038
+ * subqueries that drew the same alias from two separate counters would shadow each other.
2039
+ */
2040
+ const aliasCounters = /* @__PURE__ */ new WeakMap();
2041
+ /**
1986
2042
  * Narrows the build-scoped relation filter context to the table a resolver is filtering.
1987
2043
  *
1988
2044
  * Cached rather than rebuilt: the base carries the build's whole `tables` and `relationMap`,
@@ -1996,7 +2052,7 @@ const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
1996
2052
  * aliases only ever have to be unique *within* one statement, which a number that never
1997
2053
  * repeats gives more surely than one that restarts.
1998
2054
  */
1999
- const relationFilterCtx = (base, tableKey) => {
2055
+ const relationFilterCtx = (base, tableKey, scope) => {
2000
2056
  if (!base) return;
2001
2057
  let byTable = narrowedCtxCache.get(base);
2002
2058
  if (!byTable) {
@@ -2005,13 +2061,33 @@ const relationFilterCtx = (base, tableKey) => {
2005
2061
  }
2006
2062
  let ctx = byTable.get(tableKey);
2007
2063
  if (!ctx) {
2064
+ let aliases = aliasCounters.get(base);
2065
+ if (!aliases) {
2066
+ aliases = { n: 0 };
2067
+ aliasCounters.set(base, aliases);
2068
+ }
2008
2069
  ctx = {
2009
2070
  ...base,
2010
- tableKey
2071
+ tableKey,
2072
+ aliases
2011
2073
  };
2012
2074
  byTable.set(tableKey, ctx);
2013
2075
  }
2014
- return ctx;
2076
+ return scope ? {
2077
+ ...ctx,
2078
+ scope
2079
+ } : ctx;
2080
+ };
2081
+ /**
2082
+ * The row-policy predicate a subquery over a relation's target applies: the target's scope
2083
+ * and soft-delete condition, in the `deleted` mode the relation field defaults to — so a
2084
+ * relation filter or a relation ordering sees exactly the rows the relation field returns.
2085
+ */
2086
+ const relationTargetPolicy = (ctx, relation, targetTableName, aliasedTarget) => {
2087
+ const scope = ctx.scope;
2088
+ if (!scope) return;
2089
+ const mode = scope.relationDefault(targetTableName, is(relation, One) && relation.optional === false);
2090
+ return scope.has(targetTableName, mode) ? scope.on(targetTableName, aliasedTarget, mode) : void 0;
2015
2091
  };
2016
2092
  /**
2017
2093
  * Correlates the parent row with the aliased target table using the relation's own join
@@ -2102,8 +2178,11 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2102
2178
  const throughTable = relation.throughTable;
2103
2179
  let fromClause;
2104
2180
  let joinCondition;
2181
+ let junctionPolicy;
2105
2182
  if (throughTable) {
2106
2183
  const aliasedThrough = aliasedTable(throughTable, `dgql_rel_${aliases.n++}`);
2184
+ const throughTableName = ctx.scope ? Object.keys(ctx.tables).find((name) => ctx.tables[name] === throughTable) : void 0;
2185
+ if (throughTableName && ctx.scope.has(throughTableName)) junctionPolicy = ctx.scope.on(throughTableName, aliasedThrough);
2107
2186
  const { correlation, junctionJoin } = buildThroughJoinConditions(parentTable, relation, aliasedThrough, aliasedTarget, relationName);
2108
2187
  fromClause = sql`${getTableAsAliasSQL(aliasedTarget)} inner join ${getTableAsAliasSQL(aliasedThrough)} on ${junctionJoin}`;
2109
2188
  joinCondition = correlation;
@@ -2113,6 +2192,7 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2113
2192
  }
2114
2193
  const ownWhere = relationOwnWhere(relation);
2115
2194
  const relationWhere = ownWhere ? relationsFilterToSQL(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
2195
+ const policy = and(relationTargetPolicy(ctx, relation, targetTableName, aliasedTarget), junctionPolicy);
2116
2196
  const inner = innerFilters ? extractFilters(aliasedTarget, targetTableName, innerFilters, {
2117
2197
  ...ctx,
2118
2198
  tableKey: targetTableName,
@@ -2120,9 +2200,9 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2120
2200
  }) : void 0;
2121
2201
  if (mode === "every") {
2122
2202
  if (!inner) return;
2123
- return sql`not exists (select 1 from ${fromClause} where ${and(joinCondition, relationWhere, not(inner))})`;
2203
+ return sql`not exists (select 1 from ${fromClause} where ${and(joinCondition, relationWhere, policy, not(inner))})`;
2124
2204
  }
2125
- const condition = and(joinCondition, relationWhere, inner);
2205
+ const condition = and(joinCondition, relationWhere, policy, inner);
2126
2206
  return mode === "none" ? sql`not exists (select 1 from ${fromClause} where ${condition})` : sql`exists (select 1 from ${fromClause} where ${condition})`;
2127
2207
  };
2128
2208
  /**
@@ -2648,12 +2728,14 @@ const extendRelationOrderChain = (parentTable, relationName, relEntry, ctx, chai
2648
2728
  const joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
2649
2729
  const ownWhere = relationOwnWhere(relation);
2650
2730
  const relationWhere = ownWhere ? relationsFilterToSQL(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
2731
+ const policy = relationTargetPolicy(ctx, relation, relEntry.targetTableName, aliasedTarget);
2651
2732
  return {
2652
2733
  fromParts: [...chain?.fromParts ?? [], getTableAsAliasSQL(aliasedTarget)],
2653
2734
  conditions: [
2654
2735
  ...chain?.conditions ?? [],
2655
2736
  ...joinCondition ? [joinCondition] : [],
2656
- ...relationWhere ? [relationWhere] : []
2737
+ ...relationWhere ? [relationWhere] : [],
2738
+ ...policy ? [policy] : []
2657
2739
  ],
2658
2740
  table: aliasedTarget
2659
2741
  };
@@ -3788,8 +3870,7 @@ const relationJoinColumns = (tree, table, selectionCtx) => {
3788
3870
  const relEntry = fieldData.name.endsWith(AGGREGATE_FIELD_SUFFIX) ? relations[fieldData.name.slice(0, -9)] : relations[fieldData.name];
3789
3871
  const targetTable = relEntry ? selectionCtx.tables[relEntry.targetTableName] : void 0;
3790
3872
  if (!relEntry || !targetTable) continue;
3791
- const joinCols = extractRelationJoinColumns(relEntry, table, targetTable);
3792
- if (joinCols) needed.push(joinCols.localColPropName);
3873
+ for (const pair of extractRelationJoinColumnPairs(relEntry, table, targetTable) ?? []) needed.push(pair.localColPropName);
3793
3874
  }
3794
3875
  return needed;
3795
3876
  };
@@ -3883,10 +3964,10 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
3883
3964
  });
3884
3965
  const relWhere = relationArgs?.where;
3885
3966
  const relDeleted = relationArgs?.deleted ?? scope?.relationDefault(targetTableName, is(relEntry.relation, One) && relEntry.relation.optional === false);
3886
- 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;
3967
+ 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;
3887
3968
  const hasPagination = offset != null || limit != null;
3888
3969
  const pkNames = targetPkNames ?? [];
3889
- thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
3970
+ thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName, scope), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
3890
3971
  thisRecord.offset = offset;
3891
3972
  thisRecord.limit = limit;
3892
3973
  thisRecord.with = extractRelationsParams(relationMap, tables, targetTableName, field, relTypeName, options);
@@ -3944,7 +4025,7 @@ const runRelationalSelect = async (opts) => {
3944
4025
  tableName,
3945
4026
  distinct,
3946
4027
  pkNames: pkNames ?? [],
3947
- where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
4028
+ where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, deleted),
3948
4029
  orderBy,
3949
4030
  limit: single ? 1 : opts.limit,
3950
4031
  offset
@@ -3959,8 +4040,8 @@ const runRelationalSelect = async (opts) => {
3959
4040
  allRelations: filterCtx?.relationMap
3960
4041
  }),
3961
4042
  offset: distinctKeys ? void 0 : offset,
3962
- 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,
3963
- where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, 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,
4043
+ 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,
4044
+ where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, 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,
3964
4045
  with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
3965
4046
  typeNameMapper,
3966
4047
  filterCtx,
@@ -4542,7 +4623,7 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
4542
4623
  tableName,
4543
4624
  table,
4544
4625
  where,
4545
- relationCtx: relationFilterCtx(filterCtx, tableName),
4626
+ relationCtx: relationFilterCtx(filterCtx, tableName, scope),
4546
4627
  required: requireWhere,
4547
4628
  deleted: hard ? "INCLUDE" : void 0
4548
4629
  });
@@ -4810,13 +4891,12 @@ const identifyRows = (field, args) => {
4810
4891
  * Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
4811
4892
  * Returns raw rows (NOT remapped); the caller groups + remaps them.
4812
4893
  */
4813
- const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
4894
+ const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, partitionCols, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
4814
4895
  const cols = columns ?? getColumns(targetTable);
4815
4896
  const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
4816
4897
  const orderClause = orderExprs.length ? sql` order by ${sql.join(orderExprs, sql`, `)}` : sql``;
4817
4898
  const RN = "__drizzle_graphql_rn";
4818
- const partitionCol = junction ? junction.sourceCol : foreignCol;
4819
- const rowNumber = sql`row_number() over (partition by ${partitionCol}${orderClause})`.as(RN);
4899
+ const rowNumber = sql`row_number() over (partition by ${sql.join([...partitionCols], sql`, `)}${orderClause})`.as(RN);
4820
4900
  const sub = withJunction(db.select({
4821
4901
  ...cols,
4822
4902
  [RN]: rowNumber
@@ -4855,13 +4935,14 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4855
4935
  const targetTableName = relEntry.targetTableName;
4856
4936
  const targetTable = tables[targetTableName];
4857
4937
  if (!parentTable || !targetTable) return;
4858
- const joinCols = extractRelationJoinColumns(relEntry, parentTable, targetTable);
4859
- if (!joinCols) return;
4860
- const { localColPropName, foreignCol, foreignColPropName } = joinCols;
4938
+ const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
4939
+ if (!joinPairs) return;
4940
+ const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
4941
+ const foreignCol = joinPairs[0].foreignCol;
4861
4942
  const junction = extractJunctionColumns(relEntry);
4862
- const groupCol = junction ? junction.sourceCol : foreignCol;
4863
- const groupKey = junction ? PARENT_KEY : foreignColPropName;
4864
- const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, getColumns(parentTable)[localColPropName], groupCol);
4943
+ const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
4944
+ const groupKeys = junction ? [PARENT_KEY] : joinPairs.map((pair) => pair.foreignColPropName);
4945
+ const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
4865
4946
  const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, isOne && relEntry.relation?.optional === false);
4866
4947
  const limitPolicy = isOne ? void 0 : limits?.(targetTableName);
4867
4948
  const targetPkNames = relEntry.targetPkNames ?? [];
@@ -4890,7 +4971,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4890
4971
  allRelations: filterCtx?.relationMap
4891
4972
  });
4892
4973
  const targetColumns = getColumns(targetTable);
4893
- columns[foreignColPropName] = foreignCol;
4974
+ for (const pair of joinPairs) columns[pair.foreignColPropName] = pair.foreignCol;
4894
4975
  for (const [columnName] of cursorEntries ?? []) {
4895
4976
  const column = targetColumns[columnName];
4896
4977
  if (column) columns[columnName] = column;
@@ -4902,8 +4983,8 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4902
4983
  };
4903
4984
  const resolve = async (parent, args, context, info) => {
4904
4985
  if (parent[relationName] !== void 0) return parent[relationName];
4905
- const localValue = parent[localColPropName];
4906
- if (localValue == null) return isOne ? null : [];
4986
+ const localValues = localColPropNames.map((name) => parent[name]);
4987
+ if (localValues.some((value) => value == null)) return isOne ? null : [];
4907
4988
  const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
4908
4989
  const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
4909
4990
  const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
@@ -4944,11 +5025,11 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4944
5025
  };
4945
5026
  });
4946
5027
  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" });
4947
- return getOrCreateLoader(context, loaderKey, async (parentIds) => {
5028
+ return getOrCreateLoader(context, loaderKey, async (parentKeys) => {
4948
5029
  const executor = resolveExecutor(db, context);
4949
- const uniqueIds = [...new Set(parentIds)];
5030
+ const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
4950
5031
  const scope = resolveScope(policies, context, filterCtx);
4951
- 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);
5032
+ let whereCondition = withScope(scope, targetTableName, targetTable, 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);
4952
5033
  if (distinct) {
4953
5034
  const keys = await selectDistinctKeys({
4954
5035
  db: executor,
@@ -4958,44 +5039,44 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4958
5039
  pkNames: targetPkNames,
4959
5040
  where: whereCondition,
4960
5041
  orderBy: orderByArg,
4961
- partitionBy: [groupCol],
5042
+ partitionBy: groupCols,
4962
5043
  junction: junction ? {
4963
5044
  table: junction.table,
4964
5045
  on: eq(junction.targetCol, foreignCol),
4965
- parentKey: groupCol
5046
+ parentKey: junction.sourceCol
4966
5047
  } : void 0
4967
5048
  });
4968
- if (!keys.length) return parentIds.map(() => isOne ? null : []);
4969
- whereCondition = and(whereCondition, junction ? or(...keys.map((key) => and(eq(groupCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
5049
+ if (!keys.length) return parentKeys.map(() => isOne ? null : []);
5050
+ whereCondition = and(whereCondition, junction ? or(...keys.map((key) => and(eq(junction.sourceCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
4970
5051
  }
4971
5052
  const selection = junction ? {
4972
5053
  ...columns ?? getColumns(targetTable),
4973
- [PARENT_KEY]: groupCol
5054
+ [PARENT_KEY]: junction.sourceCol
4974
5055
  } : columns;
4975
5056
  let rows;
4976
- if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName), whereArg, junction);
5057
+ 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);
4977
5058
  else {
4978
5059
  let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
4979
- const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
5060
+ const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
4980
5061
  if (orderExprs.length) q = q.orderBy(...orderExprs);
4981
5062
  rows = await q;
4982
5063
  }
4983
5064
  if (cursorEntries) attachRowCursors(rows, cursorEntries);
4984
5065
  const keyOf = (row) => {
4985
- const key = String(row[groupKey]);
5066
+ const key = joinKeyOf(groupKeys.map((name) => row[name]));
4986
5067
  if (junction) delete row[PARENT_KEY];
4987
5068
  return key;
4988
5069
  };
4989
5070
  if (isOne) {
4990
5071
  const byKey = new Map(rows.map((row) => [keyOf(row), row]));
4991
5072
  remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
4992
- return parentIds.map((id) => byKey.get(String(id)) ?? null);
5073
+ return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? null);
4993
5074
  }
4994
- const grouped = new Map(uniqueIds.map((id) => [String(id), []]));
5075
+ const grouped = new Map(uniqueKeys.map((key) => [joinKeyOf(key), []]));
4995
5076
  for (const row of rows) grouped.get(keyOf(row))?.push(row);
4996
5077
  remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
4997
- return parentIds.map((id) => grouped.get(String(id)) ?? []);
4998
- }).load(localValue);
5078
+ return parentKeys.map((key) => grouped.get(joinKeyOf(key)) ?? []);
5079
+ }).load(localValues);
4999
5080
  };
5000
5081
  return async (parent, args, context, info) => {
5001
5082
  try {
@@ -5656,6 +5737,7 @@ const resolveBuildConfig = (config, schema) => {
5656
5737
  const normalized = {};
5657
5738
  for (const [columnName, entry] of Object.entries(orderBy)) {
5658
5739
  if (!columns[columnName]) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which is not a column of '${tableName}'.`);
5740
+ 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.`);
5659
5741
  const direction = typeof entry === "string" ? entry : entry?.direction;
5660
5742
  if (direction !== "asc" && direction !== "desc") throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy.${columnName} must be 'asc', 'desc', or { direction, priority? }.`);
5661
5743
  const priority = typeof entry === "string" ? void 0 : entry?.priority;
@@ -6036,7 +6118,8 @@ const generateAggregate = (db, tableName, table, typeName, fieldName, filterArgs
6036
6118
  const request = parseAggregateRequest(info, target);
6037
6119
  if (!Object.keys(request.selection).length) return {};
6038
6120
  let query = resolveExecutor(db, context).select(request.selection).from(table);
6039
- const where = withScope(resolveScope(policies, context, filterCtx), tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName)) : void 0, args.deleted);
6121
+ const scope = resolveScope(policies, context, filterCtx);
6122
+ const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
6040
6123
  if (where) query = query.where(where);
6041
6124
  const rows = await query;
6042
6125
  return assembleAggregateRow(rows[0] ?? {}, request, target);
@@ -6069,8 +6152,8 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6069
6152
  const targetTableName = relEntry.targetTableName;
6070
6153
  const targetTable = tables[targetTableName];
6071
6154
  if (!parentTable || !targetTable) return;
6072
- const joinCols = extractRelationJoinColumns(relEntry, parentTable, targetTable);
6073
- if (!joinCols) {
6155
+ const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
6156
+ if (!joinPairs) {
6074
6157
  const key = `${tableName}.${relationName}`;
6075
6158
  if (!warned.has(key)) {
6076
6159
  warned.add(key);
@@ -6078,11 +6161,13 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6078
6161
  }
6079
6162
  return;
6080
6163
  }
6081
- const { localColPropName, foreignCol } = joinCols;
6164
+ const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
6165
+ const foreignCol = joinPairs[0].foreignCol;
6082
6166
  const junction = extractJunctionColumns(relEntry);
6083
6167
  if (junction === null) return;
6084
- const groupCol = junction ? junction.sourceCol : foreignCol;
6085
- const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, getColumns(parentTable)[localColPropName], groupCol);
6168
+ const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
6169
+ const groupAliases = groupCols.map((_, i) => `${GROUP_KEY}${i}`);
6170
+ const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
6086
6171
  const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, false);
6087
6172
  const targetTypeName = resolveTypeName(targetTableName, typeNameMapper);
6088
6173
  const type = generateAggregateTypes(targetTable, targetTableName, targetTypeName, cacheCtx);
@@ -6106,23 +6191,25 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6106
6191
  };
6107
6192
  });
6108
6193
  if (!Object.keys(request.selection).length) return {};
6109
- const localValue = parent[localColPropName];
6110
- if (localValue == null) return assembleAggregateRow({}, request, target);
6194
+ const localValues = localColPropNames.map((name) => parent[name]);
6195
+ if (localValues.some((value) => value == null)) return assembleAggregateRow({}, request, target);
6111
6196
  const whereArg = args?.where;
6112
6197
  const deleted = args?.deleted;
6113
- const loader = getOrCreateLoader(context, loaderKey, async (parentIds) => {
6198
+ const loader = getOrCreateLoader(context, loaderKey, async (parentKeys) => {
6114
6199
  const executor = resolveExecutor(db, context);
6115
- const uniqueIds = [...new Set(parentIds)];
6116
- 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);
6200
+ const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
6201
+ const scope = resolveScope(policies, context, filterCtx);
6202
+ const whereCondition = withScope(scope, targetTableName, targetTable, and(parentKeyCondition(groupCols, uniqueKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
6203
+ const groupSelection = Object.fromEntries(groupAliases.map((alias, i) => [alias, groupCols[i]]));
6117
6204
  const selected = executor.select({
6118
- [GROUP_KEY]: groupCol,
6205
+ ...groupSelection,
6119
6206
  ...request.selection
6120
6207
  }).from(targetTable);
6121
- const rows = await (junction ? selected.innerJoin(junction.table, eq(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(groupCol);
6122
- const byKey = new Map(rows.map((row) => [row[GROUP_KEY], row]));
6123
- return parentIds.map((id) => byKey.get(id) ?? {});
6208
+ const rows = await (junction ? selected.innerJoin(junction.table, eq(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(...groupCols);
6209
+ const byKey = new Map(rows.map((row) => [joinKeyOf(groupAliases.map((alias) => row[alias])), row]));
6210
+ return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? {});
6124
6211
  });
6125
- return assembleAggregateRow(await loader.load(localValue), request, target);
6212
+ return assembleAggregateRow(await loader.load(localValues), request, target);
6126
6213
  } catch (e) {
6127
6214
  throw withErrorContext(toGraphQLError(e), errorCtx);
6128
6215
  }
@@ -6329,7 +6416,8 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
6329
6416
  ...request.selection
6330
6417
  };
6331
6418
  let query = resolveExecutor(db, context).select(selection).from(table);
6332
- const where = withScope(resolveScope(policies, context, filterCtx), tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName)) : void 0, args.deleted);
6419
+ const scope = resolveScope(policies, context, filterCtx);
6420
+ const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
6333
6421
  if (where) query = query.where(where);
6334
6422
  query = query.groupBy(...keyColumns.map(([, column]) => column));
6335
6423
  const havingCondition = buildHavingCondition(args.having, columns);
@@ -6368,6 +6456,54 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
6368
6456
  };
6369
6457
  };
6370
6458
  //#endregion
6459
+ //#region src/util/builders/common/build-registry.ts
6460
+ /**
6461
+ * Keyed by the `extensions.drizzle` object of every object type the build generated, rather
6462
+ * than by the type: a schema composed from `entities.types` keeps the types, and one rebuilt
6463
+ * by a schema-transforming tool (`mapSchema`, stitching) keeps their extensions.
6464
+ */
6465
+ const runtimeByTypeMeta = /* @__PURE__ */ new WeakMap();
6466
+ /**
6467
+ * What is known about the builds of one drizzle instance, for a caller with no resolve info to
6468
+ * look its build up by. Only a lone build is kept — with two or more there is no telling which
6469
+ * one a selection belongs to — so rebuilding a schema repeatedly holds on to nothing extra.
6470
+ */
6471
+ const buildsByDb = /* @__PURE__ */ new WeakMap();
6472
+ /** Whether a build changes what a read returns at all: a row policy, a limit or a default ordering. */
6473
+ const hasPolicies = (runtime) => !!(runtime.policies?.scope || runtime.policies?.defaultOrderBy || runtime.limits);
6474
+ /** Records a finished build against the object types it generated and the instance it read. */
6475
+ const registerBuildRuntime = (runtime, types) => {
6476
+ for (const type of types) {
6477
+ const meta = drizzleExtension(type);
6478
+ if (meta) runtimeByTypeMeta.set(meta, runtime);
6479
+ }
6480
+ const previous = buildsByDb.get(runtime.db);
6481
+ buildsByDb.set(runtime.db, previous ? {
6482
+ count: previous.count + 1,
6483
+ anyPolicies: previous.anyPolicies || hasPolicies(runtime)
6484
+ } : {
6485
+ count: 1,
6486
+ only: runtime,
6487
+ anyPolicies: hasPolicies(runtime)
6488
+ });
6489
+ };
6490
+ /** The build that generated `type`, when one did. */
6491
+ const runtimeForType = (type) => {
6492
+ const meta = type && typeof type === "object" ? drizzleExtension(type) : void 0;
6493
+ return meta ? runtimeByTypeMeta.get(meta) : void 0;
6494
+ };
6495
+ /**
6496
+ * The build of `db` to use when no generated type identifies one: the only build, or none
6497
+ * when no build applies any policy. Throws when several builds exist and one of them would
6498
+ * change the read, since picking either could apply the wrong policies — or none.
6499
+ */
6500
+ const runtimeForDb = (db, helper) => {
6501
+ const builds = buildsByDb.get(db);
6502
+ if (!builds) return;
6503
+ if (builds.count === 1) return builds.only;
6504
+ 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.`);
6505
+ };
6506
+ //#endregion
6371
6507
  //#region src/util/builders/nested-writes.ts
6372
6508
  const ALL_OPS = [
6373
6509
  "create",
@@ -6860,7 +6996,7 @@ const createNestedWriteRuntime = (params) => {
6860
6996
  * nothing is rejected: `connect: {}` would otherwise attach every row in the table.
6861
6997
  */
6862
6998
  const conditionOf = (plan, filter, operation, scope) => {
6863
- const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName));
6999
+ const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
6864
7000
  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" });
6865
7001
  return withScope(scope, plan.targetTableName, plan.targetTable, condition);
6866
7002
  };
@@ -7471,6 +7607,14 @@ const createSchemaBuilder = (adapter) => {
7471
7607
  }
7472
7608
  if (Object.keys(relResolvers).length > 0) fieldResolvers[tableName] = relResolvers;
7473
7609
  }
7610
+ registerBuildRuntime({
7611
+ db: ctx.db,
7612
+ filterCtx: ctx.filterCtx,
7613
+ policies: ctx.policies,
7614
+ scoped: !!ctx.tablePolicies?.scope,
7615
+ limits: ctx.limits,
7616
+ nullOrdering: adapter.nullOrdering
7617
+ }, Object.values(ctx.outputs));
7474
7618
  return {
7475
7619
  queries: ctx.queries,
7476
7620
  mutations: ctx.mutations,
@@ -7593,7 +7737,7 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
7593
7737
  const input = stripContextValues(remapUpdateInput(set, table, tableName), policies?.contextValues?.(tableName));
7594
7738
  if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
7595
7739
  await before();
7596
- const relationCtx = relationFilterCtx(filterCtx, tableName);
7740
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
7597
7741
  const filters = scopedWhere({
7598
7742
  scope,
7599
7743
  tableName,
@@ -7639,7 +7783,7 @@ const generateUpdateMany$2 = (db, tableName, table, updateManyInput, fieldName,
7639
7783
  if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
7640
7784
  return {
7641
7785
  set: input,
7642
- filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
7786
+ filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
7643
7787
  };
7644
7788
  });
7645
7789
  await executor.transaction(async (tx) => {
@@ -7682,7 +7826,7 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
7682
7826
  const { where } = args;
7683
7827
  const hard = canHardDelete && args.hard === true;
7684
7828
  const scope = policies?.scope?.(context);
7685
- const relationCtx = relationFilterCtx(filterCtx, tableName);
7829
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
7686
7830
  const filters = scopedWhere({
7687
7831
  scope,
7688
7832
  tableName,
@@ -8024,7 +8168,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8024
8168
  excludedRef: excludedColumnRef,
8025
8169
  withTarget: true,
8026
8170
  serverOwned: Object.keys(contextColumns ?? {}),
8027
- buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
8171
+ buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
8028
8172
  });
8029
8173
  const runBatch = async (executor, { values: rows, plan }) => {
8030
8174
  let query = executor.insert(table).values(rows).returning(returning);
@@ -8106,7 +8250,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8106
8250
  const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
8107
8251
  const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
8108
8252
  if (!Object.keys(input).length && !nestedOps) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
8109
- const relationCtx = relationFilterCtx(filterCtx, tableName);
8253
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
8110
8254
  const filters = scopedWhere({
8111
8255
  scope,
8112
8256
  tableName,
@@ -8185,7 +8329,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8185
8329
  await before();
8186
8330
  const parsedInfo = parseResolveInfo(info, { deep: true });
8187
8331
  const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
8188
- const relationCtx = relationFilterCtx(filterCtx, tableName);
8332
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
8189
8333
  const filters = scopedWhere({
8190
8334
  scope,
8191
8335
  tableName,
@@ -8636,7 +8780,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
8636
8780
  return {
8637
8781
  set: input,
8638
8782
  ops,
8639
- filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
8783
+ filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
8640
8784
  };
8641
8785
  });
8642
8786
  const anyNested = entries.some((entry) => entry.ops);
@@ -8806,6 +8950,36 @@ const namingRuleOf = (options) => resolveGeneratedTypeNames({
8806
8950
  typeNameSuffix: options.typeNameSuffix
8807
8951
  });
8808
8952
  /**
8953
+ * The build whose policies the read applies. Looked up through the generated types the
8954
+ * resolve info reaches — the field's return type, the root table's type in the executing
8955
+ * schema, the parent type — so a resolver over a generated type finds its own build. Without
8956
+ * resolve info (a parsed tree was passed), the instance's only build is used.
8957
+ */
8958
+ const runtimeFor = (info, db, typeName, helper) => {
8959
+ if (!("fieldsByTypeName" in info)) {
8960
+ const candidates = [
8961
+ info.returnType ? getNamedType(info.returnType) : void 0,
8962
+ info.schema?.getType(typeName),
8963
+ info.parentType
8964
+ ];
8965
+ for (const candidate of candidates) {
8966
+ const runtime = runtimeForType(candidate);
8967
+ if (runtime && runtime.db === db) return runtime;
8968
+ }
8969
+ }
8970
+ return runtimeForDb(db, helper);
8971
+ };
8972
+ /**
8973
+ * The request's row policies under `runtime`, bound to the context the caller passed. A build
8974
+ * with `scope` hooks cannot be read without that context: the hooks would be called with
8975
+ * nothing, and running the read unscoped is the leak the option exists to prevent.
8976
+ */
8977
+ const scopeFor = (runtime, options, helper) => {
8978
+ if (!runtime?.policies?.scope) return;
8979
+ 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.`);
8980
+ return runtime.policies.scope(options.context);
8981
+ };
8982
+ /**
8809
8983
  * Reads the parsed selection tree out of a resolver's `info`, resolving fragments and
8810
8984
  * inline fragments. Accepts an already-parsed tree so a caller that has one (or is chaining
8811
8985
  * these helpers) does not pay for the walk twice.
@@ -8832,6 +9006,10 @@ const parseInfo = (info) => {
8832
9006
  * const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
8833
9007
  * ```
8834
9008
  *
9009
+ * Every nested entry carries the build's row policies — `scope`, `softDelete`, `limits` and
9010
+ * `defaults.orderBy` — exactly as the generated `with:` clause does. The build is found
9011
+ * through the resolve info; pass `context` when it configures `scope`.
9012
+ *
8835
9013
  * Returns `undefined` when the selection asks for no relations, which is the value the
8836
9014
  * query builder wants for "no `with` clause".
8837
9015
  */
@@ -8841,11 +9019,16 @@ const selectionToWith = (info, options) => {
8841
9019
  const { tables, relationMap, filterCtx } = contextFor(db);
8842
9020
  if (!tables[table]) throw new Error(`Drizzle-GraphQL Error: '${table}' is not a table in the Drizzle schema.`);
8843
9021
  if (!relationMap[table]) return;
9022
+ const typeName = resolveObjectTypeName(table, typeNameMapper, resolveName);
9023
+ const runtime = runtimeFor(info, db, typeName, "selectionToWith");
8844
9024
  const parsed = parseInfo(info);
8845
- const params = extractRelationsParams(relationMap, tables, table, parsed, resolveObjectTypeName(table, typeNameMapper, resolveName), {
9025
+ const params = extractRelationsParams(relationMap, tables, table, parsed, typeName, {
8846
9026
  typeNameMapper,
8847
- filterCtx,
8848
- resolveName
9027
+ filterCtx: runtime?.filterCtx ?? filterCtx,
9028
+ resolveName,
9029
+ scope: scopeFor(runtime, options, "selectionToWith"),
9030
+ limits: runtime?.limits,
9031
+ defaultOrderBy: runtime?.policies?.defaultOrderBy
8849
9032
  });
8850
9033
  return params && Object.keys(params).length ? params : void 0;
8851
9034
  };
@@ -8857,14 +9040,16 @@ const selectionToWith = (info, options) => {
8857
9040
  * This is what an override usually wants after it has done its own work:
8858
9041
  *
8859
9042
  * ```ts
8860
- * const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
9043
+ * const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
8861
9044
  * ```
8862
9045
  *
8863
9046
  * Relation arguments and aggregate fields keep behaving as they do in the generated schema,
8864
- * because it is the generated schema's own code path.
9047
+ * because it is the generated schema's own code path. So do the build's row policies: the
9048
+ * root table and every nested relation are scoped and soft-delete-filtered, and the limit
9049
+ * policy and default ordering apply. Pass `context` when the build configures `scope`.
8865
9050
  */
8866
9051
  const resolveSelection = async (info, options) => {
8867
- const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after } = options;
9052
+ const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after, deleted } = options;
8868
9053
  const resolveName = namingRuleOf(options);
8869
9054
  const { tables, relationMap, filterCtx, primaryKeyOf } = contextFor(db);
8870
9055
  const table = tables[tableName];
@@ -8874,25 +9059,34 @@ const resolveSelection = async (info, options) => {
8874
9059
  const source = executor ?? db;
8875
9060
  const queryBase = source.query?.[tableName];
8876
9061
  if (!queryBase) throw new Error(`Drizzle-GraphQL Error: the executor passed to resolveSelection has no relational query builder for '${tableName}'.`);
9062
+ const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
9063
+ const runtime = runtimeFor(info, db, typeName, "resolveSelection");
9064
+ const scope = scopeFor(runtime, options, "resolveSelection");
9065
+ const defaultOrderBy = runtime?.policies?.defaultOrderBy;
8877
9066
  return runRelationalSelect({
8878
9067
  queryBase,
8879
9068
  tables,
8880
9069
  tableName,
8881
9070
  table,
8882
9071
  relationMap,
8883
- typeName: resolveObjectTypeName(tableName, typeNameMapper, resolveName),
9072
+ typeName,
8884
9073
  typeNameMapper,
8885
9074
  resolveName,
8886
9075
  parsedInfo,
8887
9076
  single,
8888
9077
  where,
8889
- orderBy,
8890
- limit,
9078
+ orderBy: withDefaultOrderBy({ orderBy }, tableName, defaultOrderBy).orderBy,
9079
+ limit: single ? limit : applyLimitPolicy(limit, runtime?.limits?.(tableName)),
8891
9080
  offset,
8892
9081
  after,
8893
- filterCtx,
9082
+ filterCtx: runtime?.filterCtx ?? filterCtx,
8894
9083
  pkNames: primaryKeyOf(tableName),
8895
- db: source
9084
+ db: source,
9085
+ scope,
9086
+ deleted,
9087
+ limits: runtime?.limits,
9088
+ defaultOrderBy,
9089
+ nullOrdering: runtime?.nullOrdering
8896
9090
  });
8897
9091
  };
8898
9092
  //#endregion