@vantreeseba/drizzle-graphql 15.1.4 → 15.1.6

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
@@ -1235,6 +1235,39 @@ const remapToGraphQLCore = (key, value, tableName, column, relationMap) => {
1235
1235
  }
1236
1236
  return value;
1237
1237
  };
1238
+ /**
1239
+ * Where a remapped row keeps the values its columns held before remapping, by property name —
1240
+ * only for the columns remapping changed. The output form is not always reversible: a
1241
+ * microsecond timestamp read in `'string'` mode leaves as an ISO string the `DateTime` input
1242
+ * side would cut to milliseconds. A relation field correlating on the row's join columns reads
1243
+ * them from here (see `parentJoinValues`). Non-enumerable, so the row's own keys, JSON form and
1244
+ * equality are unchanged.
1245
+ */
1246
+ const RAW_COLUMN_VALUES = Symbol("drizzle-graphql.rawColumnValues");
1247
+ /**
1248
+ * The value column `key` held in `row` before remapping: `{ found: true, value }` when remapping
1249
+ * changed it, `{ found: false }` when it did not (or the row was never remapped), in which case
1250
+ * `row[key]` is still the column's own value.
1251
+ */
1252
+ const rawColumnValue = (row, key) => {
1253
+ const raw = row[RAW_COLUMN_VALUES];
1254
+ return raw && key in raw ? {
1255
+ found: true,
1256
+ value: raw[key]
1257
+ } : { found: false };
1258
+ };
1259
+ const keepRawColumnValue = (row, key, value) => {
1260
+ let raw = row[RAW_COLUMN_VALUES];
1261
+ if (!raw) {
1262
+ raw = Object.create(null);
1263
+ Object.defineProperty(row, RAW_COLUMN_VALUES, {
1264
+ value: raw,
1265
+ enumerable: false,
1266
+ configurable: true
1267
+ });
1268
+ }
1269
+ if (!(key in raw)) raw[key] = value;
1270
+ };
1238
1271
  const remapToGraphQLSingleOutput = (queryOutput, tableName, table, relationMap) => {
1239
1272
  const columns = getColumns(table);
1240
1273
  for (const [key, value] of Object.entries(queryOutput)) {
@@ -1252,7 +1285,9 @@ const remapToGraphQLSingleOutput = (queryOutput, tableName, table, relationMap)
1252
1285
  delete queryOutput[key];
1253
1286
  continue;
1254
1287
  }
1255
- queryOutput[key] = remapToGraphQLCore(key, value, tableName, column, relationMap);
1288
+ const remapped = remapToGraphQLCore(key, value, tableName, column, relationMap);
1289
+ if (column && remapped !== value) keepRawColumnValue(queryOutput, key, value);
1290
+ queryOutput[key] = remapped;
1256
1291
  }
1257
1292
  return queryOutput;
1258
1293
  };
@@ -1831,17 +1866,104 @@ const extractRelationJoinColumnPairs = (relEntry, parentTable, targetTable) => {
1831
1866
  }
1832
1867
  return pairs;
1833
1868
  };
1869
+ /** A decimal written as text, in a form `canonicalDecimal` can normalise. */
1870
+ const DECIMAL_TEXT = /^([+-]?)(\d*)(?:\.(\d*))?$/;
1871
+ /**
1872
+ * Decimal text without the zeros that do not change its value: `1.500` and `01.5` are both
1873
+ * `1.5`, as SQL compares them. Anything else (an exponent, `NaN`) is returned unchanged.
1874
+ */
1875
+ const canonicalDecimal = (value) => {
1876
+ const match = DECIMAL_TEXT.exec(value);
1877
+ if (!match || !match[2] && !match[3]) return value;
1878
+ const whole = match[2].replace(/^0+(?=\d)/, "") || "0";
1879
+ const fraction = (match[3] ?? "").replace(/0+$/, "");
1880
+ const digits = fraction ? `${whole}.${fraction}` : whole;
1881
+ return match[1] === "-" && digits !== "0" ? `-${digits}` : digits;
1882
+ };
1883
+ /** An ISO instant without trailing zeros in its fraction: `…:00.100Z` and `…:00.1Z` agree. */
1884
+ const trimIsoFraction = (iso) => iso.replace(/(\.\d*?)0+Z$/, "$1Z").replace(/\.Z$/, "Z");
1885
+ /**
1886
+ * One join value in a form both sides of a relation produce for the same SQL value. The parent's
1887
+ * key and a related row's can arrive in different JS forms — a `Date` and the text of the same
1888
+ * instant, `1.50` and `1.500` from decimals of different scale — and `String()` would tell them
1889
+ * apart (it even drops a `Date`'s milliseconds).
1890
+ */
1891
+ const joinValueKey = (value, column) => {
1892
+ if (value instanceof Date) {
1893
+ if (Number.isNaN(value.getTime())) return String(value);
1894
+ if (column && isDateOnlyColumn(column)) return dateToCalendarDate(value) ?? value.toISOString();
1895
+ return trimIsoFraction(value.toISOString());
1896
+ }
1897
+ if (typeof value === "bigint") return value.toString();
1898
+ if (value instanceof Uint8Array) return Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString("hex");
1899
+ if (typeof value === "string" && column) {
1900
+ if (isMySqlDateTimeStringColumn(column)) return trimIsoFraction(mySqlDateTimeTextToIso(value));
1901
+ if (isPgTimestampStringColumn(column)) return trimIsoFraction(pgTimestampTextToIso(value));
1902
+ if ((column.dataType ?? "") === "string numeric") return canonicalDecimal(value);
1903
+ }
1904
+ return String(value);
1905
+ };
1834
1906
  /**
1835
1907
  * 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.
1908
+ * parent. `columns` are the columns the values were read from, in the same order: the parent's
1909
+ * join columns for a parent key, the target's (or the junction's) for a related row. Values the
1910
+ * database compares equal give the same key — see {@link joinValueKey}.
1911
+ */
1912
+ const joinKeyOf = (values, columns) => values.length === 1 ? joinValueKey(values[0], columns?.[0]) : JSON.stringify(values.map((value, i) => joinValueKey(value, columns?.[i])));
1913
+ /**
1914
+ * The parent row's values for the relation's join columns, as the database holds them. The row
1915
+ * a relation field resolves under has been remapped to its GraphQL form — a `Date` is an ISO
1916
+ * string by then, a `bigint` a decimal string — which can neither be bound back through every
1917
+ * column's encoder (SQLite's integer timestamp calls `getTime()` on it) nor be compared with the
1918
+ * related rows' decoded values. The values from before remapping are used when the row kept
1919
+ * them; otherwise the output form is mapped back the way an input value would be.
1920
+ */
1921
+ const parentJoinValues = (parent, pairs) => pairs.map(({ localColPropName, localCol }) => {
1922
+ const raw = rawColumnValue(parent, localColPropName);
1923
+ if (raw.found) return raw.value;
1924
+ const value = parent[localColPropName];
1925
+ if (value == null) return value;
1926
+ try {
1927
+ return remapFromGraphQLCore(value, localCol, localColPropName);
1928
+ } catch {
1929
+ return value;
1930
+ }
1931
+ });
1932
+ /**
1933
+ * At most this many bound parameters for the parent keys of one batched relation query. SQLite
1934
+ * accepts 32,766 per statement, Postgres and MySQL 65,535; the rest is left to the request's own
1935
+ * filters. A batch with more parents runs as several queries.
1936
+ */
1937
+ const MAX_PARENT_KEY_PARAMS = 1e4;
1938
+ /**
1939
+ * The parent keys of one batch split into the groups each query reads, so that no query binds
1940
+ * more than {@link MAX_PARENT_KEY_PARAMS} key values. Every parent lands in exactly one group, so
1941
+ * a per-parent result — its rows, its window, its count — is the same whichever query made it.
1942
+ */
1943
+ const parentKeyChunks = (keys, width) => {
1944
+ const size = Math.max(1, Math.floor(MAX_PARENT_KEY_PARAMS / Math.max(1, width)));
1945
+ const chunks = [];
1946
+ for (let i = 0; i < keys.length; i += size) chunks.push(keys.slice(i, i + size));
1947
+ return chunks;
1948
+ };
1949
+ /**
1950
+ * `or` over `conditions` as a balanced tree. A flat `a OR b OR c …` is parsed left-deep, and
1951
+ * SQLite refuses an expression more than 1,000 levels deep; balanced, a batch of thousands of
1952
+ * parents stays a few levels deep.
1837
1953
  */
