@vantreeseba/drizzle-graphql 15.1.1 → 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.cjs CHANGED
@@ -2007,6 +2007,13 @@ const extractUniqueKeyFilter = (table, tableName, fieldName, members, value) =>
2007
2007
  */
2008
2008
  const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
2009
2009
  /**
2010
+ * One alias counter per build, shared by every table's narrowed context. A statement can
2011
+ * combine subqueries compiled from different contexts — a relation filter on one table whose
2012
+ * target's scope is a filter object reaching through another relation, say — and nested
2013
+ * subqueries that drew the same alias from two separate counters would shadow each other.
2014
+ */
2015
+ const aliasCounters = /* @__PURE__ */ new WeakMap();
2016
+ /**
2010
2017
  * Narrows the build-scoped relation filter context to the table a resolver is filtering.
2011
2018
  *
2012
2019
  * Cached rather than rebuilt: the base carries the build's whole `tables` and `relationMap`,
@@ -2020,7 +2027,7 @@ const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
2020
2027
  * aliases only ever have to be unique *within* one statement, which a number that never
2021
2028
  * repeats gives more surely than one that restarts.
2022
2029
  */
2023
- const relationFilterCtx = (base, tableKey) => {
2030
+ const relationFilterCtx = (base, tableKey, scope) => {
2024
2031
  if (!base) return;
2025
2032
  let byTable = narrowedCtxCache.get(base);
2026
2033
  if (!byTable) {
@@ -2029,13 +2036,33 @@ const relationFilterCtx = (base, tableKey) => {
2029
2036
  }
2030
2037
  let ctx = byTable.get(tableKey);
2031
2038
  if (!ctx) {
2039
+ let aliases = aliasCounters.get(base);
2040
+ if (!aliases) {
2041
+ aliases = { n: 0 };
2042
+ aliasCounters.set(base, aliases);
2043
+ }
2032
2044
  ctx = {
2033
2045
  ...base,
2034
- tableKey
2046
+ tableKey,
2047
+ aliases
2035
2048
  };
2036
2049
  byTable.set(tableKey, ctx);
2037
2050
  }
2038
- return ctx;
2051
+ return scope ? {
2052
+ ...ctx,
2053
+ scope
2054
+ } : ctx;
2055
+ };
2056
+ /**
2057
+ * The row-policy predicate a subquery over a relation's target applies: the target's scope
2058
+ * and soft-delete condition, in the `deleted` mode the relation field defaults to — so a
2059
+ * relation filter or a relation ordering sees exactly the rows the relation field returns.
2060
+ */
2061
+ const relationTargetPolicy = (ctx, relation, targetTableName, aliasedTarget) => {
2062
+ const scope = ctx.scope;
2063
+ if (!scope) return;
2064
+ const mode = scope.relationDefault(targetTableName, (0, drizzle_orm.is)(relation, drizzle_orm.One) && relation.optional === false);
2065
+ return scope.has(targetTableName, mode) ? scope.on(targetTableName, aliasedTarget, mode) : void 0;
2039
2066
  };
2040
2067
  /**
2041
2068
  * Correlates the parent row with the aliased target table using the relation's own join
@@ -2126,8 +2153,11 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2126
2153
  const throughTable = relation.throughTable;
2127
2154
  let fromClause;
2128
2155
  let joinCondition;
2156
+ let junctionPolicy;
2129
2157
  if (throughTable) {
2130
2158
  const aliasedThrough = (0, drizzle_orm.aliasedTable)(throughTable, `dgql_rel_${aliases.n++}`);
2159
+ const throughTableName = ctx.scope ? Object.keys(ctx.tables).find((name) => ctx.tables[name] === throughTable) : void 0;
2160
+ if (throughTableName && ctx.scope.has(throughTableName)) junctionPolicy = ctx.scope.on(throughTableName, aliasedThrough);
2131
2161
  const { correlation, junctionJoin } = buildThroughJoinConditions(parentTable, relation, aliasedThrough, aliasedTarget, relationName);
2132
2162
  fromClause = drizzle_orm.sql`${(0, drizzle_orm.getTableAsAliasSQL)(aliasedTarget)} inner join ${(0, drizzle_orm.getTableAsAliasSQL)(aliasedThrough)} on ${junctionJoin}`;
2133
2163
  joinCondition = correlation;
@@ -2137,6 +2167,7 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2137
2167
  }
2138
2168
  const ownWhere = relationOwnWhere(relation);
2139
2169
  const relationWhere = ownWhere ? (0, drizzle_orm.relationsFilterToSQL)(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
2170
+ const policy = (0, drizzle_orm.and)(relationTargetPolicy(ctx, relation, targetTableName, aliasedTarget), junctionPolicy);
2140
2171
  const inner = innerFilters ? extractFilters(aliasedTarget, targetTableName, innerFilters, {
2141
2172
  ...ctx,
2142
2173
  tableKey: targetTableName,
@@ -2144,9 +2175,9 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
2144
2175
  }) : void 0;
2145
2176
  if (mode === "every") {
2146
2177
  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))})`;
2178
+ return drizzle_orm.sql`not exists (select 1 from ${fromClause} where ${(0, drizzle_orm.and)(joinCondition, relationWhere, policy, (0, drizzle_orm.not)(inner))})`;
2148
2179
  }
2149
- const condition = (0, drizzle_orm.and)(joinCondition, relationWhere, inner);
2180
+ const condition = (0, drizzle_orm.and)(joinCondition, relationWhere, policy, inner);
2150
2181
  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
2182
  };
2152
2183
  /**
@@ -2418,8 +2449,8 @@ const columnEnumCache = /* @__PURE__ */ new WeakMap();
2418
2449
  * input field the enum would have typed, rather than emitting an empty enum, which is
2419
2450
  * invalid GraphQL.
2420
2451
  */
2421
- const generateColumnEnum = (table, enumName, description, predicate = () => true) => {
2422
- const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}`;
2452
+ const generateColumnEnum = (table, enumName, description, predicate = () => true, variant = "") => {
2453
+ const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}\u0000${variant}`;
2423
2454
  let tableCache = columnEnumCache.get(table);
2424
2455
  const cached = tableCache?.get(cacheKey);
2425
2456
  if (cached) return cached;
@@ -2479,6 +2510,9 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
2479
2510
  * `ON DUPLICATE KEY UPDATE` fires on any unique key and takes no predicate, so offering
2480
2511
  * either there would mean silently ignoring it.
2481
2512
  *
2513
+ * `update` never offers a `contextValues` column: the server owns it, and the conflict
2514
+ * branch is an update, which must not hand the row to another owner.
2515
+ *
2482
2516
  * Returns `undefined` when the table has nothing to conflict on (`withTarget` dialects
2483
2517
  * only) — the caller then generates no upsert mutations for that table at all, rather than
2484
2518
  * an operation whose every call is a database error.
@@ -2486,12 +2520,13 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
2486
2520
  const generateOnConflictInput = (params) => {
2487
2521
  const { table, tableName, typeName, uniqueSets, tableFilters, withTarget, cacheCtx } = params;
2488
2522
  const resolveName = cacheCtx.typeName;
2523
+ const serverOwned = Object.keys(cacheCtx.contextValuesOf?.(tableName) ?? {}).sort();
2489
2524
  const updateEnum = generateColumnEnum(table, resolveName({
2490
2525
  kind: "columnEnum",
2491
2526
  defaultName: `${typeName}UpdateColumn`,
2492
2527
  table: tableName,
2493
2528
  operation: "conflictUpdate"
2494
- }), `Columns of ${typeName} that an upsert can overwrite`);
2529
+ }), `Columns of ${typeName} that an upsert can overwrite`, (_column, columnName) => !serverOwned.includes(columnName), serverOwned.join(","));
2495
2530
  if (!updateEnum) return;
