@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.cjs CHANGED
@@ -1259,6 +1259,39 @@ const remapToGraphQLCore = (key, value, tableName, column, relationMap) => {
1259
1259
  }
1260
1260
  return value;
1261
1261
  };
1262
+ /**
1263
+ * Where a remapped row keeps the values its columns held before remapping, by property name —
1264
+ * only for the columns remapping changed. The output form is not always reversible: a
1265
+ * microsecond timestamp read in `'string'` mode leaves as an ISO string the `DateTime` input
1266
+ * side would cut to milliseconds. A relation field correlating on the row's join columns reads
1267
+ * them from here (see `parentJoinValues`). Non-enumerable, so the row's own keys, JSON form and
1268
+ * equality are unchanged.
1269
+ */
1270
+ const RAW_COLUMN_VALUES = Symbol("drizzle-graphql.rawColumnValues");
1271
+ /**
1272
+ * The value column `key` held in `row` before remapping: `{ found: true, value }` when remapping
1273
+ * changed it, `{ found: false }` when it did not (or the row was never remapped), in which case
1274
+ * `row[key]` is still the column's own value.
1275
+ */
1276
+ const rawColumnValue = (row, key) => {
1277
+ const raw = row[RAW_COLUMN_VALUES];
1278
+ return raw && key in raw ? {
1279
+ found: true,
1280
+ value: raw[key]
1281
+ } : { found: false };
1282
+ };
1283
+ const keepRawColumnValue = (row, key, value) => {
1284
+ let raw = row[RAW_COLUMN_VALUES];
1285
+ if (!raw) {
1286
+ raw = Object.create(null);
1287
+ Object.defineProperty(row, RAW_COLUMN_VALUES, {
1288
+ value: raw,
1289
+ enumerable: false,
1290
+ configurable: true
1291
+ });
1292
+ }
1293
+ if (!(key in raw)) raw[key] = value;
1294
+ };
1262
1295
  const remapToGraphQLSingleOutput = (queryOutput, tableName, table, relationMap) => {
1263
1296
  const columns = (0, drizzle_orm.getColumns)(table);
1264
1297
  for (const [key, value] of Object.entries(queryOutput)) {
@@ -1276,7 +1309,9 @@ const remapToGraphQLSingleOutput = (queryOutput, tableName, table, relationMap)
1276
1309
  delete queryOutput[key];
1277
1310
  continue;
1278
1311
  }
1279
- queryOutput[key] = remapToGraphQLCore(key, value, tableName, column, relationMap);
1312
+ const remapped = remapToGraphQLCore(key, value, tableName, column, relationMap);
1313
+ if (column && remapped !== value) keepRawColumnValue(queryOutput, key, value);
1314
+ queryOutput[key] = remapped;
1280
1315
  }
1281
1316
  return queryOutput;
1282
1317
  };
@@ -1855,17 +1890,104 @@ const extractRelationJoinColumnPairs = (relEntry, parentTable, targetTable) => {
1855
1890
  }
1856
1891
  return pairs;
1857
1892
  };
1893
+ /** A decimal written as text, in a form `canonicalDecimal` can normalise. */
1894
+ const DECIMAL_TEXT = /^([+-]?)(\d*)(?:\.(\d*))?$/;
1895
+ /**
1896
+ * Decimal text without the zeros that do not change its value: `1.500` and `01.5` are both
1897
+ * `1.5`, as SQL compares them. Anything else (an exponent, `NaN`) is returned unchanged.
1898
+ */
1899
+ const canonicalDecimal = (value) => {
1900
+ const match = DECIMAL_TEXT.exec(value);
1901
+ if (!match || !match[2] && !match[3]) return value;
1902
+ const whole = match[2].replace(/^0+(?=\d)/, "") || "0";
1903
+ const fraction = (match[3] ?? "").replace(/0+$/, "");
1904
+ const digits = fraction ? `${whole}.${fraction}` : whole;
1905
+ return match[1] === "-" && digits !== "0" ? `-${digits}` : digits;
1906
+ };
1907
+ /** An ISO instant without trailing zeros in its fraction: `…:00.100Z` and `…:00.1Z` agree. */
1908
+ const trimIsoFraction = (iso) => iso.replace(/(\.\d*?)0+Z$/, "$1Z").replace(/\.Z$/, "Z");
1909
+ /**
1910
+ * One join value in a form both sides of a relation produce for the same SQL value. The parent's
1911
+ * key and a related row's can arrive in different JS forms — a `Date` and the text of the same
1912
+ * instant, `1.50` and `1.500` from decimals of different scale — and `String()` would tell them
1913
+ * apart (it even drops a `Date`'s milliseconds).
1914
+ */
1915
+ const joinValueKey = (value, column) => {
1916
+ if (value instanceof Date) {
1917
+ if (Number.isNaN(value.getTime())) return String(value);
1918
+ if (column && isDateOnlyColumn(column)) return dateToCalendarDate(value) ?? value.toISOString();
1919
+ return trimIsoFraction(value.toISOString());
1920
+ }
1921
+ if (typeof value === "bigint") return value.toString();
1922
+ if (value instanceof Uint8Array) return Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString("hex");
1923
+ if (typeof value === "string" && column) {
1924
+ if (isMySqlDateTimeStringColumn(column)) return trimIsoFraction(mySqlDateTimeTextToIso(value));
1925
+ if (isPgTimestampStringColumn(column)) return trimIsoFraction(pgTimestampTextToIso(value));
1926
+ if ((column.dataType ?? "") === "string numeric") return canonicalDecimal(value);
1927
+ }
1928
+ return String(value);
1929
+ };
1858
1930
  /**
1859
1931
  * A string identifying one combination of join-column values, for grouping a batch's rows by
1860
- * parent. Values are compared by their string form, as a single-column key always has been.
1932
+ * parent. `columns` are the columns the values were read from, in the same order: the parent's
1933
+ * join columns for a parent key, the target's (or the junction's) for a related row. Values the
1934
+ * database compares equal give the same key — see {@link joinValueKey}.
1935
+ */
1936
+ const joinKeyOf = (values, columns) => values.length === 1 ? joinValueKey(values[0], columns?.[0]) : JSON.stringify(values.map((value, i) => joinValueKey(value, columns?.[i])));
1937
+ /**
1938
+ * The parent row's values for the relation's join columns, as the database holds them. The row
1939
+ * a relation field resolves under has been remapped to its GraphQL form — a `Date` is an ISO
1940
+ * string by then, a `bigint` a decimal string — which can neither be bound back through every
1941
+ * column's encoder (SQLite's integer timestamp calls `getTime()` on it) nor be compared with the
1942
+ * related rows' decoded values. The values from before remapping are used when the row kept
1943
+ * them; otherwise the output form is mapped back the way an input value would be.
1944
+ */
1945
+ const parentJoinValues = (parent, pairs) => pairs.map(({ localColPropName, localCol }) => {
1946
+ const raw = rawColumnValue(parent, localColPropName);
1947
+ if (raw.found) return raw.value;
1948
+ const value = parent[localColPropName];
1949
+ if (value == null) return value;
1950
+ try {
1951
+ return remapFromGraphQLCore(value, localCol, localColPropName);
1952
+ } catch {
1953
+ return value;
1954
+ }
1955
+ });
1956
+ /**
1957
+ * At most this many bound parameters for the parent keys of one batched relation query. SQLite
1958
+ * accepts 32,766 per statement, Postgres and MySQL 65,535; the rest is left to the request's own
1959
+ * filters. A batch with more parents runs as several queries.
1960
+ */
1961
+ const MAX_PARENT_KEY_PARAMS = 1e4;
1962
+ /**
1963
+ * The parent keys of one batch split into the groups each query reads, so that no query binds
1964
+ * more than {@link MAX_PARENT_KEY_PARAMS} key values. Every parent lands in exactly one group, so
1965
+ * a per-parent result — its rows, its window, its count — is the same whichever query made it.
1966
+ */
1967
+ const parentKeyChunks = (keys, width) => {
1968
+ const size = Math.max(1, Math.floor(MAX_PARENT_KEY_PARAMS / Math.max(1, width)));
1969
+ const chunks = [];
1970
+ for (let i = 0; i < keys.length; i += size) chunks.push(keys.slice(i, i + size));
1971
+ return chunks;
1972
+ };
1973
+ /**
1974
+ * `or` over `conditions` as a balanced tree. A flat `a OR b OR c …` is parsed left-deep, and
1975
+ * SQLite refuses an expression more than 1,000 levels deep; balanced, a batch of thousands of
1976
+ * parents stays a few levels deep.
1861
1977
  */