1838
- const joinKeyOf = (values) => values.length === 1 ? String(values[0]) : JSON.stringify(values.map((value) => String(value)));
1954
+ const balancedOr = (conditions) => {
1955
+ if (conditions.length <= 2) return or(...conditions);
1956
+ const middle = Math.ceil(conditions.length / 2);
1957
+ return or(balancedOr(conditions.slice(0, middle)), balancedOr(conditions.slice(middle)));
1958
+ };
1839
1959
  /**
1840
1960
  * The rows whose `cols` hold one of `keys`: `col IN (…)` for a single join column, and an `OR`
1841
1961
  * 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.
1962
+ * every dialect accepts, unlike a row-value `IN` list. The `OR` is nested as a balanced tree.
1963
+ * Callers split a large batch with {@link parentKeyChunks} first, which keeps either form within
1964
+ * the dialects' parameter limits.
1843
1965
  */
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])))));
1966
+ const parentKeyCondition = (cols, keys) => cols.length === 1 ? inArray(cols[0], keys.map((key) => key[0])) : balancedOr(keys.map((key) => and(...cols.map((col, i) => eq(col, key[i])))));
1845
1967
  /**
1846
1968
  * The junction table and its two columns for a `.through()` (many-to-many) relation:
1847
1969
  * `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
@@ -4937,7 +5059,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4937
5059
  if (!parentTable || !targetTable) return;
4938
5060
  const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
4939
5061
  if (!joinPairs) return;
4940
- const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
5062
+ const localCols = joinPairs.map((pair) => pair.localCol);
4941
5063
  const foreignCol = joinPairs[0].foreignCol;
4942
5064
  const junction = extractJunctionColumns(relEntry);
4943
5065
  const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
@@ -4983,7 +5105,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4983
5105
  };
4984
5106
  const resolve = async (parent, args, context, info) => {
4985
5107
  if (parent[relationName] !== void 0) return parent[relationName];
4986
- const localValues = localColPropNames.map((name) => parent[name]);
5108
+ const localValues = parentJoinValues(parent, joinPairs);
4987
5109
  if (localValues.some((value) => value == null)) return isOne ? null : [];
4988
5110
  const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
4989
5111
  const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
@@ -5027,55 +5149,60 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
5027
5149
  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" });
5028
5150
  return getOrCreateLoader(context, loaderKey, async (parentKeys) => {
5029
5151
  const executor = resolveExecutor(db, context);
5030
- const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
5152
+ const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key, localCols), key])).values()];
5031
5153
  const scope = resolveScope(policies, context, filterCtx);
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);
5033
- if (distinct) {
5034
- const keys = await selectDistinctKeys({
5035
- db: executor,
5036
- table: targetTable,
5037
- tableName: targetTableName,
5038
- distinct,
5039
- pkNames: targetPkNames,
5040
- where: whereCondition,
5041
- orderBy: orderByArg,
5042
- partitionBy: groupCols,
5043
- junction: junction ? {
5044
- table: junction.table,
5045
- on: eq(junction.targetCol, foreignCol),
5046
- parentKey: junction.sourceCol
5047
- } : void 0
5048
- });
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));
5051
- }
5052
- const selection = junction ? {
5053
- ...columns ?? getColumns(targetTable),
5054
- [PARENT_KEY]: junction.sourceCol
5055
- } : columns;
5056
- let rows;
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);
5058
- else {
5059
- let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
5060
- const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
5061
- if (orderExprs.length) q = q.orderBy(...orderExprs);
5062
- rows = await q;
5063
- }
5064
- if (cursorEntries) attachRowCursors(rows, cursorEntries);
5065
- const keyOf = (row) => {
5066
- const key = joinKeyOf(groupKeys.map((name) => row[name]));
5067
- if (junction) delete row[PARENT_KEY];
5068
- return key;
5069
- };
5070
- if (isOne) {
5071
- const byKey = new Map(rows.map((row) => [keyOf(row), row]));
5154
+ const loadChunk = async (chunkKeys) => {
5155
+ let whereCondition = withScope(scope, targetTableName, targetTable, and(parentKeyCondition(groupCols, chunkKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
5156
+ if (distinct) {
5157
+ const keys = await selectDistinctKeys({
5158
+ db: executor,
5159
+ table: targetTable,
5160
+ tableName: targetTableName,
5161
+ distinct,
5162
+ pkNames: targetPkNames,
5163
+ where: whereCondition,
5164
+ orderBy: orderByArg,
5165
+ partitionBy: groupCols,
5166
+ junction: junction ? {
5167
+ table: junction.table,
5168
+ on: eq(junction.targetCol, foreignCol),
5169
+ parentKey: junction.sourceCol
5170
+ } : void 0
5171
+ });
5172
+ if (!keys.length) return /* @__PURE__ */ new Map();
5173
+ whereCondition = and(whereCondition, junction ? or(...keys.map((key) => and(eq(junction.sourceCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
5174
+ }
5175
+ const selection = junction ? {
5176
+ ...columns ?? getColumns(targetTable),
5177
+ [PARENT_KEY]: junction.sourceCol
5178
+ } : columns;
5179
+ let rows;
5180
+ 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);
5181
+ else {
5182
+ let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
5183
+ const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
5184
+ if (orderExprs.length) q = q.orderBy(...orderExprs);
5185
+ rows = await q;
5186
+ }
5187
+ if (cursorEntries) attachRowCursors(rows, cursorEntries);
5188
+ const keyOf = (row) => {
5189
+ const key = joinKeyOf(groupKeys.map((name) => row[name]), groupCols);
5190
+ if (junction) delete row[PARENT_KEY];
5191
+ return key;
5192
+ };
5193
+ if (isOne) {
5194
+ const byKey = new Map(rows.map((row) => [keyOf(row), row]));
5195
+ remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
5196
+ return byKey;
5197
+ }
5198
+ const grouped = new Map(chunkKeys.map((key) => [joinKeyOf(key, localCols), []]));
5199
+ for (const row of rows) grouped.get(keyOf(row))?.push(row);
5072
5200
  remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
5073
- return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? null);
5074
- }
5075
- const grouped = new Map(uniqueKeys.map((key) => [joinKeyOf(key), []]));
5076
- for (const row of rows) grouped.get(keyOf(row))?.push(row);
5077
- remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
5078
- return parentKeys.map((key) => grouped.get(joinKeyOf(key)) ?? []);
5201
+ return grouped;
5202
+ };
5203
+ const results = /* @__PURE__ */ new Map();
5204
+ for (const chunk of parentKeyChunks(uniqueKeys, groupCols.length)) for (const [key, value] of await loadChunk(chunk)) results.set(key, value);
5205
+ return parentKeys.map((key) => results.get(joinKeyOf(key, localCols)) ?? (isOne ? null : []));
5079
5206
  }).load(localValues);
