@vantreeseba/drizzle-graphql 15.1.2 → 15.1.3

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
@@ -1983,6 +1983,13 @@ const extractUniqueKeyFilter = (table, tableName, fieldName, members, value) =>
1983
1983
  */
1984
1984
  const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
1985
1985
  /**
1986
+ * One alias counter per build, shared by every table's narrowed context. A statement can
1987
+ * combine subqueries compiled from different contexts — a relation filter on one table whose
1988
+ * target's scope is a filter object reaching through another relation, say — and nested
1989
+ * subqueries that drew the same alias from two separate counters would shadow each other.
1990
+ */
1991
+ const aliasCounters = /* @__PURE__ */ new WeakMap();
1992
+ /**
1986
1993
  * Narrows the build-scoped relation filter context to the table a resolver is filtering.
1987
1994
  *
1988
1995
  * Cached rather than rebuilt: the base carries the build's whole `tables` and `relationMap`,
@@ -1996,7 +2003,7 @@ const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
1996
2003
  * aliases only ever have to be unique *within* one statement, which a number that never
1997
2004
  * repeats gives more surely than one that restarts.
1998
2005
  */
1999
- const relationFilterCtx = (base, tableKey) => {
2006
+ const relationFilterCtx = (base, tableKey, scope) => {
2000
2007
  if (!base) return;
2001
2008
  let byTable = narrowedCtxCache.get(base);
2002
2009
  if (!byTable) {
@@ -2005,13 +2012,33 @@ const relationFilterCtx = (base, tableKey) => {
2005
2012
  }
2006
2013
  let ctx = byTable.get(tableKey);
2007
2014
  if (!ctx) {
2015
+ let aliases = aliasCounters.get(base);
2016
+ if (!aliases) {
2017
+ aliases = { n: 0 };
2018
+ aliasCounters.set(base, aliases);
2019
+ }
2008
2020
  ctx = {
2009
2021
  ...base,
2010
- tableKey
2022
+ tableKey,
2023
+ aliases
2011
2024
  };
2012
2025
  byTable.set(tableKey, ctx);
2013
2026
  }
2014
- return ctx;
2027
+ return scope ? {
2028
+ ...ctx,
2029
+ scope
2030
+ } : ctx;
2031
+ };
2032
+ /**
2033
+ * The row-policy predicate a subquery over a relation's target applies: the target's scope
2034
+ * and soft-delete condition, in the `deleted` mode the relation field defaults to — so a
2035
+ * relation filter or a relation ordering sees exactly the rows the relation field returns.
2036
+ */
2037
+ const relationTargetPolicy = (ctx, relation, targetTableName, aliasedTarget) => {
2038
+ const scope = ctx.scope;
2039
+ if (!scope) return;
2040
+ const mode = scope.relationDefault(targetTableName, is(relation, One) && relation.optional === false);
2041
+ return scope.has(targetTableName, mode) ? scope.on(targetTableName, aliasedTarget, mode) : void 0;
2015
2042
  };
2016
2043
  /**
2017
2044
  * Correlates the parent row with the aliased target table using the relation's own join
@@ -2102,8 +2129,11 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2102
2129
  const throughTable = relation.throughTable;
2103
2130
  let fromClause;
2104
2131
  let joinCondition;
2132
+ let junctionPolicy;
2105
2133
  if (throughTable) {
2106
2134
  const aliasedThrough = aliasedTable(throughTable, `dgql_rel_${aliases.n++}`);
2135
+ const throughTableName = ctx.scope ? Object.keys(ctx.tables).find((name) => ctx.tables[name] === throughTable) : void 0;
2136
+ if (throughTableName && ctx.scope.has(throughTableName)) junctionPolicy = ctx.scope.on(throughTableName, aliasedThrough);
2107
2137
  const { correlation, junctionJoin } = buildThroughJoinConditions(parentTable, relation, aliasedThrough, aliasedTarget, relationName);
2108
2138
  fromClause = sql`${getTableAsAliasSQL(aliasedTarget)} inner join ${getTableAsAliasSQL(aliasedThrough)} on ${junctionJoin}`;
2109
2139
  joinCondition = correlation;
@@ -2113,6 +2143,7 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2113
2143
  }
2114
2144
  const ownWhere = relationOwnWhere(relation);
2115
2145
  const relationWhere = ownWhere ? relationsFilterToSQL(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
2146
+ const policy = and(relationTargetPolicy(ctx, relation, targetTableName, aliasedTarget), junctionPolicy);
2116
2147
  const inner = innerFilters ? extractFilters(aliasedTarget, targetTableName, innerFilters, {
2117
2148
  ...ctx,
2118
2149
  tableKey: targetTableName,
@@ -2120,9 +2151,9 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2120
2151
  }) : void 0;
2121
2152
  if (mode === "every") {
2122
2153
  if (!inner) return;
2123
- return sql`not exists (select 1 from ${fromClause} where ${and(joinCondition, relationWhere, not(inner))})`;
2154
+ return sql`not exists (select 1 from ${fromClause} where ${and(joinCondition, relationWhere, policy, not(inner))})`;
2124
2155
  }
2125
- const condition = and(joinCondition, relationWhere, inner);
2156
+ const condition = and(joinCondition, relationWhere, policy, inner);
2126
2157
  return mode === "none" ? sql`not exists (select 1 from ${fromClause} where ${condition})` : sql`exists (select 1 from ${fromClause} where ${condition})`;
2127
2158
  };
2128
2159
  /**
@@ -2648,12 +2679,14 @@ const extendRelationOrderChain = (parentTable, relationName, relEntry, ctx, chai
2648
2679
  const joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
2649
2680
  const ownWhere = relationOwnWhere(relation);
2650
2681
  const relationWhere = ownWhere ? relationsFilterToSQL(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
2682
+ const policy = relationTargetPolicy(ctx, relation, relEntry.targetTableName, aliasedTarget);
2651
2683
  return {
2652
2684
  fromParts: [...chain?.fromParts ?? [], getTableAsAliasSQL(aliasedTarget)],
2653
2685
  conditions: [
2654
2686
  ...chain?.conditions ?? [],
2655
2687
  ...joinCondition ? [joinCondition] : [],
2656
- ...relationWhere ? [relationWhere] : []
2688
+ ...relationWhere ? [relationWhere] : [],
2689
+ ...policy ? [policy] : []
2657
2690
  ],
2658
2691
  table: aliasedTarget
2659
2692
  };
@@ -3883,10 +3916,10 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
3883
3916
  });
3884
3917
  const relWhere = relationArgs?.where;
3885
3918
  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;
3919
+ 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
3920
  const hasPagination = offset != null || limit != null;
3888
3921
  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;
3922
+ thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName, scope), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
3890
3923
  thisRecord.offset = offset;
3891
3924
  thisRecord.limit = limit;
3892
3925
  thisRecord.with = extractRelationsParams(relationMap, tables, targetTableName, field, relTypeName, options);
@@ -3944,7 +3977,7 @@ const runRelationalSelect = async (opts) => {
3944
3977
  tableName,
3945
3978
  distinct,
3946
3979
  pkNames: pkNames ?? [],
3947
- where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
3980
+ where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, deleted),
3948
3981
  orderBy,
3949
3982
  limit: single ? 1 : opts.limit,
3950
3983
  offset
@@ -3959,8 +3992,8 @@ const runRelationalSelect = async (opts) => {
3959
3992
  allRelations: filterCtx?.relationMap
3960
3993
  }),
3961
3994
  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,
3995
+ 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,
3996
+ 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
3997
  with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
3965
3998
  typeNameMapper,
3966
3999
  filterCtx,
@@ -4542,7 +4575,7 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
4542
4575
  tableName,
4543
4576
  table,
4544
4577
  where,
4545
- relationCtx: relationFilterCtx(filterCtx, tableName),
4578
+ relationCtx: relationFilterCtx(filterCtx, tableName, scope),
4546
4579
  required: requireWhere,
4547
4580
  deleted: hard ? "INCLUDE" : void 0
4548
4581
  });
@@ -4948,7 +4981,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4948
4981
  const executor = resolveExecutor(db, context);
4949
4982
  const uniqueIds = [...new Set(parentIds)];
4950
4983
  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);
4984
+ let whereCondition = withScope(scope, targetTableName, targetTable, and(inArray(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
4952
4985
  if (distinct) {
4953
4986
  const keys = await selectDistinctKeys({
4954
4987
  db: executor,
@@ -4973,10 +5006,10 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4973
5006
  [PARENT_KEY]: groupCol
4974
5007
  } : columns;
4975
5008
  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);
5009
+ if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName, scope), whereArg, junction);
4977
5010
  else {
4978
5011
  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) : [];
5012
+ const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
4980
5013
  if (orderExprs.length) q = q.orderBy(...orderExprs);
4981
5014
  rows = await q;
4982
5015
  }
@@ -5656,6 +5689,7 @@ const resolveBuildConfig = (config, schema) => {
5656
5689
  const normalized = {};
5657
5690
  for (const [columnName, entry] of Object.entries(orderBy)) {
5658
5691
  if (!columns[columnName]) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which is not a column of '${tableName}'.`);
5692
+ 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
5693
  const direction = typeof entry === "string" ? entry : entry?.direction;
5660
5694
  if (direction !== "asc" && direction !== "desc") throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy.${columnName} must be 'asc', 'desc', or { direction, priority? }.`);
5661
5695
  const priority = typeof entry === "string" ? void 0 : entry?.priority;
@@ -6036,7 +6070,8 @@ const generateAggregate = (db, tableName, table, typeName, fieldName, filterArgs
6036
6070
  const request = parseAggregateRequest(info, target);
6037
6071
  if (!Object.keys(request.selection).length) return {};
6038
6072
  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);
6073
+ const scope = resolveScope(policies, context, filterCtx);
6074
+ const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
6040
6075
  if (where) query = query.where(where);
6041
6076
  const rows = await query;
6042
6077
  return assembleAggregateRow(rows[0] ?? {}, request, target);
@@ -6113,7 +6148,8 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6113
6148
  const loader = getOrCreateLoader(context, loaderKey, async (parentIds) => {
6114
6149
  const executor = resolveExecutor(db, context);
6115
6150
  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);
6151
+ const scope = resolveScope(policies, context, filterCtx);
6152
+ const whereCondition = withScope(scope, targetTableName, targetTable, and(inArray(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
6117
6153
  const selected = executor.select({
6118
6154
  [GROUP_KEY]: groupCol,
6119
6155
  ...request.selection
@@ -6329,7 +6365,8 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
6329
6365
  ...request.selection
6330
6366
  };
6331
6367
  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);
6368
+ const scope = resolveScope(policies, context, filterCtx);
6369
+ const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
6333
6370
  if (where) query = query.where(where);
6334
6371
  query = query.groupBy(...keyColumns.map(([, column]) => column));
6335
6372
  const havingCondition = buildHavingCondition(args.having, columns);
@@ -6368,6 +6405,54 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
6368
6405
  };
6369
6406
  };
6370
6407
  //#endregion
6408
+ //#region src/util/builders/common/build-registry.ts
6409
+ /**
6410
+ * Keyed by the `extensions.drizzle` object of every object type the build generated, rather
6411
+ * than by the type: a schema composed from `entities.types` keeps the types, and one rebuilt
6412
+ * by a schema-transforming tool (`mapSchema`, stitching) keeps their extensions.
6413
+ */
6414
+ const runtimeByTypeMeta = /* @__PURE__ */ new WeakMap();
6415
+ /**
6416
+ * What is known about the builds of one drizzle instance, for a caller with no resolve info to
6417
+ * look its build up by. Only a lone build is kept — with two or more there is no telling which
6418
+ * one a selection belongs to — so rebuilding a schema repeatedly holds on to nothing extra.
6419
+ */
6420
+ const buildsByDb = /* @__PURE__ */ new WeakMap();
6421
+ /** Whether a build changes what a read returns at all: a row policy, a limit or a default ordering. */
6422
+ const hasPolicies = (runtime) => !!(runtime.policies?.scope || runtime.policies?.defaultOrderBy || runtime.limits);
6423
+ /** Records a finished build against the object types it generated and the instance it read. */
6424
+ const registerBuildRuntime = (runtime, types) => {
6425
+ for (const type of types) {
6426
+ const meta = drizzleExtension(type);
6427
+ if (meta) runtimeByTypeMeta.set(meta, runtime);
6428
+ }
6429
+ const previous = buildsByDb.get(runtime.db);
6430
+ buildsByDb.set(runtime.db, previous ? {
6431
+ count: previous.count + 1,
6432
+ anyPolicies: previous.anyPolicies || hasPolicies(runtime)
6433
+ } : {
6434
+ count: 1,
6435
+ only: runtime,
6436
+ anyPolicies: hasPolicies(runtime)
6437
+ });
6438
+ };
6439
+ /** The build that generated `type`, when one did. */
6440
+ const runtimeForType = (type) => {
6441
+ const meta = type && typeof type === "object" ? drizzleExtension(type) : void 0;
6442
+ return meta ? runtimeByTypeMeta.get(meta) : void 0;
6443
+ };
6444
+ /**
6445
+ * The build of `db` to use when no generated type identifies one: the only build, or none
6446
+ * when no build applies any policy. Throws when several builds exist and one of them would
6447
+ * change the read, since picking either could apply the wrong policies — or none.
6448
+ */
6449
+ const runtimeForDb = (db, helper) => {
6450
+ const builds = buildsByDb.get(db);
6451
+ if (!builds) return;
6452
+ if (builds.count === 1) return builds.only;
6453
+ 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.`);
6454
+ };
6455
+ //#endregion
6371
6456
  //#region src/util/builders/nested-writes.ts
6372
6457
  const ALL_OPS = [
6373
6458
  "create",
@@ -6860,7 +6945,7 @@ const createNestedWriteRuntime = (params) => {
6860
6945
  * nothing is rejected: `connect: {}` would otherwise attach every row in the table.
6861
6946
  */
6862
6947
  const conditionOf = (plan, filter, operation, scope) => {
6863
- const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName));
6948
+ const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
6864
6949
  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
6950
  return withScope(scope, plan.targetTableName, plan.targetTable, condition);
6866
6951
  };
@@ -7471,6 +7556,14 @@ const createSchemaBuilder = (adapter) => {
7471
7556
  }
7472
7557
  if (Object.keys(relResolvers).length > 0) fieldResolvers[tableName] = relResolvers;
7473
7558
  }
7559
+ registerBuildRuntime({
7560
+ db: ctx.db,
7561
+ filterCtx: ctx.filterCtx,
7562
+ policies: ctx.policies,
7563
+ scoped: !!ctx.tablePolicies?.scope,
7564
+ limits: ctx.limits,
7565
+ nullOrdering: adapter.nullOrdering
7566
+ }, Object.values(ctx.outputs));
7474
7567
  return {
7475
7568
  queries: ctx.queries,
7476
7569
  mutations: ctx.mutations,
@@ -7593,7 +7686,7 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
7593
7686
  const input = stripContextValues(remapUpdateInput(set, table, tableName), policies?.contextValues?.(tableName));
7594
7687
  if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
7595
7688
  await before();
7596
- const relationCtx = relationFilterCtx(filterCtx, tableName);
7689
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
7597
7690
  const filters = scopedWhere({
7598
7691
  scope,
7599
7692
  tableName,
@@ -7639,7 +7732,7 @@ const generateUpdateMany$2 = (db, tableName, table, updateManyInput, fieldName,
7639
7732
  if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
7640
7733
  return {
7641
7734
  set: input,
7642
- filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
7735
+ filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
7643
7736
  };
7644
7737
  });
7645
7738
  await executor.transaction(async (tx) => {
@@ -7682,7 +7775,7 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
7682
7775
  const { where } = args;
7683
7776
  const hard = canHardDelete && args.hard === true;
7684
7777
  const scope = policies?.scope?.(context);
7685
- const relationCtx = relationFilterCtx(filterCtx, tableName);
7778
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
7686
7779
  const filters = scopedWhere({
7687
7780
  scope,
7688
7781
  tableName,
@@ -8024,7 +8117,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8024
8117
  excludedRef: excludedColumnRef,
8025
8118
  withTarget: true,
8026
8119
  serverOwned: Object.keys(contextColumns ?? {}),
8027
- buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
8120
+ buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
8028
8121
  });
8029
8122
  const runBatch = async (executor, { values: rows, plan }) => {
8030
8123
  let query = executor.insert(table).values(rows).returning(returning);
@@ -8106,7 +8199,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8106
8199
  const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
8107
8200
  const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
8108
8201
  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);
8202
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
8110
8203
  const filters = scopedWhere({
8111
8204
  scope,
8112
8205
  tableName,
@@ -8185,7 +8278,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8185
8278
  await before();
8186
8279
  const parsedInfo = parseResolveInfo(info, { deep: true });
8187
8280
  const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
8188
- const relationCtx = relationFilterCtx(filterCtx, tableName);
8281
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
8189
8282
  const filters = scopedWhere({
8190
8283
  scope,
8191
8284
  tableName,
@@ -8636,7 +8729,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
8636
8729
  return {
8637
8730
  set: input,
8638
8731
  ops,
8639
- filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
8732
+ filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
8640
8733
  };
8641
8734
  });
8642
8735
  const anyNested = entries.some((entry) => entry.ops);
@@ -8806,6 +8899,36 @@ const namingRuleOf = (options) => resolveGeneratedTypeNames({
8806
8899
  typeNameSuffix: options.typeNameSuffix
8807
8900
  });
8808
8901
  /**
8902
+ * The build whose policies the read applies. Looked up through the generated types the
8903
+ * resolve info reaches — the field's return type, the root table's type in the executing
8904
+ * schema, the parent type — so a resolver over a generated type finds its own build. Without
8905
+ * resolve info (a parsed tree was passed), the instance's only build is used.
8906
+ */
8907
+ const runtimeFor = (info, db, typeName, helper) => {
8908
+ if (!("fieldsByTypeName" in info)) {
8909
+ const candidates = [
8910
+ info.returnType ? getNamedType(info.returnType) : void 0,
8911
+ info.schema?.getType(typeName),
8912
+ info.parentType
8913
+ ];
8914
+ for (const candidate of candidates) {
8915
+ const runtime = runtimeForType(candidate);
8916
+ if (runtime && runtime.db === db) return runtime;
8917
+ }
8918
+ }
8919
+ return runtimeForDb(db, helper);
8920
+ };
8921
+ /**
8922
+ * The request's row policies under `runtime`, bound to the context the caller passed. A build
8923
+ * with `scope` hooks cannot be read without that context: the hooks would be called with
8924
+ * nothing, and running the read unscoped is the leak the option exists to prevent.
8925
+ */
8926
+ const scopeFor = (runtime, options, helper) => {
8927
+ if (!runtime?.policies?.scope) return;
8928
+ 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.`);
8929
+ return runtime.policies.scope(options.context);
8930
+ };
8931
+ /**
8809
8932
  * Reads the parsed selection tree out of a resolver's `info`, resolving fragments and
8810
8933
  * inline fragments. Accepts an already-parsed tree so a caller that has one (or is chaining
8811
8934
  * these helpers) does not pay for the walk twice.
@@ -8832,6 +8955,10 @@ const parseInfo = (info) => {
8832
8955
  * const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
8833
8956
  * ```
8834
8957
  *
8958
+ * Every nested entry carries the build's row policies — `scope`, `softDelete`, `limits` and
8959
+ * `defaults.orderBy` — exactly as the generated `with:` clause does. The build is found
8960
+ * through the resolve info; pass `context` when it configures `scope`.
8961
+ *
8835
8962
  * Returns `undefined` when the selection asks for no relations, which is the value the
8836
8963
  * query builder wants for "no `with` clause".
8837
8964
  */
@@ -8841,11 +8968,16 @@ const selectionToWith = (info, options) => {
8841
8968
  const { tables, relationMap, filterCtx } = contextFor(db);
8842
8969
  if (!tables[table]) throw new Error(`Drizzle-GraphQL Error: '${table}' is not a table in the Drizzle schema.`);
8843
8970
  if (!relationMap[table]) return;
8971
+ const typeName = resolveObjectTypeName(table, typeNameMapper, resolveName);
8972
+ const runtime = runtimeFor(info, db, typeName, "selectionToWith");
8844
8973
  const parsed = parseInfo(info);
8845
- const params = extractRelationsParams(relationMap, tables, table, parsed, resolveObjectTypeName(table, typeNameMapper, resolveName), {
8974
+ const params = extractRelationsParams(relationMap, tables, table, parsed, typeName, {
8846
8975
  typeNameMapper,
8847
- filterCtx,
8848
- resolveName
8976
+ filterCtx: runtime?.filterCtx ?? filterCtx,
8977
+ resolveName,
8978
+ scope: scopeFor(runtime, options, "selectionToWith"),
8979
+ limits: runtime?.limits,
8980
+ defaultOrderBy: runtime?.policies?.defaultOrderBy
8849
8981
  });
8850
8982
  return params && Object.keys(params).length ? params : void 0;
8851
8983
  };
@@ -8857,14 +8989,16 @@ const selectionToWith = (info, options) => {
8857
8989
  * This is what an override usually wants after it has done its own work:
8858
8990
  *
8859
8991
  * ```ts
8860
- * const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
8992
+ * const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
8861
8993
  * ```
8862
8994
  *
8863
8995
  * 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.
8996
+ * because it is the generated schema's own code path. So do the build's row policies: the
8997
+ * root table and every nested relation are scoped and soft-delete-filtered, and the limit
8998
+ * policy and default ordering apply. Pass `context` when the build configures `scope`.
8865
8999
  */
8866
9000
  const resolveSelection = async (info, options) => {
8867
- const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after } = options;
9001
+ const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after, deleted } = options;
8868
9002
  const resolveName = namingRuleOf(options);
8869
9003
  const { tables, relationMap, filterCtx, primaryKeyOf } = contextFor(db);
8870
9004
  const table = tables[tableName];
@@ -8874,25 +9008,34 @@ const resolveSelection = async (info, options) => {
8874
9008
  const source = executor ?? db;
8875
9009
  const queryBase = source.query?.[tableName];
8876
9010
  if (!queryBase) throw new Error(`Drizzle-GraphQL Error: the executor passed to resolveSelection has no relational query builder for '${tableName}'.`);
9011
+ const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
9012
+ const runtime = runtimeFor(info, db, typeName, "resolveSelection");
9013
+ const scope = scopeFor(runtime, options, "resolveSelection");
9014
+ const defaultOrderBy = runtime?.policies?.defaultOrderBy;
8877
9015
  return runRelationalSelect({
8878
9016
  queryBase,
8879
9017
  tables,
8880
9018
  tableName,
8881
9019
  table,
8882
9020
  relationMap,
8883
- typeName: resolveObjectTypeName(tableName, typeNameMapper, resolveName),
9021
+ typeName,
8884
9022
  typeNameMapper,
8885
9023
  resolveName,
8886
9024
  parsedInfo,
8887
9025
  single,
8888
9026
  where,
8889
- orderBy,
8890
- limit,
9027
+ orderBy: withDefaultOrderBy({ orderBy }, tableName, defaultOrderBy).orderBy,
9028
+ limit: single ? limit : applyLimitPolicy(limit, runtime?.limits?.(tableName)),
8891
9029
  offset,
8892
9030
  after,
8893
- filterCtx,
9031
+ filterCtx: runtime?.filterCtx ?? filterCtx,
8894
9032
  pkNames: primaryKeyOf(tableName),
8895
- db: source
9033
+ db: source,
9034
+ scope,
9035
+ deleted,
9036
+ limits: runtime?.limits,
9037
+ defaultOrderBy,
9038
+ nullOrdering: runtime?.nullOrdering
8896
9039
  });
8897
9040
  };
8898
9041
  //#endregion