1862
- const joinKeyOf = (values) => values.length === 1 ? String(values[0]) : JSON.stringify(values.map((value) => String(value)));
1978
+ const balancedOr = (conditions) => {
1979
+ if (conditions.length <= 2) return (0, drizzle_orm.or)(...conditions);
1980
+ const middle = Math.ceil(conditions.length / 2);
1981
+ return (0, drizzle_orm.or)(balancedOr(conditions.slice(0, middle)), balancedOr(conditions.slice(middle)));
1982
+ };
1863
1983
  /**
1864
1984
  * The rows whose `cols` hold one of `keys`: `col IN (…)` for a single join column, and an `OR`
1865
1985
  * of per-parent equalities for several — `(a = 1 AND b = 1) OR (a = 1 AND b = 2)` — which
1866
- * every dialect accepts, unlike a row-value `IN` list.
1986
+ * every dialect accepts, unlike a row-value `IN` list. The `OR` is nested as a balanced tree.
1987
+ * Callers split a large batch with {@link parentKeyChunks} first, which keeps either form within
1988
+ * the dialects' parameter limits.
1867
1989
  */
1868
- const parentKeyCondition = (cols, keys) => cols.length === 1 ? (0, drizzle_orm.inArray)(cols[0], keys.map((key) => key[0])) : (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)(...cols.map((col, i) => (0, drizzle_orm.eq)(col, key[i])))));
1990
+ const parentKeyCondition = (cols, keys) => cols.length === 1 ? (0, drizzle_orm.inArray)(cols[0], keys.map((key) => key[0])) : balancedOr(keys.map((key) => (0, drizzle_orm.and)(...cols.map((col, i) => (0, drizzle_orm.eq)(col, key[i])))));
1869
1991
  /**
1870
1992
  * The junction table and its two columns for a `.through()` (many-to-many) relation:
1871
1993
  * `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
@@ -4961,7 +5083,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4961
5083
  if (!parentTable || !targetTable) return;
4962
5084
  const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
4963
5085
  if (!joinPairs) return;
4964
- const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
5086
+ const localCols = joinPairs.map((pair) => pair.localCol);
4965
5087
  const foreignCol = joinPairs[0].foreignCol;
4966
5088
  const junction = extractJunctionColumns(relEntry);
4967
5089
  const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
@@ -5007,7 +5129,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
5007
5129
  };
5008
5130
  const resolve = async (parent, args, context, info) => {
5009
5131
  if (parent[relationName] !== void 0) return parent[relationName];
5010
- const localValues = localColPropNames.map((name) => parent[name]);
5132
+ const localValues = parentJoinValues(parent, joinPairs);
5011
5133
  if (localValues.some((value) => value == null)) return isOne ? null : [];
5012
5134
  const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
5013
5135
  const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
@@ -5051,55 +5173,60 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
5051
5173
  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" });
5052
5174
  return getOrCreateLoader(context, loaderKey, async (parentKeys) => {
5053
5175
  const executor = resolveExecutor(db, context);
5054
- const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
5176
+ const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key, localCols), key])).values()];
5055
5177
  const scope = resolveScope(policies, context, filterCtx);
5056
- let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)(parentKeyCondition(groupCols, uniqueKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
5057
- if (distinct) {
5058
- const keys = await selectDistinctKeys({
5059
- db: executor,
5060
- table: targetTable,
5061
- tableName: targetTableName,
5062
- distinct,
5063
- pkNames: targetPkNames,
5064
- where: whereCondition,
5065
- orderBy: orderByArg,
5066
- partitionBy: groupCols,
5067
- junction: junction ? {
5068
- table: junction.table,
5069
- on: (0, drizzle_orm.eq)(junction.targetCol, foreignCol),
5070
- parentKey: junction.sourceCol
5071
- } : void 0
5072
- });
5073
- if (!keys.length) return parentKeys.map(() => isOne ? null : []);
5074
- whereCondition = (0, drizzle_orm.and)(whereCondition, junction ? (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)((0, drizzle_orm.eq)(junction.sourceCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
5075
- }
5076
- const selection = junction ? {
5077
- ...columns ?? (0, drizzle_orm.getColumns)(targetTable),
5078
- [PARENT_KEY]: junction.sourceCol
5079
- } : columns;
5080
- let rows;
5081
- if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, groupCols, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName, scope), whereArg, junction);
5082
- else {
5083
- let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
5084
- const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
5085
- if (orderExprs.length) q = q.orderBy(...orderExprs);
5086
- rows = await q;
5087
- }
5088
- if (cursorEntries) attachRowCursors(rows, cursorEntries);
5089
- const keyOf = (row) => {
5090
- const key = joinKeyOf(groupKeys.map((name) => row[name]));
5091
- if (junction) delete row[PARENT_KEY];
5092
- return key;
5093
- };
5094
- if (isOne) {
5095
- const byKey = new Map(rows.map((row) => [keyOf(row), row]));
5178
+ const loadChunk = async (chunkKeys) => {
5179
+ let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.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);
5180
+ if (distinct) {
5181
+ const keys = await selectDistinctKeys({
5182
+ db: executor,
5183
+ table: targetTable,
5184
+ tableName: targetTableName,
5185
+ distinct,
5186
+ pkNames: targetPkNames,
5187
+ where: whereCondition,
5188
+ orderBy: orderByArg,
5189
+ partitionBy: groupCols,
5190
+ junction: junction ? {
5191
+ table: junction.table,
5192
+ on: (0, drizzle_orm.eq)(junction.targetCol, foreignCol),
5193
+ parentKey: junction.sourceCol
5194
+ } : void 0
5195
+ });
5196
+ if (!keys.length) return /* @__PURE__ */ new Map();
5197
+ whereCondition = (0, drizzle_orm.and)(whereCondition, junction ? (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)((0, drizzle_orm.eq)(junction.sourceCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
5198
+ }
5199
+ const selection = junction ? {
5200
+ ...columns ?? (0, drizzle_orm.getColumns)(targetTable),
5201
+ [PARENT_KEY]: junction.sourceCol
5202
+ } : columns;
5203
+ let rows;
5204
+ 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);
5205
+ else {
5206
+ let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
5207
+ const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
5208
+ if (orderExprs.length) q = q.orderBy(...orderExprs);
5209
+ rows = await q;
5210
+ }
5211
+ if (cursorEntries) attachRowCursors(rows, cursorEntries);
5212
+ const keyOf = (row) => {
5213
+ const key = joinKeyOf(groupKeys.map((name) => row[name]), groupCols);
5214
+ if (junction) delete row[PARENT_KEY];
5215
+ return key;
5216
+ };
5217
+ if (isOne) {
5218
+ const byKey = new Map(rows.map((row) => [keyOf(row), row]));
5219
+ remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
5220
+ return byKey;
5221
+ }
5222
+ const grouped = new Map(chunkKeys.map((key) => [joinKeyOf(key, localCols), []]));
5223
+ for (const row of rows) grouped.get(keyOf(row))?.push(row);
5096
5224
  remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
5097
- return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? null);
5098
- }
5099
- const grouped = new Map(uniqueKeys.map((key) => [joinKeyOf(key), []]));
5100
- for (const row of rows) grouped.get(keyOf(row))?.push(row);
5101
- remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
5102
- return parentKeys.map((key) => grouped.get(joinKeyOf(key)) ?? []);
5225
+ return grouped;
5226
+ };
5227
+ const results = /* @__PURE__ */ new Map();
5228
+ for (const chunk of parentKeyChunks(uniqueKeys, groupCols.length)) for (const [key, value] of await loadChunk(chunk)) results.set(key, value);
5229
+ return parentKeys.map((key) => results.get(joinKeyOf(key, localCols)) ?? (isOne ? null : []));
5103
5230
  }).load(localValues);