5080
5207
  };
5081
5208
  return async (parent, args, context, info) => {
@@ -6161,7 +6288,7 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6161
6288
  }
6162
6289
  return;
6163
6290
  }
6164
- const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
6291
+ const localCols = joinPairs.map((pair) => pair.localCol);
6165
6292
  const foreignCol = joinPairs[0].foreignCol;
6166
6293
  const junction = extractJunctionColumns(relEntry);
6167
6294
  if (junction === null) return;
@@ -6191,23 +6318,26 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6191
6318
  };
6192
6319
  });
6193
6320
  if (!Object.keys(request.selection).length) return {};
6194
- const localValues = localColPropNames.map((name) => parent[name]);
6321
+ const localValues = parentJoinValues(parent, joinPairs);
6195
6322
  if (localValues.some((value) => value == null)) return assembleAggregateRow({}, request, target);
6196
6323
  const whereArg = args?.where;
6197
6324
  const deleted = args?.deleted;
6198
6325
  const loader = getOrCreateLoader(context, loaderKey, async (parentKeys) => {
6199
6326
  const executor = resolveExecutor(db, context);
6200
- const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
6327
+ const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key, localCols), key])).values()];
6201
6328
  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
6329
  const groupSelection = Object.fromEntries(groupAliases.map((alias, i) => [alias, groupCols[i]]));