2496
2531
  const fields = {
2497
2532
  action: {
@@ -2501,11 +2536,11 @@ const generateOnConflictInput = (params) => {
2501
2536
  },
2502
2537
  update: {
2503
2538
  type: new graphql.GraphQLList(new graphql.GraphQLNonNull(updateEnum)),
2504
- description: "Columns to overwrite on conflict. Defaults to every column the request supplied, minus the conflict target. Columns the request did not supply cannot be listed here — there would be no value to write."
2539
+ description: `Columns to overwrite on conflict. Defaults to every column the request supplied, minus the ${withTarget ? "conflict target" : "primary key"}. Columns the request did not supply cannot be listed here — there would be no value to write.`
2505
2540
  }
2506
2541
  };
2507
2542
  if (withTarget) {
2508
- const uniqueColumns = new Set(uniqueSets.flat());
2543
+ const uniqueColumns = new Set(uniqueSets.flatMap((set) => set.columns));
2509
2544
  const targetEnum = generateColumnEnum(table, resolveName({
2510
2545
  kind: "columnEnum",
2511
2546
  defaultName: `${typeName}ConflictTarget`,
@@ -2533,55 +2568,88 @@ const generateOnConflictInput = (params) => {
2533
2568
  });
2534
2569
  };
2535
2570
  /**
2536
- * Turns the request's `onConflict` argument and the rows it is inserting into the clause a
2537
- * dialect should attach.
2571
+ * Turns the request's `onConflict` argument and the rows it is inserting into the
2572
+ * statements a dialect should run — one per run of consecutive rows that write the same
2573
+ * columns on conflict.
2574
+ *
2575
+ * The split is what keeps a partial row partial. A multi-row insert fills a key one row
2576
+ * left out with DEFAULT, and `excluded.<column>` would carry that default onto the existing
2577
+ * row, so rows that supply different columns cannot share a `SET` list. Splitting into
2578
+ * *consecutive* runs, rather than grouping, keeps the rows a caller gets back in the order
2579
+ * it sent them. A batch whose rows agree — the usual case — is still one statement.
2538
2580
  *
2539
2581
  * `excludedRef` names the row that failed to insert in the dialect's own terms
2540
2582
  * (`excluded.col` on PostgreSQL and SQLite, `values(col)` on MySQL), which is what makes a
2541
2583
  * batch upsert update each row with its own values instead of the last row's.
2542
2584
  *
2585
+ * Never written on conflict, even when supplied: the conflict target (it already matches);
2586
+ * the primary key on a dialect with no target (MySQL's conflict can be on another unique
2587
+ * key, and copying a generated id over the existing row's would move it); and `serverOwned`
2588
+ * — the `contextValues` columns, which an update never reassigns.
2589
+ *
2543
2590
  * An UPDATE with nothing left to write degrades to NOTHING: `DO UPDATE SET` with an empty
2544
2591
  * body is not valid SQL, and doing nothing is what the request asked for anyway.
2545
2592
  */
2546
2593
  const resolveConflictPlan = (params) => {
2547
2594
  const { table, values, onConflict, pkNames, uniqueSets, excludedRef, withTarget, buildWhere } = params;
2548
2595
  const columns = (0, drizzle_orm.getColumns)(table);
2596
+ const serverOwned = new Set(params.serverOwned ?? []);
2549
2597
  let target;
2598
+ let targetWhere;
2550
2599
  if (withTarget) {
2551
2600
  const targetNames = onConflict?.target?.length ? onConflict.target : [...pkNames];
2552
2601
  if (!targetNames.length) throw drizzleError("Unable to upsert: no conflict target was given and this table has no primary key. Pass onConflict.target.", { code: "DRIZZLE_INVALID_ON_CONFLICT" });
2553
2602
  const requested = [...targetNames].sort().join(",");
2554
- if (!uniqueSets.some((set) => [...set].sort().join(",") === requested)) throw drizzleError(`Unable to upsert: [${targetNames.join(", ")}] is not a unique constraint on this table. Valid conflict targets: ${uniqueSets.map((set) => `[${set.join(", ")}]`).join(", ")}.`, { code: "DRIZZLE_INVALID_ON_CONFLICT" });
2603
+ const matched = uniqueSets.find((set) => [...set.columns].sort().join(",") === requested);
2604
+ if (!matched) throw drizzleError(`Unable to upsert: [${targetNames.join(", ")}] is not a unique constraint on this table. Valid conflict targets: ${uniqueSets.map((set) => `[${set.columns.join(", ")}]`).join(", ")}.`, { code: "DRIZZLE_INVALID_ON_CONFLICT" });
2555
2605
  target = targetNames.map((name) => columns[name]);
2606
+ targetWhere = matched.where;
2556
2607
  }
2557
- if ((onConflict?.action ?? "UPDATE") === "NOTHING") return {
2608
+ const nothing = {
2558
2609
  action: "NOTHING",
2559
2610
  target,
2611
+ targetWhere,
2560
2612
  set: {},
2561
2613
  setWhere: void 0
2562
2614
  };
2615
+ if ((onConflict?.action ?? "UPDATE") === "NOTHING") return [{
2616
+ values,
2617
+ plan: nothing
2618
+ }];
2563
2619
  const supplied = new Set(values.flatMap((row) => Object.keys(row)));
2564
- const targetNames = new Set(withTarget ? onConflict?.target?.length ? onConflict.target : pkNames : []);
2565
- let updateNames;
2620
+ const neverWritten = new Set(withTarget && onConflict?.target?.length ? onConflict.target : pkNames);
2621
+ let candidates;
2566
2622
  if (onConflict?.update?.length) {
2623
+ const owned = onConflict.update.filter((name) => serverOwned.has(name));
2624
+ if (owned.length) throw drizzleError(`Unable to upsert: onConflict.update lists ${owned.join(", ")}, which the server sets and an update never overwrites.`, { code: "DRIZZLE_INVALID_ON_CONFLICT" });
2567
2625
  const unsupplied = onConflict.update.filter((name) => !supplied.has(name));
2568
2626
  if (unsupplied.length) throw drizzleError(`Unable to upsert: onConflict.update lists ${unsupplied.join(", ")}, which the values do not supply.`, { code: "DRIZZLE_INVALID_ON_CONFLICT" });
2569
- updateNames = onConflict.update;
2570
- } else updateNames = [...supplied].filter((name) => !targetNames.has(name));
2571
- if (!updateNames.length) return {
2572
- action: "NOTHING",
2573
- target,
2574
- set: {},
2575
- setWhere: void 0
2576
- };
2577
- const set = Object.fromEntries(updateNames.map((name) => [name, excludedRef(columns[name].name)]));
2627
+ candidates = onConflict.update;
2628
+ } else candidates = [...supplied].filter((name) => !neverWritten.has(name) && !serverOwned.has(name));
2578
2629
  const setWhere = onConflict?.where && buildWhere ? buildWhere(onConflict.where) : void 0;
2579
- return {
2630
+ const planFor = (updateNames) => updateNames.length ? {
2580
2631
  action: "UPDATE",
2581
2632
  target,
2582
- set,
2633
+ targetWhere,
2634
+ set: Object.fromEntries(updateNames.map((name) => [name, excludedRef(columns[name].name)])),
2583
2635
  setWhere
2584
- };
2636
+ } : nothing;
2637
+ const batches = [];
2638
+ for (const row of values) {
2639
+ const updateNames = candidates.filter((name) => name in row);
2640
+ const key = updateNames.join(",");
2641
+ const last = batches[batches.length - 1];
2642
+ if (last?.key === key) last.values.push(row);
2643
+ else batches.push({
2644
+ key,
2645
+ values: [row],
2646
+ plan: planFor(updateNames)
2647
+ });
2648
+ }
2649
+ return batches.map(({ values: rows, plan }) => ({
2650
+ values: rows,
2651
+ plan
2652
+ }));
2585
2653
  };
2586
2654
  /** `excluded.<column>` — PostgreSQL and SQLite name the rejected row this way. */
2587
2655
  const excludedColumnRef = (columnName) => drizzle_orm.sql`excluded.${drizzle_orm.sql.identifier(columnName)}`;
@@ -2635,12 +2703,14 @@ const extendRelationOrderChain = (parentTable, relationName, relEntry, ctx, chai
2635
2703
  const joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
2636
2704
  const ownWhere = relationOwnWhere(relation);
2637
2705
  const relationWhere = ownWhere ? (0, drizzle_orm.relationsFilterToSQL)(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
2706
+ const policy = relationTargetPolicy(ctx, relation, relEntry.targetTableName, aliasedTarget);
2638
2707
  return {
2639
2708
  fromParts: [...chain?.fromParts ?? [], (0, drizzle_orm.getTableAsAliasSQL)(aliasedTarget)],
2640
2709
  conditions: [
2641
2710
  ...chain?.conditions ?? [],
2642
2711
  ...joinCondition ? [joinCondition] : [],
2643
- ...relationWhere ? [relationWhere] : []
2712
+ ...relationWhere ? [relationWhere] : [],
2713
+ ...policy ? [policy] : []
2644
2714
  ],
2645
2715
  table: aliasedTarget
2646
2716
  };
@@ -3025,9 +3095,9 @@ const primaryKeyOrderExprs = (table, pkNames) => {
3025
3095
  return pkNames.map((n) => cols[n]).filter(Boolean).map((col) => (0, drizzle_orm.asc)(col));
3026
3096
  };
3027
3097
  /**
3028
- * Every set of columns that uniquely identifies a row of `table`: the primary key first,
3029
- * then each unique constraint and unique index, then each column declared `.unique()`
3030
- * inline. Sets are property names (what GraphQL inputs use), not database column names.
3098
+ * Every set of columns that is unique on `table`: the primary key first, then each unique
3099
+ * constraint and unique index, then each column declared `.unique()` inline. Sets are
3100
+ * property names (what GraphQL inputs use), not database column names.
3031
3101
  *
3032
3102
  * `getTableConfig` is the dialect's own — the three dialects expose the same
3033
3103
  * `{ primaryKeys, uniqueConstraints, indexes }` shape but from different modules, so the
@@ -3036,9 +3106,13 @@ const primaryKeyOrderExprs = (table, pkNames) => {
3036
3106
  * Index entries whose columns are SQL expressions rather than plain columns are skipped:
3037
3107
  * an expression index is a valid conflict target in the database but cannot be named by a
3038
3108
  * column enum. Deduplicated, order-insensitive — a column that is both the primary key and
3039
- * a unique constraint yields one set.
3109
+ * a unique constraint yields one set, and a set that is unique both outright and under a
3110
+ * partial index keeps the outright one.
3111
+ *
3112
+ * A partial unique index carries its predicate as `where`: it is a conflict target only
3113
+ * together with that predicate, and it does not make the set unique across the whole table.
3040
3114
  */
3041
- const getUniqueColumnSets = (table, getTableConfig) => {
3115
+ const getUniqueTargets = (table, getTableConfig) => {
3042
3116
  const cols = visibleColumns(table);
3043
3117
  const propNameByColumnName = new Map(Object.entries(cols).map(([propName, col]) => [col.name, propName]));
3044
3118
  const toPropNames = (columnNames) => {
@@ -3050,25 +3124,40 @@ const getUniqueColumnSets = (table, getTableConfig) => {
3050
3124
  }
3051
3125
  return propNames.length ? propNames : void 0;
3052
3126
  };
3127
+ const whole = (columns) => columns ? { columns } : void 0;
3053
3128
  const config = getTableConfig(table);
3054
3129
  const candidates = [
3055
- Object.entries(cols).filter(([, col]) => col.primary).map(([propName]) => propName),
3056
- ...config.primaryKeys.map((pk) => toPropNames(pk.columns.map((c) => c.name))),
3057
- ...(config.uniqueConstraints ?? []).map((uc) => toPropNames(uc.columns.map((c) => c.name))),
3058
- ...(config.indexes ?? []).filter((index) => index.config.unique).map((index) => toPropNames(index.config.columns.map((c) => c?.name))),
3059
- ...Object.entries(cols).filter(([, col]) => col.isUnique).map(([propName]) => [propName])
3130
+ whole(Object.entries(cols).filter(([, col]) => col.primary).map(([propName]) => propName)),
3131
+ ...config.primaryKeys.map((pk) => whole(toPropNames(pk.columns.map((c) => c.name)))),
3132
+ ...(config.uniqueConstraints ?? []).map((uc) => whole(toPropNames(uc.columns.map((c) => c.name)))),
3133
+ ...(config.indexes ?? []).filter((index) => index.config.unique).map((index) => {
3134
+ const columns = toPropNames(index.config.columns.map((c) => c?.name));
3135
+ return columns ? {
3136
+ columns,
3137
+ ...index.config.where ? { where: index.config.where } : {}
3138
+ } : void 0;
3139
+ }),
3140
+ ...Object.entries(cols).filter(([, col]) => col.isUnique).map(([propName]) => ({ columns: [propName] }))
3060
3141
  ];
3061
- const seen = /* @__PURE__ */ new Set();
3062
- const sets = [];
3063
- for (const set of candidates) {
3064
- if (!set?.length) continue;
3065
- const key = [...set].sort().join(",");
3066
- if (seen.has(key)) continue;
3067
- seen.add(key);
3068
- sets.push(set);
3142
+ const byKey = /* @__PURE__ */ new Map();
3143
+ for (const candidate of candidates) {
3144
+ if (!candidate?.columns.length) continue;
3145
+ const key = [...candidate.columns].sort().join(",");
3146
+ const seen = byKey.get(key);
3147
+ if (!seen) byKey.set(key, candidate);
3148
+ else if (seen.where && !candidate.where) byKey.set(key, { columns: seen.columns });
3069
3149
  }
3070
- return sets;
3150
+ return [...byKey.values()];
3071
3151
  };
3152
+ /**
3153
+ * The column sets that identify at most one row of `table` — {@link getUniqueTargets}
3154
+ * without the partial unique indexes, which only constrain the rows their predicate
3155
+ * matches (a soft-deleted duplicate is allowed), and so do not.
3156
+ *
3157
+ * Pass `{ partial: true }` to keep them, for callers that only need to know a set is
3158
+ * unique among the rows that matter.
3159
+ */
3160
+ const getUniqueColumnSets = (table, getTableConfig, options = {}) => getUniqueTargets(table, getTableConfig).filter((target) => options.partial || !target.where).map((target) => target.columns);
3072
3161
  //#endregion
3073
3162
  //#region src/util/builders/common/distinct.ts
3074
3163
  /**
@@ -3851,10 +3940,10 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
3851
3940
  });
3852
3941
  const relWhere = relationArgs?.where;
3853
3942
  const relDeleted = relationArgs?.deleted ?? scope?.relationDefault(targetTableName, (0, drizzle_orm.is)(relEntry.relation, drizzle_orm.One) && relEntry.relation.optional === false);
3854
- 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;
3943
+ 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;
3855
3944
  const hasPagination = offset != null || limit != null;
3856
3945
  const pkNames = targetPkNames ?? [];
3857
- thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
3946
+ thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName, scope), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
3858
3947
  thisRecord.offset = offset;
3859
3948
  thisRecord.limit = limit;
3860
3949
  thisRecord.with = extractRelationsParams(relationMap, tables, targetTableName, field, relTypeName, options);
@@ -3912,7 +4001,7 @@ const runRelationalSelect = async (opts) => {
3912
4001
  tableName,
3913
4002
  distinct,
3914
4003
  pkNames: pkNames ?? [],
3915
- where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
4004
+ where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, deleted),
3916
4005
  orderBy,
3917
4006
  limit: single ? 1 : opts.limit,
3918
4007
  offset
@@ -3927,8 +4016,8 @@ const runRelationalSelect = async (opts) => {
3927
4016
  allRelations: filterCtx?.relationMap
3928
4017
  }),
3929
4018
  offset: distinctKeys ? void 0 : offset,
3930
- 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,
3931
- 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,
4019
+ 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,
4020
+ 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,
3932
4021
  with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
3933
4022
  typeNameMapper,
3934
4023
  filterCtx,
@@ -4510,7 +4599,7 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
4510
4599
  tableName,
4511
4600
  table,
4512
4601
  where,
4513
- relationCtx: relationFilterCtx(filterCtx, tableName),
4602
+ relationCtx: relationFilterCtx(filterCtx, tableName, scope),
4514
4603
  required: requireWhere,
4515
4604
  deleted: hard ? "INCLUDE" : void 0
4516
4605
  });
@@ -4698,6 +4787,19 @@ const keysFromWhere = (where, primaryKey) => {
4698
4787
  return none;
4699
4788
  };
4700
4789
  /**
4790
+ * Whether an upsert's `onConflict` leaves the primary-key values in `values` naming exactly
4791
+ * the rows it writes. It does not when the conflict is detected on another unique key — the
4792
+ * row updated is whichever one holds that key, not the one the supplied primary key names —
4793
+ * nor when conflicting rows can be skipped: `action: NOTHING`, or a `where` guard.
4794
+ */
4795
+ const conflictsOnKeyAlone = (onConflict, primaryKey) => {
4796
+ if (!onConflict || typeof onConflict !== "object") return true;
4797
+ const { target, action, where } = onConflict;
4798
+ if (action === "NOTHING" || where !== void 0 && where !== null) return false;
4799
+ if (!Array.isArray(target) || !target.length) return true;
4800
+ return [...target].sort().join(",") === [...primaryKey].sort().join(",");
4801
+ };
4802
+ /**
4701
4803
  * The rows a generated field's arguments are about, as primary-key values.
4702
4804
  *
4703
4805
  * The row identity of a mutation lives in a different argument for every operation —
@@ -4733,7 +4835,7 @@ const identifyRows = (field, args) => {
4733
4835
  table,
4734
4836
  primaryKey,
4735
4837
  rows: rows.filter((row) => !!row),
4736
- complete: entries.length > 0 && rows.every(Boolean)
4838
+ complete: entries.length > 0 && rows.every(Boolean) && (meta.operation !== "upsert" || conflictsOnKeyAlone(args?.["onConflict"], primaryKey))
4737
4839
  };
4738
4840
  }
4739
4841
  if (targetArg === "updates") {
@@ -4903,7 +5005,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4903
5005
  const executor = resolveExecutor(db, context);
4904
5006
  const uniqueIds = [...new Set(parentIds)];
4905
5007
  const scope = resolveScope(policies, context, filterCtx);
4906
- 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);
5008
+ 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, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
4907
5009
  if (distinct) {
4908
5010
  const keys = await selectDistinctKeys({
4909
5011
  db: executor,
@@ -4928,10 +5030,10 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4928
5030
  [PARENT_KEY]: groupCol
4929
5031
  } : columns;
4930
5032
  let rows;
4931
- if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName), whereArg, junction);
5033
+ 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);
4932
5034
  else {
4933
5035
  let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
4934
- const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
5036
+ const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
4935
5037
  if (orderExprs.length) q = q.orderBy(...orderExprs);
4936
5038
  rows = await q;
4937
5039
  }
@@ -5611,6 +5713,7 @@ const resolveBuildConfig = (config, schema) => {
5611
5713
  const normalized = {};
5612
5714
  for (const [columnName, entry] of Object.entries(orderBy)) {
5613
5715
  if (!columns[columnName]) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which is not a column of '${tableName}'.`);
5716
+ 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.`);
5614
5717
  const direction = typeof entry === "string" ? entry : entry?.direction;
5615
5718
  if (direction !== "asc" && direction !== "desc") throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy.${columnName} must be 'asc', 'desc', or { direction, priority? }.`);
5616
5719
  const priority = typeof entry === "string" ? void 0 : entry?.priority;
@@ -5991,7 +6094,8 @@ const generateAggregate = (db, tableName, table, typeName, fieldName, filterArgs
5991
6094
  const request = parseAggregateRequest(info, target);
5992
6095
  if (!Object.keys(request.selection).length) return {};
5993
6096
  let query = resolveExecutor(db, context).select(request.selection).from(table);
5994
- const where = withScope(resolveScope(policies, context, filterCtx), tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName)) : void 0, args.deleted);
6097
+ const scope = resolveScope(policies, context, filterCtx);
6098
+ const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
5995
6099
  if (where) query = query.where(where);
5996
6100
  const rows = await query;
5997
6101
  return assembleAggregateRow(rows[0] ?? {}, request, target);
@@ -6068,7 +6172,8 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6068
6172
  const loader = getOrCreateLoader(context, loaderKey, async (parentIds) => {
6069
6173
  const executor = resolveExecutor(db, context);
6070
6174
  const uniqueIds = [...new Set(parentIds)];
6071
- 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);
6175
+ const scope = resolveScope(policies, context, filterCtx);
6176
+ const whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
6072
6177
  const selected = executor.select({
6073
6178
  [GROUP_KEY]: groupCol,
6074
6179
  ...request.selection
@@ -6284,7 +6389,8 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
6284
6389
  ...request.selection
6285
6390
  };
6286
6391
  let query = resolveExecutor(db, context).select(selection).from(table);
6287
- const where = withScope(resolveScope(policies, context, filterCtx), tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName)) : void 0, args.deleted);
6392
+ const scope = resolveScope(policies, context, filterCtx);
6393
+ const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
6288
6394
  if (where) query = query.where(where);
6289
6395
  query = query.groupBy(...keyColumns.map(([, column]) => column));
6290
6396
  const havingCondition = buildHavingCondition(args.having, columns);
@@ -6323,6 +6429,54 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
6323
6429
  };
6324
6430
  };
6325
6431
  //#endregion
6432
+ //#region src/util/builders/common/build-registry.ts
6433
+ /**
6434
+ * Keyed by the `extensions.drizzle` object of every object type the build generated, rather
6435
+ * than by the type: a schema composed from `entities.types` keeps the types, and one rebuilt
6436
+ * by a schema-transforming tool (`mapSchema`, stitching) keeps their extensions.
6437
+ */
6438
+ const runtimeByTypeMeta = /* @__PURE__ */ new WeakMap();
6439
+ /**
6440
+ * What is known about the builds of one drizzle instance, for a caller with no resolve info to
6441
+ * look its build up by. Only a lone build is kept — with two or more there is no telling which
6442
+ * one a selection belongs to — so rebuilding a schema repeatedly holds on to nothing extra.
6443
+ */
6444
+ const buildsByDb = /* @__PURE__ */ new WeakMap();
6445
+ /** Whether a build changes what a read returns at all: a row policy, a limit or a default ordering. */
6446
+ const hasPolicies = (runtime) => !!(runtime.policies?.scope || runtime.policies?.defaultOrderBy || runtime.limits);
6447
+ /** Records a finished build against the object types it generated and the instance it read. */
6448
+ const registerBuildRuntime = (runtime, types) => {
6449
+ for (const type of types) {
6450
+ const meta = drizzleExtension(type);
6451
+ if (meta) runtimeByTypeMeta.set(meta, runtime);
6452
+ }
6453
+ const previous = buildsByDb.get(runtime.db);
6454
+ buildsByDb.set(runtime.db, previous ? {
6455
+ count: previous.count + 1,
6456
+ anyPolicies: previous.anyPolicies || hasPolicies(runtime)
6457
+ } : {
6458
+ count: 1,
6459
+ only: runtime,
6460
+ anyPolicies: hasPolicies(runtime)
6461
+ });
6462
+ };
6463
+ /** The build that generated `type`, when one did. */
6464
+ const runtimeForType = (type) => {
6465
+ const meta = type && typeof type === "object" ? drizzleExtension(type) : void 0;
6466
+ return meta ? runtimeByTypeMeta.get(meta) : void 0;
6467
+ };
6468
+ /**
6469
+ * The build of `db` to use when no generated type identifies one: the only build, or none
6470
+ * when no build applies any policy. Throws when several builds exist and one of them would
6471
+ * change the read, since picking either could apply the wrong policies — or none.
6472
+ */
6473
+ const runtimeForDb = (db, helper) => {
6474
+ const builds = buildsByDb.get(db);
6475
+ if (!builds) return;
6476
+ if (builds.count === 1) return builds.only;
6477
+ 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.`);
6478
+ };
6479
+ //#endregion
6326
6480
  //#region src/util/builders/nested-writes.ts
6327
6481
  const ALL_OPS = [
6328
6482
  "create",
@@ -6815,7 +6969,7 @@ const createNestedWriteRuntime = (params) => {
6815
6969
  * nothing is rejected: `connect: {}` would otherwise attach every row in the table.
6816
6970
  */
6817
6971
  const conditionOf = (plan, filter, operation, scope) => {
6818
- const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName));
6972
+ const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
6819
6973
  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" });
6820
6974
  return withScope(scope, plan.targetTableName, plan.targetTable, condition);
6821
6975
  };
@@ -7210,7 +7364,7 @@ const createSchemaBuilder = (adapter) => {
7210
7364
  uniqueKeysOf: (tableName) => uniqueKeys[tableName]
7211
7365
  };
7212
7366
  adapter.preflight?.(db, options);
7213
- const nestedPlans = features.nestedWrites ? buildNestedWritePlans(tables, namedRelations, (target) => uniqueColumnSets(target), (target) => primaryKeyPropNames(target), extractRelationJoinColumns, (junction) => schemaEntries.find(([, value]) => value === junction)?.[0]) : void 0;
7367
+ const nestedPlans = features.nestedWrites ? buildNestedWritePlans(tables, namedRelations, (target) => getUniqueColumnSets(target, adapter.getTableConfig, { partial: true }), (target) => primaryKeyPropNames(target), extractRelationJoinColumns, (junction) => schemaEntries.find(([, value]) => value === junction)?.[0]) : void 0;
7214
7368
  if (nestedPlans) {
7215
7369
  const omitted = gateNestedWritePlans(nestedPlans, featureOf, (tableName) => excludedTables.has(tableName));
7216
7370
  if (omitted.length) {
@@ -7426,6 +7580,14 @@ const createSchemaBuilder = (adapter) => {
7426
7580
  }
7427
7581
  if (Object.keys(relResolvers).length > 0) fieldResolvers[tableName] = relResolvers;
7428
7582
  }
7583
+ registerBuildRuntime({
7584
+ db: ctx.db,
7585
+ filterCtx: ctx.filterCtx,
7586
+ policies: ctx.policies,
7587
+ scoped: !!ctx.tablePolicies?.scope,
7588
+ limits: ctx.limits,
7589
+ nullOrdering: adapter.nullOrdering
7590
+ }, Object.values(ctx.outputs));
7429
7591
  return {
7430
7592
  queries: ctx.queries,
7431
7593
  mutations: ctx.mutations,
@@ -7498,20 +7660,33 @@ const generateUpsert = (db, tableName, table, baseType, onConflictType, fieldNam
7498
7660
  }
7499
7661
  },
7500
7662
  run: async ({ executor, args, context, before, after }) => {
7501
- const input = applyContextValuesAll(single ? [remapFromGraphQLSingleInput(args.values, table)] : remapFromGraphQLArrayInput(args.values, table), policies?.contextValues?.(tableName), context);
7663
+ const contextColumns = policies?.contextValues?.(tableName);
7664
+ const input = applyContextValuesAll(single ? [remapFromGraphQLSingleInput(args.values, table)] : remapFromGraphQLArrayInput(args.values, table), contextColumns, context);
7502
7665
  if (!input.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
7503
7666
  await before();
7504
- const plan = resolveConflictPlan({
7667
+ const batches = resolveConflictPlan({
7505
7668
  table,
7506
7669
  values: input,
7507
7670
  onConflict: args.onConflict,
7508
7671
  pkNames,
7509
7672
  uniqueSets: [],
7510
7673
  excludedRef: mysqlValuesColumnRef,
7511
- withTarget: false
7674
+ withTarget: false,
7675
+ serverOwned: Object.keys(contextColumns ?? {})
7676
+ });
7677
+ const requestedNothing = args.onConflict?.action === "NOTHING";
7678
+ const runBatch = async (target, { values, plan }) => {
7679
+ if (plan.action === "NOTHING" && !requestedNothing) {
7680
+ const columns = (0, drizzle_orm.getColumns)(table);
7681
+ const keyName = pkNames[0] ?? Object.keys(columns)[0];
7682
+ await target.insert(table).values(values).onDuplicateKeyUpdate({ set: { [keyName]: drizzle_orm.sql`${drizzle_orm.sql.identifier(columns[keyName].name)}` } });
7683
+ } else if (plan.action === "NOTHING") await target.insert(table).ignore().values(values);
7684
+ else await target.insert(table).values(values).onDuplicateKeyUpdate({ set: plan.set });
7685
+ };
7686
+ if (batches.length === 1) await runBatch(executor, batches[0]);
7687
+ else await executor.transaction(async (tx) => {
7688
+ for (const batch of batches) await runBatch(tx, batch);
7512
7689
  });
7513
- if (plan.action === "NOTHING") await executor.insert(table).ignore().values(input);
7514
- else await executor.insert(table).values(input).onDuplicateKeyUpdate({ set: plan.set });
7515
7690
  await after();
7516
7691
  return isSuccess;
7517
7692
  }
@@ -7535,7 +7710,7 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
7535
7710
  const input = stripContextValues(remapUpdateInput(set, table, tableName), policies?.contextValues?.(tableName));
7536
7711
  if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
7537
7712
  await before();
7538
- const relationCtx = relationFilterCtx(filterCtx, tableName);
7713
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
7539
7714
  const filters = scopedWhere({
7540
7715
  scope,
7541
7716
  tableName,
@@ -7581,7 +7756,7 @@ const generateUpdateMany$2 = (db, tableName, table, updateManyInput, fieldName,
7581
7756
  if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
7582
7757
  return {
7583
7758
  set: input,
7584
- filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
7759
+ filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
7585
7760
  };
7586
7761
  });
7587
7762
  await executor.transaction(async (tx) => {
@@ -7624,7 +7799,7 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
7624
7799
  const { where } = args;
7625
7800
  const hard = canHardDelete && args.hard === true;
7626
7801
  const scope = policies?.scope?.(context);
7627
- const relationCtx = relationFilterCtx(filterCtx, tableName);
7802
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
7628
7803
  const filters = scopedWhere({
7629
7804
  scope,
7630
7805
  tableName,
@@ -7957,7 +8132,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7957
8132
  });
7958
8133
  const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
7959
8134
  const runUpsert = async (target, values) => {
7960
- const plan = resolveConflictPlan({
8135
+ const batches = resolveConflictPlan({
7961
8136
  table,
7962
8137
  values,
7963
8138
  onConflict: args.onConflict,
@@ -7965,16 +8140,29 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7965
8140
  uniqueSets,
7966
8141
  excludedRef: excludedColumnRef,
7967
8142
  withTarget: true,
7968
- buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
8143
+ serverOwned: Object.keys(contextColumns ?? {}),
8144
+ buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
7969
8145
  });
7970
- let query = target.insert(table).values(values).returning(returning);
7971
- const setWhere = withScope(scope, tableName, table, plan.setWhere);
7972
- query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? { target: plan.target } : void 0) : query.onConflictDoUpdate({
7973
- target: plan.target,
7974
- set: plan.set,
7975
- setWhere
8146
+ const runBatch = async (executor, { values: rows, plan }) => {
8147
+ let query = executor.insert(table).values(rows).returning(returning);
8148
+ const setWhere = withScope(scope, tableName, table, plan.setWhere);
8149
+ query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? {
8150
+ target: plan.target,
8151
+ where: plan.targetWhere
8152
+ } : void 0) : query.onConflictDoUpdate({
8153
+ target: plan.target,
8154
+ targetWhere: plan.targetWhere,
8155
+ set: plan.set,
8156
+ setWhere
8157
+ });
8158
+ return await query;
8159
+ };
8160
+ if (batches.length === 1) return runBatch(target, batches[0]);
8161
+ return target.transaction(async (tx) => {
8162
+ const rows = [];
8163
+ for (const batch of batches) rows.push(...await runBatch(tx, batch));
8164
+ return rows;
7976
8165
  });
7977
- return await query;
7978
8166
  };
7979
8167
  const written = nestedEntries ? await writeWithNestedOps({
7980
8168
  executor,
@@ -8035,7 +8223,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8035
8223
  const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
8036
8224
  const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
8037
8225
  if (!Object.keys(input).length && !nestedOps) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
8038
- const relationCtx = relationFilterCtx(filterCtx, tableName);
8226
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
8039
8227
  const filters = scopedWhere({
8040
8228
  scope,
8041
8229
  tableName,
@@ -8114,7 +8302,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8114
8302
  await before();
8115
8303
  const parsedInfo = parseResolveInfo(info, { deep: true });
8116
8304
  const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
8117
- const relationCtx = relationFilterCtx(filterCtx, tableName);
8305
+ const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
8118
8306
  const filters = scopedWhere({
8119
8307
  scope,
8120
8308
  tableName,
@@ -8154,7 +8342,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8154
8342
  */
8155
8343
  const createSchemaDataGenerator = (adapter) => {
8156
8344
  const primaryKeyPropNames = adapter.primaryKeyPropNames;
8157
- const uniqueColumnSets = (table) => getUniqueColumnSets(table, adapter.getTableConfig);
8345
+ const uniqueTargets = (table) => getUniqueTargets(table, adapter.getTableConfig);
8158
8346
  const { generateInsert, generateUpsert, generateUpdate, generateDelete } = buildWriteResolvers(primaryKeyPropNames);
8159
8347
  const { prepareBuild, forEachTable, finalizeBuild } = createSchemaBuilder(adapter);
8160
8348
  return (db, schema, relations, options) => {
@@ -8195,7 +8383,7 @@ const createSchemaDataGenerator = (adapter) => {
8195
8383
  single: true,
8196
8384
  conflictDoNothing
8197
8385
  }) : void 0;
8198
- const uniqueSets = tableFeatures.upsert ? uniqueColumnSets(schema[tableName]) : [];
8386
+ const uniqueSets = tableFeatures.upsert ? uniqueTargets(schema[tableName]) : [];
8199
8387
  const onConflictInput = tableFeatures.upsert ? generateOnConflictInput({
8200
8388
  table: schema[tableName],
8201
8389
  tableName,
@@ -8565,7 +8753,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
8565
8753
  return {
8566
8754
  set: input,
8567
8755
  ops,
8568
- filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
8756
+ filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
8569
8757
  };
8570
8758
  });
8571
8759
  const anyNested = entries.some((entry) => entry.ops);
@@ -8735,6 +8923,36 @@ const namingRuleOf = (options) => resolveGeneratedTypeNames({
8735
8923
  typeNameSuffix: options.typeNameSuffix
8736
8924
  });
8737
8925
  /**
8926
+ * The build whose policies the read applies. Looked up through the generated types the
8927
+ * resolve info reaches — the field's return type, the root table's type in the executing
8928
+ * schema, the parent type — so a resolver over a generated type finds its own build. Without
8929
+ * resolve info (a parsed tree was passed), the instance's only build is used.
8930
+ */
8931
+ const runtimeFor = (info, db, typeName, helper) => {
8932
+ if (!("fieldsByTypeName" in info)) {
8933
+ const candidates = [
8934
+ info.returnType ? (0, graphql.getNamedType)(info.returnType) : void 0,
8935
+ info.schema?.getType(typeName),
8936
+ info.parentType
8937
+ ];
8938
+ for (const candidate of candidates) {
8939
+ const runtime = runtimeForType(candidate);
8940
+ if (runtime && runtime.db === db) return runtime;
8941
+ }
8942
+ }
8943
+ return runtimeForDb(db, helper);
8944
+ };
8945
+ /**
8946
+ * The request's row policies under `runtime`, bound to the context the caller passed. A build
8947
+ * with `scope` hooks cannot be read without that context: the hooks would be called with
8948
+ * nothing, and running the read unscoped is the leak the option exists to prevent.
8949
+ */
8950
+ const scopeFor = (runtime, options, helper) => {
8951
+ if (!runtime?.policies?.scope) return;
8952
+ 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.`);
8953
+ return runtime.policies.scope(options.context);
8954
+ };
8955
+ /**
8738
8956
  * Reads the parsed selection tree out of a resolver's `info`, resolving fragments and
8739
8957
  * inline fragments. Accepts an already-parsed tree so a caller that has one (or is chaining
8740
8958
  * these helpers) does not pay for the walk twice.
@@ -8761,6 +8979,10 @@ const parseInfo = (info) => {
8761
8979
  * const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
8762
8980
  * ```
8763
8981
  *
8982
+ * Every nested entry carries the build's row policies — `scope`, `softDelete`, `limits` and
8983
+ * `defaults.orderBy` — exactly as the generated `with:` clause does. The build is found
8984
+ * through the resolve info; pass `context` when it configures `scope`.
8985
+ *
8764
8986
  * Returns `undefined` when the selection asks for no relations, which is the value the
8765
8987
  * query builder wants for "no `with` clause".
8766
8988
  */
@@ -8770,11 +8992,16 @@ const selectionToWith = (info, options) => {
8770
8992
  const { tables, relationMap, filterCtx } = contextFor(db);
8771
8993
  if (!tables[table]) throw new Error(`Drizzle-GraphQL Error: '${table}' is not a table in the Drizzle schema.`);
8772
8994
  if (!relationMap[table]) return;
8995
+ const typeName = resolveObjectTypeName(table, typeNameMapper, resolveName);
8996
+ const runtime = runtimeFor(info, db, typeName, "selectionToWith");
8773
8997
  const parsed = parseInfo(info);
8774
- const params = extractRelationsParams(relationMap, tables, table, parsed, resolveObjectTypeName(table, typeNameMapper, resolveName), {
8998
+ const params = extractRelationsParams(relationMap, tables, table, parsed, typeName, {
8775
8999
  typeNameMapper,
8776
- filterCtx,
8777
- resolveName
9000
+ filterCtx: runtime?.filterCtx ?? filterCtx,
9001
+ resolveName,
9002
+ scope: scopeFor(runtime, options, "selectionToWith"),
9003
+ limits: runtime?.limits,
9004
+ defaultOrderBy: runtime?.policies?.defaultOrderBy
8778
9005
  });
8779
9006
  return params && Object.keys(params).length ? params : void 0;
8780
9007
  };
@@ -8786,14 +9013,16 @@ const selectionToWith = (info, options) => {
8786
9013
  * This is what an override usually wants after it has done its own work:
8787
9014
  *
8788
9015
  * ```ts
8789
- * const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
9016
+ * const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
8790
9017
  * ```
8791
9018
  *
8792
9019
  * Relation arguments and aggregate fields keep behaving as they do in the generated schema,
8793
- * because it is the generated schema's own code path.
9020
+ * because it is the generated schema's own code path. So do the build's row policies: the
9021
+ * root table and every nested relation are scoped and soft-delete-filtered, and the limit
9022
+ * policy and default ordering apply. Pass `context` when the build configures `scope`.
8794
9023
  */
8795
9024
  const resolveSelection = async (info, options) => {
8796
- const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after } = options;
9025
+ const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after, deleted } = options;
8797
9026
  const resolveName = namingRuleOf(options);
8798
9027
  const { tables, relationMap, filterCtx, primaryKeyOf } = contextFor(db);
8799
9028
  const table = tables[tableName];
@@ -8803,25 +9032,34 @@ const resolveSelection = async (info, options) => {
8803
9032
  const source = executor ?? db;
8804
9033
  const queryBase = source.query?.[tableName];
8805
9034
  if (!queryBase) throw new Error(`Drizzle-GraphQL Error: the executor passed to resolveSelection has no relational query builder for '${tableName}'.`);
9035
+ const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
9036
+ const runtime = runtimeFor(info, db, typeName, "resolveSelection");
9037
+ const scope = scopeFor(runtime, options, "resolveSelection");
9038
+ const defaultOrderBy = runtime?.policies?.defaultOrderBy;
8806
9039
  return runRelationalSelect({
8807
9040
  queryBase,
8808
9041
  tables,
8809
9042
  tableName,
8810
9043
  table,
8811
9044
  relationMap,
8812
- typeName: resolveObjectTypeName(tableName, typeNameMapper, resolveName),
9045
+ typeName,
8813
9046
  typeNameMapper,
8814
9047
  resolveName,
8815
9048
  parsedInfo,
8816
9049
  single,
8817
9050
  where,
8818
- orderBy,
8819
- limit,
9051
+ orderBy: withDefaultOrderBy({ orderBy }, tableName, defaultOrderBy).orderBy,
9052
+ limit: single ? limit : applyLimitPolicy(limit, runtime?.limits?.(tableName)),
8820
9053
  offset,
8821
9054
  after,
8822
- filterCtx,
9055
+ filterCtx: runtime?.filterCtx ?? filterCtx,
8823
9056
  pkNames: primaryKeyOf(tableName),
8824
- db: source
9057
+ db: source,
9058
+ scope,
9059
+ deleted,
9060
+ limits: runtime?.limits,
9061
+ defaultOrderBy,
9062
+ nullOrdering: runtime?.nullOrdering
8825
9063
  });
8826
9064
  };
8827
9065
  //#endregion