5104
5231
  };
5105
5232
  return async (parent, args, context, info) => {
@@ -6185,7 +6312,7 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6185
6312
  }
6186
6313
  return;
6187
6314
  }
6188
- const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
6315
+ const localCols = joinPairs.map((pair) => pair.localCol);
6189
6316
  const foreignCol = joinPairs[0].foreignCol;
6190
6317
  const junction = extractJunctionColumns(relEntry);
6191
6318
  if (junction === null) return;
@@ -6215,23 +6342,26 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6215
6342
  };
6216
6343
  });
6217
6344
  if (!Object.keys(request.selection).length) return {};
6218
- const localValues = localColPropNames.map((name) => parent[name]);
6345
+ const localValues = parentJoinValues(parent, joinPairs);
6219
6346
  if (localValues.some((value) => value == null)) return assembleAggregateRow({}, request, target);
6220
6347
  const whereArg = args?.where;
6221
6348
  const deleted = args?.deleted;
6222
6349
  const loader = getOrCreateLoader(context, loaderKey, async (parentKeys) => {
6223
6350
  const executor = resolveExecutor(db, context);
6224
- const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
6351
+ const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key, localCols), key])).values()];
6225
6352
  const scope = resolveScope(policies, context, filterCtx);
6226
- const whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)(parentKeyCondition(groupCols, uniqueKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
6227
6353
  const groupSelection = Object.fromEntries(groupAliases.map((alias, i) => [alias, groupCols[i]]));
6228
- const selected = executor.select({
6229
- ...groupSelection,
6230
- ...request.selection
6231
- }).from(targetTable);
6232
- const rows = await (junction ? selected.innerJoin(junction.table, (0, drizzle_orm.eq)(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(...groupCols);
6233
- const byKey = new Map(rows.map((row) => [joinKeyOf(groupAliases.map((alias) => row[alias])), row]));
6234
- return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? {});
6354
+ const byKey = /* @__PURE__ */ new Map();
6355
+ for (const chunk of parentKeyChunks(uniqueKeys, groupCols.length)) {
6356
+ const whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)(parentKeyCondition(groupCols, chunk), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
6357
+ const selected = executor.select({
6358
+ ...groupSelection,
6359
+ ...request.selection
6360
+ }).from(targetTable);
6361
+ const rows = await (junction ? selected.innerJoin(junction.table, (0, drizzle_orm.eq)(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(...groupCols);
6362
+ for (const row of rows) byKey.set(joinKeyOf(groupAliases.map((alias) => row[alias]), groupCols), row);
6363
+ }
6364
+ return parentKeys.map((key) => byKey.get(joinKeyOf(key, localCols)) ?? {});
6235
6365
  });
6236
6366
  return assembleAggregateRow(await loader.load(localValues), request, target);
6237
6367
  } catch (e) {
@@ -6540,6 +6670,20 @@ const ALL_OPS = [
6540
6670
  * accepts NULL.
6541
6671
  */
6542
6672
  const isOptionalOnInsert = (column) => !column.notNull || !!column.hasDefault || !!column.defaultFn;
6673
+ /**
6674
+ * The plan fields that carry a relation's own `where` (see {@link relationOwnCondition}).
6675
+ *
6676
+ * A `where` on the target narrows every row selection the write makes, and takes `create`
6677
+ * away: the row it inserts is attached whether or not it satisfies the `where`, so the
6678
+ * relation could come back without it. A `where` inherited from the reverse relation filters
6679
+ * the parent instead, and becomes a per-parent check.
6680
+ */
6681
+ const ownWherePlan = (relEntry, parentTable, targetTable, localCol) => {
6682
+ const condition = () => relationOwnCondition(relEntry, parentTable, targetTable, [localCol], [localCol]);
6683
+ if (!condition()) return {};
6684
+ if (!isRelationFilterReversed(relEntry.relation ?? relEntry)) return { ownCondition: () => condition() };
6685
+ return { parentShows: async (executor, key) => (await executor.select({ key: localCol }).from(parentTable).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(localCol, key), condition())).limit(1)).length > 0 };
6686
+ };
6543
6687
  /** The property name a column is stored under on its table, matched by identity. */
6544
6688
  const propNameOf = (table, column) => {
6545
6689
  if (!column) return;
@@ -6560,7 +6704,7 @@ const propNameOf = (table, column) => {
6560
6704
  * `onUnresolved` is called when the relation's columns are of a supported shape but cannot be
6561
6705
  * matched to the three tables — a drizzle-orm mismatch rather than an unsupported relation.
6562
6706
  */
6563
- const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
6707
+ const buildThroughPlan = (relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
6564
6708
  if ((0, drizzle_orm.is)(relation, drizzle_orm.One)) return;
6565
6709
  const junction = relation.throughTable;
6566
6710
  const sourceEntries = relation.through?.source;
@@ -6601,6 +6745,7 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
6601
6745
  targetCol
6602
6746
  },
6603
6747
  targetKeyColumns: { [foreignColPropName]: foreignCol },
6748
+ ...ownWherePlan(relEntry, table, targetTable, localCol),
6604
6749
  allowed: /* @__PURE__ */ new Set([
6605
6750
  "connect",
6606
6751
  "disconnect",
@@ -6615,8 +6760,9 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
6615
6760
  * rather than a foreign-key one — see {@link buildThroughPlan}.
6616
6761
  *
6617
6762
  * Skipped, and so absent from the generated inputs rather than failing at runtime:
6618
- * relations whose join columns cannot be read off the drizzle relation; and relations where
6619
- * neither join column is unique, which leaves no way to tell which side stores the key.
6763
+ * relations whose join columns cannot be read off the drizzle relation; relations joining on
6764
+ * more than one column per side; and relations where neither join column is unique, which
6765
+ * leaves no way to tell which side stores the key.
6620
6766
  */
6621
6767
  const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns, tableNameOf = () => void 0) => {
6622
6768
  const uniqueCache = /* @__PURE__ */ new Map();
@@ -6648,10 +6794,11 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
6648
6794
  if (!targetTable) continue;
6649
6795
  const omitted = `the '${relationName}' field of its nested-write create and update inputs`;
6650
6796
  if (relation.through) {
6651
- const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
6797
+ const throughPlan = buildThroughPlan(relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
6652
6798
  if (throughPlan) tablePlans[relationName] = throughPlan;
6653
6799
  continue;
6654
6800
  }
6801
+ if ((relationColumns(relation, "source")?.length ?? 0) > 1 || (relationColumns(relation, "target")?.length ?? 0) > 1) continue;
6655
6802
  const joinColumns = extractJoinColumns(relEntry, table, targetTable);
6656
6803
  if (!joinColumns) {
6657
6804
  warnUnresolvedJoin(relEntry, tableName, relationName, omitted);
@@ -6674,8 +6821,15 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
6674
6821
  else continue;
6675
6822
  const fkColumn = fkSide === "child" ? foreignCol : localCol;
6676
6823
  const targetColumns = (0, drizzle_orm.getColumns)(targetTable);
6677
- const targetKeyColumns = Object.fromEntries(resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]).map((name) => [name, targetColumns[name]]));
6824
+ const targetPrimaryKey = resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]);
6825
+ const targetKeyColumns = Object.fromEntries(targetPrimaryKey.map((name) => [name, targetColumns[name]]));
6678
6826
  targetKeyColumns[foreignColPropName] = foreignCol;
6827
+ const ownWhere = ownWherePlan(relEntry, table, targetTable, localCol);
6828
+ const ops = !fkColumn.notNull ? isOne ? [
6829
+ "create",
6830
+ "connect",
6831
+ "disconnect"
6832
+ ] : ALL_OPS : ["create", "connect"];
6679
6833
  tablePlans[relationName] = {
6680
6834
  relationName,
6681
6835
  targetTableName,
@@ -6688,11 +6842,9 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
6688
6842
  fkSide,
6689
6843
  canDetach: !fkColumn.notNull,
6690
6844
  targetKeyColumns,
6691
- allowed: new Set(!fkColumn.notNull ? isOne ? [
6692
- "create",
6693
- "connect",
6694
- "disconnect"
6695
- ] : ALL_OPS : ["create", "connect"])
6845
+ targetPrimaryKey,
6846
+ ...ownWhere,
6847
+ allowed: new Set(ownWhere.ownCondition ? ops.filter((op) => op !== "create") : ops)
6696
6848
  };
6697
6849
  }
6698
6850
  if (Object.keys(tablePlans).length) plans[tableName] = tablePlans;
@@ -6767,6 +6919,35 @@ const gateNestedWritePlans = (plans, featureOf, isExcluded) => {
6767
6919
  }
6768
6920
  return omitted;
6769
6921
  };
6922
+ /**
6923
+ * Takes out every relation whose foreign key is a `contextValues` column of the table storing
6924
+ * it — the parent's own column for a parent-side relation, the target's for a child-side one,
6925
+ * either junction column for a many-to-many.
6926
+ *
6927
+ * Every operation on such a relation writes that column: `connect`, `disconnect` and `set`
6928
+ * by definition, and a child-side `create` too, since it fills the key in from the parent row
6929
+ * rather than from context. That column is the server's to set — an insert stamps it and an
6930
+ * update never touches it — so writing it from a nested input would let a client pick, or
6931
+ * change, the owner the server assigns. Whole relations are dropped, not single inputs: no
6932
+ * operation on them is left that does not write the key.
6933
+ *
6934
+ * Returns one line per relation taken out, for the build note.
6935
+ */
6936
+ const dropContextValueRelations = (plans, contextValuesOf) => {
6937
+ const omitted = [];
6938
+ if (!contextValuesOf) return omitted;
6939
+ for (const [tableName, tablePlans] of Object.entries(plans)) {
6940
+ for (const [relationName, plan] of Object.entries(tablePlans)) {
6941
+ 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]);
6942
+ if (stamped) {
6943
+ delete tablePlans[relationName];
6944
+ omitted.push(`${tableName}.${relationName} (${stamped[0]}.${stamped[1]} is a contextValues column)`);
6945
+ }
6946
+ }
6947
+ if (!Object.keys(tablePlans).length) delete plans[tableName];
6948
+ }
6949
+ return omitted;
6950
+ };
6770
6951
  const NO_COLUMNS = /* @__PURE__ */ new Set();
6771
6952
  /**
6772
6953
  * Builds the nested-write input types for a schema.
@@ -6797,7 +6978,8 @@ const createNestedWriteTypes = (params) => {
6797
6978
  if (cached) return cached;
6798
6979
  const omitted = plan.fkSide === "child" ? plan.foreignColPropName : void 0;
6799
6980
  const contextColumns = cacheCtx.contextValuesOf?.(plan.targetTableName);
6800
- 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)]));
6981
+ const markerColumn = cacheCtx.softDeleteOf?.(plan.targetTableName)?.columnName;
6982
+ 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)]));
6801
6983
  const type = new graphql.GraphQLInputObjectType({
6802
6984
  name: cacheCtx.typeName({
6803
6985
  kind: "nestedWriteInput",
@@ -6921,6 +7103,8 @@ const createNestedWriteTypes = (params) => {
6921
7103
  * join columns the whole batch's RETURNING clause needs, without inspecting each row's write.
6922
7104
  */
6923
7105
  const mergedOps = (entries) => Object.assign({}, ...entries.map((entry) => entry.ops));
7106
+ /** A to-one `connect` whose filter did not match exactly one row. */
7107
+ 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" });
6924
7108
  const asArray = (value) => Array.isArray(value) ? value : value === void 0 ? [] : [value];
6925
7109
  const supplied = (value) => value !== void 0 && value !== null;
6926
7110
  /**
@@ -7012,6 +7196,8 @@ const registerChildSteps = (steps, plan, op) => {
7012
7196
  };
7013
7197
  /** The input that attaches a row to a to-one, which detaches whatever was attached before. */
7014
7198
  const attachingOp = (op) => op["connect"] !== void 0 ? "connect" : op["create"] !== void 0 ? "create" : void 0;
7199
+ /** The child-side relation in `ops` whose target is the parent's own table, if there is one. */
7200
+ const selfTargetingPlan = (tablePlans, tableName, ops) => Object.keys(ops).map((relationName) => tablePlans?.[relationName]).find((plan) => plan?.fkSide === "child" && !plan.through && plan.targetTableName === tableName);
7015
7201
  const createNestedWriteRuntime = (params) => {
7016
7202
  const { plans, filterCtx, policies, contextValues } = params;
7017
7203
  const onWrite = policies?.onWrite;
@@ -7022,7 +7208,7 @@ const createNestedWriteRuntime = (params) => {
7022
7208
  const conditionOf = (plan, filter, operation, scope) => {
7023
7209
  const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
7024
7210
  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" });
7025
- return withScope(scope, plan.targetTableName, plan.targetTable, condition);
7211
+ return withScope(scope, plan.targetTableName, plan.targetTable, (0, drizzle_orm.and)(condition, plan.ownCondition?.()));
7026
7212
  };
7027
7213
  const assertSingleOperation = (plan, op) => {
7028
7214
  const given = [
@@ -7045,22 +7231,24 @@ const createNestedWriteRuntime = (params) => {
7045
7231
  * error on some of them — the read and the write share the surrounding transaction, so no
7046
7232
  * other writer can slip a duplicate in between them.
7047
7233
  */
7048
- const applyThroughOps = async (executor, plan, key, op, scope, steps) => {
7234
+ const applyThroughOps = async (executor, plan, key, op, scope, steps, context) => {
7049
7235
  const through = plan.through;
7050
7236
  const link = async (operands, operation) => {
7051
7237
  const existing = new Set((await executor.select({ [through.targetColPropName]: through.targetCol }).from(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
7052
7238
  const rows = [];
7053
7239
  for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
7054
7240
  existing.add(targetKey);
7055
- rows.push({
7241
+ rows.push(applyContextValues({
7056
7242
  [through.sourceColPropName]: key,
7057
7243
  [through.targetColPropName]: targetKey
7058
- });
7244
+ }, through.tableName ? contextValues?.(through.tableName) : void 0, context));
7059
7245
  }
7060
7246
  if (rows.length) await steps.run(executor.insert(through.table).values(rows), operation, "insert");
7061
7247
  };
7062
7248
  if (supplied(op["set"])) {
7063
- await steps.run(executor.delete(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key)), "set", "delete");
7249
+ const reachable = withScope(scope, plan.targetTableName, plan.targetTable, plan.ownCondition?.());
7250
+ const unlink = reachable ? (0, drizzle_orm.and)((0, drizzle_orm.eq)(through.sourceCol, key), (0, drizzle_orm.inArray)(through.targetCol, executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(reachable))) : (0, drizzle_orm.eq)(through.sourceCol, key);
7251
+ await steps.run(executor.delete(through.table).where(unlink), "set", "delete");
7064
7252
  await link(asArray(op["set"]), "set");
7065
7253
  }
7066
7254
  if (supplied(op["disconnect"])) for (const filter of asArray(op["disconnect"])) {
@@ -7069,7 +7257,7 @@ const createNestedWriteRuntime = (params) => {
7069
7257
  }
7070
7258
  if (supplied(op["connect"])) await link(asArray(op["connect"]), "connect");
7071
7259
  };
7072
- 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, (0, drizzle_orm.eq)(plan.foreignCol, key))), op, "update");
7260
+ 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, (0, drizzle_orm.and)((0, drizzle_orm.eq)(plan.foreignCol, key), plan.ownCondition?.()))), op, "update");
7073
7261
  return {
7074
7262
  enabled: (tableName) => !!plans[tableName],
7075
7263
  split: (tableName, values) => {
@@ -7096,8 +7284,24 @@ const createNestedWriteRuntime = (params) => {
7096
7284
  const plan = tablePlans[relationName];
7097
7285
  if (plan?.fkSide === "child" && !(plan.localColPropName in columns) && allColumns[plan.localColPropName]) columns[plan.localColPropName] = allColumns[plan.localColPropName];
7098
7286
  }
7287
+ for (const name of selfTargetingPlan(tablePlans, tableName, ops)?.targetPrimaryKey ?? []) if (!(name in columns) && allColumns[name]) columns[name] = allColumns[name];
7099
7288
  return columns;
7100
7289
  },
7290
+ createsParentSide: (tableName, ops) => Object.entries(ops).some(([relationName, op]) => plans[tableName]?.[relationName]?.fkSide === "parent" && supplied(op?.["create"])),
7291
+ refreshed: async (executor, tableName, ops, rows) => {
7292
+ const plan = selfTargetingPlan(plans[tableName], tableName, ops);
7293
+ const pk = plan?.targetPrimaryKey ?? [];
7294
+ if (!plan || !pk.length || !rows.length || !pk.every((name) => name in rows[0])) return rows;
7295
+ const columns = (0, drizzle_orm.getColumns)(plan.targetTable);
7296
+ const selection = Object.fromEntries(Object.keys(rows[0]).filter((name) => columns[name]).map((name) => [name, columns[name]]));
7297
+ const keyOf = (row) => pk.map((name) => String(row[name])).join("\0");
7298
+ const match = pk.length === 1 ? (0, drizzle_orm.inArray)(columns[pk[0]], rows.map((row) => row[pk[0]])) : (0, drizzle_orm.or)(...rows.map((row) => (0, drizzle_orm.and)(...pk.map((name) => (0, drizzle_orm.eq)(columns[name], row[name])))));
7299
+ const fresh = new Map((await executor.select(selection).from(plan.targetTable).where(match)).map((row) => [keyOf(row), row]));
7300
+ return rows.map((row) => ({
7301
+ ...row,
7302
+ ...fresh.get(keyOf(row))
7303
+ }));
7304
+ },
7101
7305
  applyParentSide: async (executor, tableName, ops, request) => {
7102
7306
  const tablePlans = plans[tableName];
7103
7307
  const patch = {};
@@ -7122,10 +7326,11 @@ const createNestedWriteRuntime = (params) => {
7122
7326
  patch[plan.localColPropName] = created[plan.foreignColPropName];
7123
7327
  } else if (op["connect"] !== void 0) {
7124
7328
  const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
7125
- 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" });
7329
+ if (rows.length !== 1) throw connectMatchError(relationName, rows.length);
7126
7330
  patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
7127
7331
  } else if (op["disconnect"] === true) patch[plan.localColPropName] = null;
7128
7332
  }
7333
+ for (const columnName of Object.keys(contextValues?.(tableName) ?? {})) delete patch[columnName];
7129
7334
  return patch;
7130
7335
  },
7131
7336
  applyChildSide: async (executor, tableName, ops, parentRows, request) => {
@@ -7143,8 +7348,9 @@ const createNestedWriteRuntime = (params) => {
7143
7348
  for (const parentRow of parentRows) {
7144
7349
  const key = parentRow?.[plan.localColPropName];
7145
7350
  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" });
7351
+ 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" });
7146
7352
  if (plan.through) {
7147
- await applyThroughOps(executor, plan, key, op, scope, steps);
7353
+ await applyThroughOps(executor, plan, key, op, scope, steps, context);
7148
7354
  continue;
7149
7355
  }
7150
7356
  if (op["set"] !== void 0) {
@@ -7156,14 +7362,18 @@ const createNestedWriteRuntime = (params) => {
7156
7362
  if (op["disconnect"] === true) await detachAll(executor, plan, key, scope, steps, "disconnect");
7157
7363
  } else for (const filter of asArray(op["disconnect"])) await steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(plan.foreignCol, key), conditionOf(plan, filter, "disconnect", scope))), "disconnect", "update");
7158
7364
  }
7365
+ if (plan.isOne && op["connect"] !== void 0) {
7366
+ const matched = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
7367
+ if (matched.length !== 1) throw connectMatchError(relationName, matched.length);
7368
+ }
7159
7369
  const attachedBy = attachingOp(op);
7160
7370
  if (plan.isOne && attachedBy && plan.canDetach && op["set"] === void 0) await detachAll(executor, plan, key, scope, steps, attachedBy);
7161
7371
  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");
7162
7372
  if (op["create"] !== void 0) {
7163
- const rows = asArray(op["create"]).map((row) => ({
7164
- ...applyContextValues(remapFromGraphQLSingleInput({ ...row }, plan.targetTable), contextValues?.(plan.targetTableName), context),
7373
+ const rows = asArray(op["create"]).map((row) => applyContextValues({
7374
+ ...remapFromGraphQLSingleInput({ ...row }, plan.targetTable),
7165
7375
  [plan.foreignColPropName]: key
7166
- }));
7376
+ }, contextValues?.(plan.targetTableName), context));
7167
7377
  if (rows.length) await steps.run(executor.insert(plan.targetTable).values(rows), "create", "insert");
7168
7378
  }
7169
7379
  }
@@ -7172,6 +7382,39 @@ const createNestedWriteRuntime = (params) => {
7172
7382
  }
7173
7383
  };
7174
7384
  };
7385
+ /** Thrown inside an entry's savepoint to undo a parent-side `create` whose parent was never written. */
7386
+ var NoParentRow = class extends Error {};
7387
+ /**
7388
+ * One parent write with its nested operations around it: the parent-side operations, whose
7389
+ * values `write` receives as a patch, then the write, then the child-side operations — and
7390
+ * the rows it returns read back when a child-side statement may have changed them.
7391
+ *
7392
+ * A parent write can come back empty: an update whose `where` matched nothing, an insert or
7393
+ * upsert that a conflict turned into a no-op. Anything a parent-side `create` inserted would
7394
+ * then be left with nothing pointing at it, so such an entry runs in a savepoint of its own
7395
+ * that is rolled back when the write produced no row, and the entry returns no rows — as it
7396
+ * would have without the nested operation. The rest of the batch is unaffected.
7397
+ *
7398
+ * `executor` must already be a transaction.
7399
+ */
7400
+ const writeNestedEntry = async (params) => {
7401
+ const { executor, runtime, tableName, ops, write, request } = params;
7402
+ const guarded = runtime.createsParentSide(tableName, ops);
7403
+ const run = async (tx) => {
7404
+ const patch = await runtime.applyParentSide(tx, tableName, ops, request);
7405
+ const rows = await write(tx, patch);
7406
+ if (guarded && !rows.length) throw new NoParentRow();
7407
+ await runtime.applyChildSide(tx, tableName, ops, rows, request);
7408
+ return runtime.refreshed(tx, tableName, ops, rows);
7409
+ };
7410
+ if (!guarded) return run(executor);
7411
+ try {
7412
+ return await executor.transaction(run);
7413
+ } catch (error) {
7414
+ if (error instanceof NoParentRow) return [];
7415
+ throw error;
7416
+ }
7417
+ };
7175
7418
  /**
7176
7419
  * Runs one parent write per input entry, with that entry's nested operations around it, all
7177
7420
  * inside a single transaction — a savepoint when the caller already runs on one, so a failing
@@ -7190,12 +7433,17 @@ const writeWithNestedOps = async (params) => {
7190
7433
  return executor.transaction(async (tx) => {
7191
7434
  const rows = [];
7192
7435
  for (const entry of entries) {
7193
- const patch = await runtime.applyParentSide(tx, tableName, entry.ops, request);
7194
- const written = await write(tx, {
7195
- ...remapValues(entry.columns),
7196
- ...patch
7436
+ const written = await writeNestedEntry({
7437
+ executor: tx,
7438
+ runtime,
7439
+ tableName,
7440
+ ops: entry.ops,
7441
+ write: (target, patch) => write(target, {
7442
+ ...remapValues(entry.columns),
7443
+ ...patch
7444
+ }),
7445
+ request
7197
7446
  });
7198
- await runtime.applyChildSide(tx, tableName, entry.ops, written, request);
7199
7447
  rows.push(...written);
7200
7448
  }
7201
7449
  return rows;
@@ -7208,15 +7456,17 @@ const writeWithNestedOps = async (params) => {
7208
7456
  */
7209
7457
  const updateWithNestedOps = async (params) => {
7210
7458
  const { executor, runtime, tableName, columns, ops, remapValues, write, request } = params;
7211
- return executor.transaction(async (tx) => {
7212
- const patch = await runtime.applyParentSide(tx, tableName, ops, request);
7213
- const rows = await write(tx, {
7459
+ return executor.transaction((tx) => writeNestedEntry({
7460
+ executor: tx,
7461
+ runtime,
7462
+ tableName,
7463
+ ops,
7464
+ write: (target, patch) => write(target, {
7214
7465
  ...remapValues(columns),
7215
7466
  ...patch
7216
- });
7217
- await runtime.applyChildSide(tx, tableName, ops, rows, request);
7218
- return rows;
7219
- });
7467
+ }),
7468
+ request
7469
+ }));
7220
7470
  };
7221
7471
  //#endregion
7222
7472
  //#region src/util/builders/errors.ts
@@ -7423,6 +7673,10 @@ const createSchemaBuilder = (adapter) => {
7423
7673
  console.warn(`Drizzle-GraphQL Warning: config.features — nestedWrites leaves out the nested inputs whose table has that write turned off: ${listed}.`);
7424
7674
  }
7425
7675
  }