6204
- const selected = executor.select({
6205
- ...groupSelection,
6206
- ...request.selection
6207
- }).from(targetTable);
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)) ?? {});
6330
+ const byKey = /* @__PURE__ */ new Map();
6331
+ for (const chunk of parentKeyChunks(uniqueKeys, groupCols.length)) {
6332
+ const whereCondition = withScope(scope, targetTableName, targetTable, and(parentKeyCondition(groupCols, chunk), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
6333
+ const selected = executor.select({
6334
+ ...groupSelection,
6335
+ ...request.selection
6336
+ }).from(targetTable);
6337
+ const rows = await (junction ? selected.innerJoin(junction.table, eq(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(...groupCols);
6338
+ for (const row of rows) byKey.set(joinKeyOf(groupAliases.map((alias) => row[alias]), groupCols), row);
6339
+ }
6340
+ return parentKeys.map((key) => byKey.get(joinKeyOf(key, localCols)) ?? {});
6211
6341
  });
6212
6342
  return assembleAggregateRow(await loader.load(localValues), request, target);
6213
6343
  } catch (e) {
@@ -6516,6 +6646,20 @@ const ALL_OPS = [
6516
6646
  * accepts NULL.
6517
6647
  */
6518
6648
  const isOptionalOnInsert = (column) => !column.notNull || !!column.hasDefault || !!column.defaultFn;
6649
+ /**
6650
+ * The plan fields that carry a relation's own `where` (see {@link relationOwnCondition}).
6651
+ *
6652
+ * A `where` on the target narrows every row selection the write makes, and takes `create`
6653
+ * away: the row it inserts is attached whether or not it satisfies the `where`, so the
6654
+ * relation could come back without it. A `where` inherited from the reverse relation filters
6655
+ * the parent instead, and becomes a per-parent check.
6656
+ */
6657
+ const ownWherePlan = (relEntry, parentTable, targetTable, localCol) => {
6658
+ const condition = () => relationOwnCondition(relEntry, parentTable, targetTable, [localCol], [localCol]);
6659
+ if (!condition()) return {};
6660
+ if (!isRelationFilterReversed(relEntry.relation ?? relEntry)) return { ownCondition: () => condition() };
6661
+ return { parentShows: async (executor, key) => (await executor.select({ key: localCol }).from(parentTable).where(and(eq(localCol, key), condition())).limit(1)).length > 0 };
6662
+ };
6519
6663
  /** The property name a column is stored under on its table, matched by identity. */
6520
6664
  const propNameOf = (table, column) => {
6521
6665
  if (!column) return;
@@ -6536,7 +6680,7 @@ const propNameOf = (table, column) => {
6536
6680
  * `onUnresolved` is called when the relation's columns are of a supported shape but cannot be
6537
6681
  * matched to the three tables — a drizzle-orm mismatch rather than an unsupported relation.
6538
6682
  */
6539
- const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
6683
+ const buildThroughPlan = (relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
6540
6684
  if (is(relation, One)) return;
6541
6685
  const junction = relation.throughTable;
6542
6686
  const sourceEntries = relation.through?.source;
@@ -6577,6 +6721,7 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
6577
6721
  targetCol
6578
6722
  },
6579
6723
  targetKeyColumns: { [foreignColPropName]: foreignCol },
6724
+ ...ownWherePlan(relEntry, table, targetTable, localCol),
6580
6725
  allowed: /* @__PURE__ */ new Set([
6581
6726
  "connect",
6582
6727
  "disconnect",
@@ -6591,8 +6736,9 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
6591
6736
  * rather than a foreign-key one — see {@link buildThroughPlan}.
6592
6737
  *
6593
6738
  * Skipped, and so absent from the generated inputs rather than failing at runtime:
6594
- * relations whose join columns cannot be read off the drizzle relation; and relations where
6595
- * neither join column is unique, which leaves no way to tell which side stores the key.
6739
+ * relations whose join columns cannot be read off the drizzle relation; relations joining on
6740
+ * more than one column per side; and relations where neither join column is unique, which
6741
+ * leaves no way to tell which side stores the key.
6596
6742
  */
6597
6743
  const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns, tableNameOf = () => void 0) => {
6598
6744
  const uniqueCache = /* @__PURE__ */ new Map();
@@ -6624,10 +6770,11 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
6624
6770
  if (!targetTable) continue;
6625
6771
  const omitted = `the '${relationName}' field of its nested-write create and update inputs`;
6626
6772
  if (relation.through) {
6627
- const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
6773
+ const throughPlan = buildThroughPlan(relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
6628
6774
  if (throughPlan) tablePlans[relationName] = throughPlan;
6629
6775
  continue;
6630
6776
  }
6777
+ if ((relationColumns(relation, "source")?.length ?? 0) > 1 || (relationColumns(relation, "target")?.length ?? 0) > 1) continue;
6631
6778
  const joinColumns = extractJoinColumns(relEntry, table, targetTable);
6632
6779
  if (!joinColumns) {
6633
6780
  warnUnresolvedJoin(relEntry, tableName, relationName, omitted);
@@ -6650,8 +6797,15 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
6650
6797
  else continue;
6651
6798
  const fkColumn = fkSide === "child" ? foreignCol : localCol;
6652
6799
  const targetColumns = getColumns(targetTable);
6653
- const targetKeyColumns = Object.fromEntries(resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]).map((name) => [name, targetColumns[name]]));
6800
+ const targetPrimaryKey = resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]);
6801
+ const targetKeyColumns = Object.fromEntries(targetPrimaryKey.map((name) => [name, targetColumns[name]]));
6654
6802
  targetKeyColumns[foreignColPropName] = foreignCol;
6803
+ const ownWhere = ownWherePlan(relEntry, table, targetTable, localCol);
6804
+ const ops = !fkColumn.notNull ? isOne ? [
6805
+ "create",
6806
+ "connect",
6807
+ "disconnect"
6808
+ ] : ALL_OPS : ["create", "connect"];
6655
6809
  tablePlans[relationName] = {
6656
6810
  relationName,
6657
6811
  targetTableName,
@@ -6664,11 +6818,9 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
6664
6818
  fkSide,
6665
6819
  canDetach: !fkColumn.notNull,
6666
6820
  targetKeyColumns,
6667
- allowed: new Set(!fkColumn.notNull ? isOne ? [
6668
- "create",
6669
- "connect",
6670
- "disconnect"
6671
- ] : ALL_OPS : ["create", "connect"])
6821
+ targetPrimaryKey,
6822
+ ...ownWhere,
6823
+ allowed: new Set(ownWhere.ownCondition ? ops.filter((op) => op !== "create") : ops)
6672
6824
  };
6673
6825
  }
6674
6826
  if (Object.keys(tablePlans).length) plans[tableName] = tablePlans;
@@ -6743,6 +6895,35 @@ const gateNestedWritePlans = (plans, featureOf, isExcluded) => {
6743
6895
  }
6744
6896
  return omitted;
6745
6897
  };
6898
+ /**
6899
+ * Takes out every relation whose foreign key is a `contextValues` column of the table storing
6900
+ * it — the parent's own column for a parent-side relation, the target's for a child-side one,
6901
+ * either junction column for a many-to-many.
6902
+ *
6903
+ * Every operation on such a relation writes that column: `connect`, `disconnect` and `set`
6904
+ * by definition, and a child-side `create` too, since it fills the key in from the parent row
6905
+ * rather than from context. That column is the server's to set — an insert stamps it and an
6906
+ * update never touches it — so writing it from a nested input would let a client pick, or
6907
+ * change, the owner the server assigns. Whole relations are dropped, not single inputs: no
6908
+ * operation on them is left that does not write the key.
6909
+ *
6910
+ * Returns one line per relation taken out, for the build note.
6911
+ */
6912
+ const dropContextValueRelations = (plans, contextValuesOf) => {
6913
+ const omitted = [];
6914
+ if (!contextValuesOf) return omitted;
6915
+ for (const [tableName, tablePlans] of Object.entries(plans)) {
6916
+ for (const [relationName, plan] of Object.entries(tablePlans)) {
6917
+ const stamped = (plan.through ? [[plan.through.tableName, plan.through.sourceColPropName], [plan.through.tableName, plan.through.targetColPropName]] : plan.fkSide === "parent" ? [[tableName, plan.localColPropName]] : [[plan.targetTableName, plan.foreignColPropName]]).find(([owner, column]) => owner !== void 0 && contextValuesOf(owner)?.[column]);
6918
+ if (stamped) {
6919
+ delete tablePlans[relationName];
6920
+ omitted.push(`${tableName}.${relationName} (${stamped[0]}.${stamped[1]} is a contextValues column)`);
6921
+ }
6922
+ }
6923
+ if (!Object.keys(tablePlans).length) delete plans[tableName];
6924
+ }
6925
+ return omitted;
6926
+ };
6746
6927
  const NO_COLUMNS = /* @__PURE__ */ new Set();
6747
6928
  /**
6748
6929
  * Builds the nested-write input types for a schema.
@@ -6773,7 +6954,8 @@ const createNestedWriteTypes = (params) => {
6773
6954
  if (cached) return cached;
6774
6955
  const omitted = plan.fkSide === "child" ? plan.foreignColPropName : void 0;
6775
6956
  const contextColumns = cacheCtx.contextValuesOf?.(plan.targetTableName);
6776
- const fields = Object.fromEntries(Object.entries(visibleColumns(plan.targetTable)).filter(([columnName]) => columnName !== omitted && !contextColumns?.[columnName]).map(([columnName, column]) => [columnName, drizzleColumnToGraphQLType(column, columnName, plan.targetTableName, false, true, true)]));
6957
+ const markerColumn = cacheCtx.softDeleteOf?.(plan.targetTableName)?.columnName;
6958
+ const fields = Object.fromEntries(Object.entries(visibleColumns(plan.targetTable)).filter(([columnName]) => columnName !== omitted && !contextColumns?.[columnName] && columnName !== markerColumn).map(([columnName, column]) => [columnName, drizzleColumnToGraphQLType(column, columnName, plan.targetTableName, false, true, true)]));
6777
6959
  const type = new GraphQLInputObjectType({
6778
6960
  name: cacheCtx.typeName({
6779
6961
  kind: "nestedWriteInput",
@@ -6897,6 +7079,8 @@ const createNestedWriteTypes = (params) => {
6897
7079
  * join columns the whole batch's RETURNING clause needs, without inspecting each row's write.
6898
7080
  */
6899
7081
  const mergedOps = (entries) => Object.assign({}, ...entries.map((entry) => entry.ops));
7082
+ /** A to-one `connect` whose filter did not match exactly one row. */
7083
+ const connectMatchError = (relationName, matched) => drizzleError(`Drizzle-GraphQL Error: 'connect' on '${relationName}' matched ${matched} rows — it attaches a single row, so its filter must match exactly one.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
6900
7084
  const asArray = (value) => Array.isArray(value) ? value : value === void 0 ? [] : [value];
6901
7085
  const supplied = (value) => value !== void 0 && value !== null;
6902
7086
  /**
@@ -6988,6 +7172,8 @@ const registerChildSteps = (steps, plan, op) => {
6988
7172
  };
6989
7173
  /** The input that attaches a row to a to-one, which detaches whatever was attached before. */
6990
7174
  const attachingOp = (op) => op["connect"] !== void 0 ? "connect" : op["create"] !== void 0 ? "create" : void 0;
7175
+ /** The child-side relation in `ops` whose target is the parent's own table, if there is one. */
7176
+ const selfTargetingPlan = (tablePlans, tableName, ops) => Object.keys(ops).map((relationName) => tablePlans?.[relationName]).find((plan) => plan?.fkSide === "child" && !plan.through && plan.targetTableName === tableName);
6991
7177
  const createNestedWriteRuntime = (params) => {
6992
7178
  const { plans, filterCtx, policies, contextValues } = params;
6993
7179
  const onWrite = policies?.onWrite;
@@ -6998,7 +7184,7 @@ const createNestedWriteRuntime = (params) => {
6998
7184
  const conditionOf = (plan, filter, operation, scope) => {
6999
7185
  const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
7000
7186
  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" });
7001
- return withScope(scope, plan.targetTableName, plan.targetTable, condition);
7187
+ return withScope(scope, plan.targetTableName, plan.targetTable, and(condition, plan.ownCondition?.()));
7002
7188
  };
7003
7189
  const assertSingleOperation = (plan, op) => {
7004
7190
  const given = [
@@ -7021,22 +7207,24 @@ const createNestedWriteRuntime = (params) => {
7021
7207
  * error on some of them — the read and the write share the surrounding transaction, so no
7022
7208
  * other writer can slip a duplicate in between them.
7023
7209
  */
7024
- const applyThroughOps = async (executor, plan, key, op, scope, steps) => {
7210
+ const applyThroughOps = async (executor, plan, key, op, scope, steps, context) => {
7025
7211
  const through = plan.through;
7026
7212
  const link = async (operands, operation) => {
7027
7213
  const existing = new Set((await executor.select({ [through.targetColPropName]: through.targetCol }).from(through.table).where(eq(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
7028
7214
  const rows = [];
7029
7215
  for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
7030
7216
  existing.add(targetKey);
7031
- rows.push({
7217
+ rows.push(applyContextValues({
7032
7218
  [through.sourceColPropName]: key,
7033
7219
  [through.targetColPropName]: targetKey
7034
- });
7220
+ }, through.tableName ? contextValues?.(through.tableName) : void 0, context));
7035
7221
  }
7036
7222
  if (rows.length) await steps.run(executor.insert(through.table).values(rows), operation, "insert");
7037
7223
  };
7038
7224
  if (supplied(op["set"])) {
7039
- await steps.run(executor.delete(through.table).where(eq(through.sourceCol, key)), "set", "delete");
7225
+ const reachable = withScope(scope, plan.targetTableName, plan.targetTable, plan.ownCondition?.());
7226
+ const unlink = reachable ? and(eq(through.sourceCol, key), inArray(through.targetCol, executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(reachable))) : eq(through.sourceCol, key);
7227
+ await steps.run(executor.delete(through.table).where(unlink), "set", "delete");
7040
7228
  await link(asArray(op["set"]), "set");
7041
7229
  }
7042
7230
  if (supplied(op["disconnect"])) for (const filter of asArray(op["disconnect"])) {
@@ -7045,7 +7233,7 @@ const createNestedWriteRuntime = (params) => {
7045
7233
  }
7046
7234
  if (supplied(op["connect"])) await link(asArray(op["connect"]), "connect");
7047
7235
  };
7048
- const detachAll = (executor, plan, key, scope, steps, op) => steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(withScope(scope, plan.targetTableName, plan.targetTable, eq(plan.foreignCol, key))), op, "update");
7236
+ const detachAll = (executor, plan, key, scope, steps, op) => steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(withScope(scope, plan.targetTableName, plan.targetTable, and(eq(plan.foreignCol, key), plan.ownCondition?.()))), op, "update");
7049
7237
  return {
7050
7238
  enabled: (tableName) => !!plans[tableName],
7051
7239
  split: (tableName, values) => {
@@ -7072,8 +7260,24 @@ const createNestedWriteRuntime = (params) => {
7072
7260
  const plan = tablePlans[relationName];
7073
7261
  if (plan?.fkSide === "child" && !(plan.localColPropName in columns) && allColumns[plan.localColPropName]) columns[plan.localColPropName] = allColumns[plan.localColPropName];
7074
7262
  }
7263
+ for (const name of selfTargetingPlan(tablePlans, tableName, ops)?.targetPrimaryKey ?? []) if (!(name in columns) && allColumns[name]) columns[name] = allColumns[name];
7075
7264
  return columns;
7076
7265
  },
7266
+ createsParentSide: (tableName, ops) => Object.entries(ops).some(([relationName, op]) => plans[tableName]?.[relationName]?.fkSide === "parent" && supplied(op?.["create"])),
7267
+ refreshed: async (executor, tableName, ops, rows) => {
7268
+ const plan = selfTargetingPlan(plans[tableName], tableName, ops);
7269
+ const pk = plan?.targetPrimaryKey ?? [];
7270
+ if (!plan || !pk.length || !rows.length || !pk.every((name) => name in rows[0])) return rows;
7271
+ const columns = getColumns(plan.targetTable);
7272
+ const selection = Object.fromEntries(Object.keys(rows[0]).filter((name) => columns[name]).map((name) => [name, columns[name]]));
7273
+ const keyOf = (row) => pk.map((name) => String(row[name])).join("\0");
7274
+ const match = pk.length === 1 ? inArray(columns[pk[0]], rows.map((row) => row[pk[0]])) : or(...rows.map((row) => and(...pk.map((name) => eq(columns[name], row[name])))));
7275
+ const fresh = new Map((await executor.select(selection).from(plan.targetTable).where(match)).map((row) => [keyOf(row), row]));
7276
+ return rows.map((row) => ({
7277
+ ...row,
7278
+ ...fresh.get(keyOf(row))
7279
+ }));
7280
+ },
7077
7281
  applyParentSide: async (executor, tableName, ops, request) => {
7078
7282
  const tablePlans = plans[tableName];
7079
7283
  const patch = {};
@@ -7098,10 +7302,11 @@ const createNestedWriteRuntime = (params) => {
7098
7302
  patch[plan.localColPropName] = created[plan.foreignColPropName];
7099
7303
  } else if (op["connect"] !== void 0) {
7100
7304
  const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
7101
- if (rows.length !== 1) throw drizzleError(`Drizzle-GraphQL Error: 'connect' on '${relationName}' matched ${rows.length} rows — it attaches a single row, so its filter must match exactly one.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
7305
+ if (rows.length !== 1) throw connectMatchError(relationName, rows.length);
7102
7306
  patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
7103
7307
  } else if (op["disconnect"] === true) patch[plan.localColPropName] = null;
7104
7308
  }
7309
+ for (const columnName of Object.keys(contextValues?.(tableName) ?? {})) delete patch[columnName];
7105
7310
  return patch;
7106
7311
  },
7107
7312
  applyChildSide: async (executor, tableName, ops, parentRows, request) => {
@@ -7119,8 +7324,9 @@ const createNestedWriteRuntime = (params) => {
7119
7324
  for (const parentRow of parentRows) {
7120
7325
  const key = parentRow?.[plan.localColPropName];
7121
7326
  if (key === void 0 || key === null) throw drizzleError(`Drizzle-GraphQL Error: cannot write through '${relationName}': the ${tableName} row has no '${plan.localColPropName}' value to attach to.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
7327
+ if (plan.parentShows && !await plan.parentShows(executor, key)) throw drizzleError(`Drizzle-GraphQL Error: cannot write through '${relationName}': its \`where\` hides the relation on this ${tableName} row.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
7122
7328
  if (plan.through) {
7123
- await applyThroughOps(executor, plan, key, op, scope, steps);
7329
+ await applyThroughOps(executor, plan, key, op, scope, steps, context);
7124
7330
  continue;
7125
7331
  }
7126
7332
  if (op["set"] !== void 0) {
@@ -7132,14 +7338,18 @@ const createNestedWriteRuntime = (params) => {
7132
7338
  if (op["disconnect"] === true) await detachAll(executor, plan, key, scope, steps, "disconnect");
7133
7339
  } else for (const filter of asArray(op["disconnect"])) await steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(and(eq(plan.foreignCol, key), conditionOf(plan, filter, "disconnect", scope))), "disconnect", "update");
7134
7340
  }
7341
+ if (plan.isOne && op["connect"] !== void 0) {
7342
+ const matched = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
7343
+ if (matched.length !== 1) throw connectMatchError(relationName, matched.length);
7344
+ }
7135
7345
  const attachedBy = attachingOp(op);
7136
7346
  if (plan.isOne && attachedBy && plan.canDetach && op["set"] === void 0) await detachAll(executor, plan, key, scope, steps, attachedBy);
7137
7347
  if (op["connect"] !== void 0) for (const filter of asArray(op["connect"])) await steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: key }).where(conditionOf(plan, filter, "connect", scope)), "connect", "update");
7138
7348
  if (op["create"] !== void 0) {
7139
- const rows = asArray(op["create"]).map((row) => ({
7140
- ...applyContextValues(remapFromGraphQLSingleInput({ ...row }, plan.targetTable), contextValues?.(plan.targetTableName), context),
7349
+ const rows = asArray(op["create"]).map((row) => applyContextValues({
7350
+ ...remapFromGraphQLSingleInput({ ...row }, plan.targetTable),
7141
7351
  [plan.foreignColPropName]: key
7142
- }));
7352
+ }, contextValues?.(plan.targetTableName), context));
7143
7353
  if (rows.length) await steps.run(executor.insert(plan.targetTable).values(rows), "create", "insert");
7144
7354
  }
7145
7355
  }
@@ -7148,6 +7358,39 @@ const createNestedWriteRuntime = (params) => {
7148
7358
  }
7149
7359
  };
7150
7360
  };
7361
+ /** Thrown inside an entry's savepoint to undo a parent-side `create` whose parent was never written. */
7362
+ var NoParentRow = class extends Error {};
7363
+ /**
7364
+ * One parent write with its nested operations around it: the parent-side operations, whose
7365
+ * values `write` receives as a patch, then the write, then the child-side operations — and
7366
+ * the rows it returns read back when a child-side statement may have changed them.
7367
+ *
7368
+ * A parent write can come back empty: an update whose `where` matched nothing, an insert or
7369
+ * upsert that a conflict turned into a no-op. Anything a parent-side `create` inserted would
7370
+ * then be left with nothing pointing at it, so such an entry runs in a savepoint of its own
7371
+ * that is rolled back when the write produced no row, and the entry returns no rows — as it
7372
+ * would have without the nested operation. The rest of the batch is unaffected.
7373
+ *
7374
+ * `executor` must already be a transaction.
7375
+ */
7376
+ const writeNestedEntry = async (params) => {
7377
+ const { executor, runtime, tableName, ops, write, request } = params;
7378
+ const guarded = runtime.createsParentSide(tableName, ops);
7379
+ const run = async (tx) => {
7380
+ const patch = await runtime.applyParentSide(tx, tableName, ops, request);
7381
+ const rows = await write(tx, patch);
7382
+ if (guarded && !rows.length) throw new NoParentRow();
7383
+ await runtime.applyChildSide(tx, tableName, ops, rows, request);
7384
+ return runtime.refreshed(tx, tableName, ops, rows);
7385
+ };
7386
+ if (!guarded) return run(executor);
7387
+ try {
7388
+ return await executor.transaction(run);
7389
+ } catch (error) {
7390
+ if (error instanceof NoParentRow) return [];
7391
+ throw error;
7392
+ }
7393
+ };
7151
7394
  /**
7152
7395
  * Runs one parent write per input entry, with that entry's nested operations around it, all
7153
7396
  * inside a single transaction — a savepoint when the caller already runs on one, so a failing
@@ -7166,12 +7409,17 @@ const writeWithNestedOps = async (params) => {
7166
7409
  return executor.transaction(async (tx) => {
7167
7410
  const rows = [];
7168
7411
  for (const entry of entries) {
7169
- const patch = await runtime.applyParentSide(tx, tableName, entry.ops, request);
7170
- const written = await write(tx, {
7171
- ...remapValues(entry.columns),
7172
- ...patch
7412
+ const written = await writeNestedEntry({
7413
+ executor: tx,
7414
+ runtime,
7415
+ tableName,
7416
+ ops: entry.ops,
7417
+ write: (target, patch) => write(target, {
7418
+ ...remapValues(entry.columns),
7419
+ ...patch
7420
+ }),
7421
+ request
7173
7422
  });
7174
- await runtime.applyChildSide(tx, tableName, entry.ops, written, request);
7175
7423
  rows.push(...written);
7176
7424
  }
7177
7425
  return rows;
@@ -7184,15 +7432,17 @@ const writeWithNestedOps = async (params) => {
7184
7432
  */
7185
7433
  const updateWithNestedOps = async (params) => {
7186
7434
  const { executor, runtime, tableName, columns, ops, remapValues, write, request } = params;
7187
- return executor.transaction(async (tx) => {
7188
- const patch = await runtime.applyParentSide(tx, tableName, ops, request);
7189
- const rows = await write(tx, {
7435
+ return executor.transaction((tx) => writeNestedEntry({
7436
+ executor: tx,
7437
+ runtime,
7438
+ tableName,
7439
+ ops,
7440
+ write: (target, patch) => write(target, {
7190
7441
  ...remapValues(columns),
7191
7442
  ...patch
7192
- });
7193
- await runtime.applyChildSide(tx, tableName, ops, rows, request);
7194
- return rows;
7195
- });
7443
+ }),
7444
+ request
7445
+ }));
7196
7446
  };
7197
7447
  //#endregion
7198
7448
  //#region src/util/builders/errors.ts
@@ -7399,6 +7649,10 @@ const createSchemaBuilder = (adapter) => {
7399
7649
  console.warn(`Drizzle-GraphQL Warning: config.features — nestedWrites leaves out the nested inputs whose table has that write turned off: ${listed}.`);
7400
7650
  }
7401
7651
  }
7652
+ if (nestedPlans) {
7653
+ const omitted = dropContextValueRelations(nestedPlans, contextValuesOf);
7654
+ if (omitted.length) console.warn(`Drizzle-GraphQL Warning: config.contextValues — nestedWrites leaves out the relations whose foreign key the server stamps from context: ${omitted.join(", ")}.`);
7655
+ }
7402
7656
  const nestedTypes = nestedPlans ? createNestedWriteTypes({
7403
7657
  plans: nestedPlans,
7404
7658
  cacheCtx,
@@ -8170,7 +8424,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8170
8424
  serverOwned: Object.keys(contextColumns ?? {}),
8171
8425
  buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
8172
8426
  });
8173
- const runBatch = async (executor, { values: rows, plan }) => {
8427
+ const batchQuery = (executor, { values: rows, plan }) => {
8174
8428
  let query = executor.insert(table).values(rows).returning(returning);
8175
8429
  const setWhere = withScope(scope, tableName, table, plan.setWhere);
8176
8430
  query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? {
@@ -8182,12 +8436,22 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8182
8436
  set: plan.set,
8183
8437
  setWhere
8184
8438
  });
8185
- return await query;
8439
+ return query;
8186
8440
  };
8187
- if (batches.length === 1) return runBatch(target, batches[0]);
8188
- return target.transaction(async (tx) => {
8189
- const rows = [];
8190
- for (const batch of batches) rows.push(...await runBatch(tx, batch));
8441
+ if (batches.length === 1) return await batchQuery(target, batches[0]);
8442
+ return target.transaction((tx) => {
8443
+ const execute = (batch) => {
8444
+ const query = batchQuery(tx, batch);
8445
+ return typeof query.all === "function" ? query.all() : query;
8446
+ };
8447
+ const first = execute(batches[0]);
8448
+ if (typeof first?.then === "function") return (async () => {
8449
+ const rows = [...await first];
8450
+ for (const batch of batches.slice(1)) rows.push(...await execute(batch));
8451
+ return rows;
8452
+ })();
8453
+ const rows = [...first];
8454
+ for (const batch of batches.slice(1)) rows.push(...execute(batch));
8191
8455
  return rows;
8192
8456
  });
8193
8457
  };
@@ -8712,16 +8976,24 @@ const createSchemaDataGenerator = (adapter) => {
8712
8976
  const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName, request }) => executor.transaction(async (tx) => {
8713
8977
  const results = [];
8714
8978
  for (const entry of entries) {
8715
- const values = entry.ops ? {
8716
- ...entry.set,
8717
- ...await nested.applyParentSide(tx, tableName, entry.ops, request)
8718
- } : entry.set;
8719
- const writes = Object.keys(values).length > 0;
8720
- let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
8721
- if (entry.filters) query = query.where(entry.filters);
8722
- const rows = await (writes ? query.returning(returning) : query);
8723
- if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
8724
- results.push(rows);
8979
+ const write = async (target, patch) => {
8980
+ const values = {
8981
+ ...entry.set,
8982
+ ...patch
8983
+ };
8984
+ const writes = Object.keys(values).length > 0;
8985
+ let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
8986
+ if (entry.filters) query = query.where(entry.filters);
8987
+ return await (writes ? query.returning(returning) : query);
8988
+ };
8989
+ results.push(entry.ops ? await writeNestedEntry({
8990
+ executor: tx,
8991
+ runtime: nested,
8992
+ tableName,
8993
+ ops: entry.ops,
8994
+ write,
8995
+ request
8996
+ }) : await write(tx, {}));
8725
8997
  }
8726
8998
  return results;
8727
8999
  });
@@ -8853,16 +9125,24 @@ const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNest
8853
9125
  return query.returning(returning).all();
8854
9126
  };
8855
9127
  const runNestedEntry = async (tx, entry) => {
8856
- const values = entry.ops ? {
8857
- ...entry.set,
8858
- ...await nested.applyParentSide(tx, tableName, entry.ops, request)
8859
- } : entry.set;
8860
- const writes = Object.keys(values).length > 0;
8861
- let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
8862
- if (entry.filters) query = query.where(entry.filters);
8863
- const rows = await (writes ? query.returning(returning) : query);
8864
- if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
8865
- return rows;
9128
+ const write = async (target, patch) => {
9129
+ const values = {
9130
+ ...entry.set,
9131
+ ...patch
9132
+ };
9133
+ const writes = Object.keys(values).length > 0;
9134
+ let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
9135
+ if (entry.filters) query = query.where(entry.filters);
9136
+ return await (writes ? query.returning(returning) : query);
9137
+ };
9138
+ return entry.ops ? writeNestedEntry({
9139
+ executor: tx,
9140
+ runtime: nested,
9141
+ tableName,
9142
+ ops: entry.ops,
9143
+ write,
9144
+ request
9145
+ }) : write(tx, {});
8866
9146
  };
8867
9147
  return executor.transaction((tx) => {
8868
9148
  if (anyNested) return (async () => {