7676
+ if (nestedPlans) {
7677
+ const omitted = dropContextValueRelations(nestedPlans, contextValuesOf);
7678
+ 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(", ")}.`);
7679
+ }
7426
7680
  const nestedTypes = nestedPlans ? createNestedWriteTypes({
7427
7681
  plans: nestedPlans,
7428
7682
  cacheCtx,
@@ -8194,7 +8448,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8194
8448
  serverOwned: Object.keys(contextColumns ?? {}),
8195
8449
  buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
8196
8450
  });
8197
- const runBatch = async (executor, { values: rows, plan }) => {
8451
+ const batchQuery = (executor, { values: rows, plan }) => {
8198
8452
  let query = executor.insert(table).values(rows).returning(returning);
8199
8453
  const setWhere = withScope(scope, tableName, table, plan.setWhere);
8200
8454
  query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? {
@@ -8206,12 +8460,22 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
8206
8460
  set: plan.set,
8207
8461
  setWhere
8208
8462
  });
8209
- return await query;
8463
+ return query;
8210
8464
  };
8211
- if (batches.length === 1) return runBatch(target, batches[0]);
8212
- return target.transaction(async (tx) => {
8213
- const rows = [];
8214
- for (const batch of batches) rows.push(...await runBatch(tx, batch));
8465
+ if (batches.length === 1) return await batchQuery(target, batches[0]);
8466
+ return target.transaction((tx) => {
8467
+ const execute = (batch) => {
8468
+ const query = batchQuery(tx, batch);
8469
+ return typeof query.all === "function" ? query.all() : query;
8470
+ };
8471
+ const first = execute(batches[0]);
8472
+ if (typeof first?.then === "function") return (async () => {
8473
+ const rows = [...await first];
8474
+ for (const batch of batches.slice(1)) rows.push(...await execute(batch));
8475
+ return rows;
8476
+ })();
8477
+ const rows = [...first];
8478
+ for (const batch of batches.slice(1)) rows.push(...execute(batch));
8215
8479
  return rows;
8216
8480
  });
8217
8481
  };
@@ -8736,16 +9000,24 @@ const createSchemaDataGenerator = (adapter) => {
8736
9000
  const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName, request }) => executor.transaction(async (tx) => {
8737
9001
  const results = [];
8738
9002
  for (const entry of entries) {
8739
- const values = entry.ops ? {
8740
- ...entry.set,
8741
- ...await nested.applyParentSide(tx, tableName, entry.ops, request)
8742
- } : entry.set;
8743
- const writes = Object.keys(values).length > 0;
8744
- let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
8745
- if (entry.filters) query = query.where(entry.filters);
8746
- const rows = await (writes ? query.returning(returning) : query);
8747
- if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
8748
- results.push(rows);
9003
+ const write = async (target, patch) => {
9004
+ const values = {
9005
+ ...entry.set,
9006
+ ...patch
9007
+ };
9008
+ const writes = Object.keys(values).length > 0;
9009
+ let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
9010
+ if (entry.filters) query = query.where(entry.filters);
9011
+ return await (writes ? query.returning(returning) : query);
9012
+ };
9013
+ results.push(entry.ops ? await writeNestedEntry({
9014
+ executor: tx,
9015
+ runtime: nested,
9016
+ tableName,
9017
+ ops: entry.ops,
9018
+ write,
9019
+ request
9020
+ }) : await write(tx, {}));
8749
9021
  }
8750
9022
  return results;
8751
9023
  });
@@ -8877,16 +9149,24 @@ const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNest
8877
9149
  return query.returning(returning).all();
8878
9150
  };
8879
9151
  const runNestedEntry = async (tx, entry) => {
8880
- const values = entry.ops ? {
8881
- ...entry.set,
8882
- ...await nested.applyParentSide(tx, tableName, entry.ops, request)
8883
- } : entry.set;
8884
- const writes = Object.keys(values).length > 0;
8885
- let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
8886
- if (entry.filters) query = query.where(entry.filters);
8887
- const rows = await (writes ? query.returning(returning) : query);
8888
- if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
8889
- return rows;
9152
+ const write = async (target, patch) => {
9153
+ const values = {
9154
+ ...entry.set,
9155
+ ...patch
9156
+ };
9157
+ const writes = Object.keys(values).length > 0;
9158
+ let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
9159
+ if (entry.filters) query = query.where(entry.filters);
9160
+ return await (writes ? query.returning(returning) : query);
9161
+ };
9162
+ return entry.ops ? writeNestedEntry({
9163
+ executor: tx,
9164
+ runtime: nested,
9165
+ tableName,
9166
+ ops: entry.ops,
9167
+ write,
9168
+ request
9169
+ }) : write(tx, {});
8890
9170
  };
8891
9171
  return executor.transaction((tx) => {
8892
9172
  if (anyNested) return (async () => {