@vantreeseba/drizzle-graphql 11.0.1 → 11.0.2

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
@@ -3768,6 +3768,154 @@ const identifyRows = (field, args) => {
3768
3768
  };
3769
3769
  };
3770
3770
  //#endregion
3771
+ //#region src/util/parse-resolve-info.ts
3772
+ /** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
3773
+ const namedTypeOf = (type) => {
3774
+ let current = type;
3775
+ while (current?.ofType) current = current.ofType;
3776
+ return current;
3777
+ };
3778
+ /**
3779
+ * Whether a field's named output type has sub-selections. Object types and interfaces expose
3780
+ * `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
3781
+ * also expose `getFields`, but an input type can never be a field's output type, so there is
3782
+ * nothing here for that to collide with.
3783
+ */
3784
+ const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
3785
+ /**
3786
+ * The fields declared on a type, or `undefined` when it has none to declare. A union is the
3787
+ * case that matters: it has no fields of its own, so a selection directly against one resolves
3788
+ * to nothing and is dropped — only the inline fragments underneath contribute.
3789
+ */
3790
+ const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
3791
+ /**
3792
+ * The runtime variable values, in the plain-map form the directive check wants. graphql 17
3793
+ * wraps them in `{ sources, coerced }`; graphql 16 hands over the map itself.
3794
+ */
3795
+ const coercedVariables = (info) => {
3796
+ const values = info.variableValues;
3797
+ if (values && typeof values === "object" && "coerced" in values) return values.coerced ?? {};
3798
+ return values ?? {};
3799
+ };
3800
+ /** The value of a `@skip` / `@include` `if:` argument — a literal or a variable reference. */
3801
+ const directiveIfValue = (info, value) => {
3802
+ if (value.kind === Kind.VARIABLE) return coercedVariables(info)[value.name.value];
3803
+ if (value.kind === Kind.BOOLEAN) return value.value;
3804
+ };
3805
+ /**
3806
+ * Whether `@skip` / `@include` on this selection exclude it. Applies to fields, fragment
3807
+ * spreads and inline fragments alike, which is where graphql puts them.
3808
+ */
3809
+ const isExcluded = (info, node) => {
3810
+ for (const directive of node.directives ?? []) {
3811
+ const directiveName = directive.name.value;
3812
+ if (directiveName !== "skip" && directiveName !== "include") continue;
3813
+ const ifArgument = directive.arguments?.find((argument) => argument.name.value === "if");
3814
+ if (!ifArgument) continue;
3815
+ const value = directiveIfValue(info, ifArgument.value);
3816
+ if (directiveName === "skip" ? !!value : !value) return true;
3817
+ }
3818
+ return false;
3819
+ };
3820
+ /** The type a fragment's `on Type` condition names, looked up in the executing schema. */
3821
+ const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
3822
+ /**
3823
+ * Introspection meta-fields are not schema fields and never reach the database. `__typename` is
3824
+ * the one that turns up in practice; `__id` is excluded from the rule because a schema is free
3825
+ * to declare a field by that name.
3826
+ */
3827
+ const isMetaField = (name) => name.startsWith("__") && name !== "__id";
3828
+ /**
3829
+ * Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
3830
+ * level, this owns the entries under `parentType`'s name.
3831
+ *
3832
+ * `activeFragments` is the set of fragment names on the current spread path. A document that
3833
+ * passed validation cannot contain a fragment cycle, but this parser also runs on trees a
3834
+ * caller assembled by hand, and an unbounded walk would hang rather than fail.
3835
+ */
3836
+ const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
3837
+ const parentTypeName = parentType?.name;
3838
+ if (!parentTypeName) return tree;
3839
+ let fields = tree[parentTypeName];
3840
+ if (!fields) {
3841
+ fields = {};
3842
+ tree[parentTypeName] = fields;
3843
+ }
3844
+ const parentFields = fieldsOf(parentType);
3845
+ for (const selection of selections) {
3846
+ if (isExcluded(info, selection)) continue;
3847
+ if (selection.kind === Kind.FIELD) {
3848
+ const name = selection.name.value;
3849
+ if (isMetaField(name)) continue;
3850
+ const fieldDef = parentFields?.[name];
3851
+ if (!fieldDef) continue;
3852
+ const fieldType = namedTypeOf(fieldDef.type);
3853
+ if (!fieldType) continue;
3854
+ const composite = isCompositeLike(fieldType);
3855
+ const alias = selection.alias?.value || name;
3856
+ let node = fields[alias];
3857
+ if (!node) {
3858
+ node = {
3859
+ name,
3860
+ alias,
3861
+ args: { ...getArgumentValues(fieldDef, selection, info.variableValues) },
3862
+ fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
3863
+ };
3864
+ fields[alias] = node;
3865
+ }
3866
+ if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
3867
+ continue;
3868
+ }
3869
+ if (!deep) continue;
3870
+ if (selection.kind === Kind.FRAGMENT_SPREAD) {
3871
+ const fragmentName = selection.name.value;
3872
+ const fragment = info.fragments?.[fragmentName];
3873
+ if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
3874
+ if (activeFragments.has(fragmentName)) continue;
3875
+ const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
3876
+ if (!isCompositeLike(fragmentType)) continue;
3877
+ activeFragments.add(fragmentName);
3878
+ try {
3879
+ collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
3880
+ } finally {
3881
+ activeFragments.delete(fragmentName);
3882
+ }
3883
+ continue;
3884
+ }
3885
+ if (selection.kind === Kind.INLINE_FRAGMENT) {
3886
+ const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
3887
+ if (!isCompositeLike(fragmentType)) continue;
3888
+ collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
3889
+ }
3890
+ }
3891
+ return tree;
3892
+ };
3893
+ /** The first key of an object, without materializing the whole key array to discard it. */
3894
+ const firstKey = (object) => {
3895
+ for (const key in object) return key;
3896
+ };
3897
+ /**
3898
+ * Reads the selection under a resolver's own field out of its `info`, as a tree of
3899
+ * {@link ResolveTree} nodes.
3900
+ *
3901
+ * ```ts
3902
+ * const parsed = parseResolveInfo(info, { deep: true })!;
3903
+ * const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
3904
+ * ```
3905
+ *
3906
+ * Returns `undefined` when the resolver's field produced no node at all, which in practice
3907
+ * means `info` carried no field nodes.
3908
+ */
3909
+ const parseResolveInfo = (info, options = {}) => {
3910
+ const fieldNodes = info.fieldNodes;
3911
+ if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
3912
+ const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
3913
+ const typeKey = firstKey(tree);
3914
+ const fields = typeKey === void 0 ? void 0 : tree[typeKey];
3915
+ const fieldKey = fields && firstKey(fields);
3916
+ return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
3917
+ };
3918
+ //#endregion
3771
3919
  //#region src/util/builders/common/relation-resolvers.ts
3772
3920
  /**
3773
3921
  * Fetches a to-many relation with per-parent limit/offset for ALL parents in a
@@ -3780,8 +3928,8 @@ const identifyRows = (field, args) => {
3780
3928
  * Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
3781
3929
  * Returns raw rows (NOT remapped); the caller groups + remaps them.
3782
3930
  */
3783
- const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, orderCtx, whereArgs) => {
3784
- const cols = getColumns(targetTable);
3931
+ const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs) => {
3932
+ const cols = columns ?? getColumns(targetTable);
3785
3933
  const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
3786
3934
  const orderClause = orderExprs.length ? sql` order by ${sql.join(orderExprs, sql`, `)}` : sql``;
3787
3935
  const RN = "__drizzle_graphql_rn";
@@ -3827,12 +3975,39 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
3827
3975
  operation: "relation",
3828
3976
  relation: relationName
3829
3977
  };
3978
+ /**
3979
+ * The columns one batch reads: the ones the selection names, plus the ones the batch needs
3980
+ * whatever the client asked for — the key it groups the rows by, and the ordering columns a
3981
+ * row cursor is encoded from. Nested relation fields contribute their own join column
3982
+ * through the selection context, the same way the root select path narrows.
3983
+ *
3984
+ * `undefined` means "every column", which is what a resolver with no readable resolve info
3985
+ * has to fall back to.
3986
+ */
3987
+ const selectedColumns = (info, cursorEntries) => {
3988
+ const parsed = info ? parseResolveInfo(info) : void 0;
3989
+ if (!parsed) return;
3990
+ const tree = Object.assign({}, ...Object.values(parsed.fieldsByTypeName));
3991
+ const columns = extractSelectedColumnsFromTreeSQLFormat(tree, targetTable, {
3992
+ tableName: targetTableName,
3993
+ relationMap: filterCtx?.relationMap ?? {},
3994
+ tables,
3995
+ allRelations: filterCtx?.relationMap
3996
+ });
3997
+ const targetColumns = getColumns(targetTable);
3998
+ columns[foreignColPropName] = foreignCol;
3999
+ for (const [columnName] of cursorEntries ?? []) {
4000
+ const column = targetColumns[columnName];
4001
+ if (column) columns[columnName] = column;
4002
+ }
4003
+ return columns;
4004
+ };
3830
4005
  const resolve = async (parent, args, context, info) => {
3831
4006
  if (parent[relationName] !== void 0) return parent[relationName];
3832
4007
  const localValue = parent[localColPropName];
3833
4008
  if (localValue == null) return isOne ? null : [];
3834
4009
  const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
3835
- const { orderByArg, limit, distinct, cursorEntries, cursorValues, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
4010
+ const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
3836
4011
  const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
3837
4012
  const limit = applyLimitPolicy(requestedLimit, limitPolicy, errorCtx);
3838
4013
  const distinct = (args ?? {}).distinct?.length ? (args ?? {}).distinct : void 0;
@@ -3847,6 +4022,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
3847
4022
  } else cursorEntries = cursorOrderingEntries(orderByArg, targetPkNames);
3848
4023
  }
3849
4024
  const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
4025
+ const columns = selectedColumns(info, cursorEntries);
3850
4026
  const argsKey = JSON.stringify({
3851
4027
  where: whereArg ?? null,
3852
4028
  orderBy: orderByArg ?? null,
@@ -3855,7 +4031,8 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
3855
4031
  deleted: deleted ?? defaultDeleted ?? null,
3856
4032
  after: after ?? null,
3857
4033
  distinct: distinct ?? null,
3858
- cursor: cursorEntries ? 1 : 0
4034
+ cursor: cursorEntries ? 1 : 0,
4035
+ columns: columns ? Object.keys(columns).sort() : null
3859
4036
  });
3860
4037
  return {
3861
4038
  orderByArg,
@@ -3863,6 +4040,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
3863
4040
  distinct,
3864
4041
  cursorEntries,
3865
4042
  cursorValues,
4043
+ columns,
3866
4044
  loaderKey: `${tableName}::${relationName}::${argsKey}`
3867
4045
  };
3868
4046
  });
@@ -3886,9 +4064,9 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
3886
4064
  whereCondition = and(whereCondition, primaryKeyRestriction(targetTable, targetPkNames, keys));
3887
4065
  }
3888
4066
  let rows;
3889
- if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, relationFilterCtx(filterCtx, targetTableName), whereArg);
4067
+ if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, columns, relationFilterCtx(filterCtx, targetTableName), whereArg);
3890
4068
  else {
3891
- let q = executor.select().from(targetTable).where(whereCondition);
4069
+ let q = (columns ? executor.select(columns) : executor.select()).from(targetTable).where(whereCondition);
3892
4070
  const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
3893
4071
  if (orderExprs.length) q = q.orderBy(...orderExprs);
3894
4072
  rows = await q;
@@ -4903,154 +5081,6 @@ const resolveBuildConfig = (config, schema) => {
4903
5081
  };
4904
5082
  };
4905
5083
  //#endregion
4906
- //#region src/util/parse-resolve-info.ts
4907
- /** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
4908
- const namedTypeOf = (type) => {
4909
- let current = type;
4910
- while (current?.ofType) current = current.ofType;
4911
- return current;
4912
- };
4913
- /**
4914
- * Whether a field's named output type has sub-selections. Object types and interfaces expose
4915
- * `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
4916
- * also expose `getFields`, but an input type can never be a field's output type, so there is
4917
- * nothing here for that to collide with.
4918
- */
4919
- const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
4920
- /**
4921
- * The fields declared on a type, or `undefined` when it has none to declare. A union is the
4922
- * case that matters: it has no fields of its own, so a selection directly against one resolves
4923
- * to nothing and is dropped — only the inline fragments underneath contribute.
4924
- */
4925
- const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
4926
- /**
4927
- * The runtime variable values, in the plain-map form the directive check wants. graphql 17
4928
- * wraps them in `{ sources, coerced }`; graphql 16 hands over the map itself.
4929
- */
4930
- const coercedVariables = (info) => {
4931
- const values = info.variableValues;
4932
- if (values && typeof values === "object" && "coerced" in values) return values.coerced ?? {};
4933
- return values ?? {};
4934
- };
4935
- /** The value of a `@skip` / `@include` `if:` argument — a literal or a variable reference. */
4936
- const directiveIfValue = (info, value) => {
4937
- if (value.kind === Kind.VARIABLE) return coercedVariables(info)[value.name.value];
4938
- if (value.kind === Kind.BOOLEAN) return value.value;
4939
- };
4940
- /**
4941
- * Whether `@skip` / `@include` on this selection exclude it. Applies to fields, fragment
4942
- * spreads and inline fragments alike, which is where graphql puts them.
4943
- */
4944
- const isExcluded = (info, node) => {
4945
- for (const directive of node.directives ?? []) {
4946
- const directiveName = directive.name.value;
4947
- if (directiveName !== "skip" && directiveName !== "include") continue;
4948
- const ifArgument = directive.arguments?.find((argument) => argument.name.value === "if");
4949
- if (!ifArgument) continue;
4950
- const value = directiveIfValue(info, ifArgument.value);
4951
- if (directiveName === "skip" ? !!value : !value) return true;
4952
- }
4953
- return false;
4954
- };
4955
- /** The type a fragment's `on Type` condition names, looked up in the executing schema. */
4956
- const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
4957
- /**
4958
- * Introspection meta-fields are not schema fields and never reach the database. `__typename` is
4959
- * the one that turns up in practice; `__id` is excluded from the rule because a schema is free
4960
- * to declare a field by that name.
4961
- */
4962
- const isMetaField = (name) => name.startsWith("__") && name !== "__id";
4963
- /**
4964
- * Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
4965
- * level, this owns the entries under `parentType`'s name.
4966
- *
4967
- * `activeFragments` is the set of fragment names on the current spread path. A document that
4968
- * passed validation cannot contain a fragment cycle, but this parser also runs on trees a
4969
- * caller assembled by hand, and an unbounded walk would hang rather than fail.
4970
- */
4971
- const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
4972
- const parentTypeName = parentType?.name;
4973
- if (!parentTypeName) return tree;
4974
- let fields = tree[parentTypeName];
4975
- if (!fields) {
4976
- fields = {};
4977
- tree[parentTypeName] = fields;
4978
- }
4979
- const parentFields = fieldsOf(parentType);
4980
- for (const selection of selections) {
4981
- if (isExcluded(info, selection)) continue;
4982
- if (selection.kind === Kind.FIELD) {
4983
- const name = selection.name.value;
4984
- if (isMetaField(name)) continue;
4985
- const fieldDef = parentFields?.[name];
4986
- if (!fieldDef) continue;
4987
- const fieldType = namedTypeOf(fieldDef.type);
4988
- if (!fieldType) continue;
4989
- const composite = isCompositeLike(fieldType);
4990
- const alias = selection.alias?.value || name;
4991
- let node = fields[alias];
4992
- if (!node) {
4993
- node = {
4994
- name,
4995
- alias,
4996
- args: { ...getArgumentValues(fieldDef, selection, info.variableValues) },
4997
- fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
4998
- };
4999
- fields[alias] = node;
5000
- }
5001
- if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
5002
- continue;
5003
- }
5004
- if (!deep) continue;
5005
- if (selection.kind === Kind.FRAGMENT_SPREAD) {
5006
- const fragmentName = selection.name.value;
5007
- const fragment = info.fragments?.[fragmentName];
5008
- if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
5009
- if (activeFragments.has(fragmentName)) continue;
5010
- const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
5011
- if (!isCompositeLike(fragmentType)) continue;
5012
- activeFragments.add(fragmentName);
5013
- try {
5014
- collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
5015
- } finally {
5016
- activeFragments.delete(fragmentName);
5017
- }
5018
- continue;
5019
- }
5020
- if (selection.kind === Kind.INLINE_FRAGMENT) {
5021
- const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
5022
- if (!isCompositeLike(fragmentType)) continue;
5023
- collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
5024
- }
5025
- }
5026
- return tree;
5027
- };
5028
- /** The first key of an object, without materializing the whole key array to discard it. */
5029
- const firstKey = (object) => {
5030
- for (const key in object) return key;
5031
- };
5032
- /**
5033
- * Reads the selection under a resolver's own field out of its `info`, as a tree of
5034
- * {@link ResolveTree} nodes.
5035
- *
5036
- * ```ts
5037
- * const parsed = parseResolveInfo(info, { deep: true })!;
5038
- * const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
5039
- * ```
5040
- *
5041
- * Returns `undefined` when the resolver's field produced no node at all, which in practice
5042
- * means `info` carried no field nodes.
5043
- */
5044
- const parseResolveInfo = (info, options = {}) => {
5045
- const fieldNodes = info.fieldNodes;
5046
- if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
5047
- const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
5048
- const typeKey = firstKey(tree);
5049
- const fields = typeKey === void 0 ? void 0 : tree[typeKey];
5050
- const fieldKey = fields && firstKey(fields);
5051
- return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
5052
- };
5053
- //#endregion
5054
5084
  //#region src/util/builders/aggregates.ts
5055
5085
  /** Operations that aggregate over a set of column values. `count` is handled separately (whole rows). */
5056
5086
  const AGGREGATE_OPS = [
@@ -5935,7 +5965,7 @@ const createNestedWriteRuntime = (params) => {
5935
5965
  };
5936
5966
  /** The values the junction stores for the target rows one operand selects. */
5937
5967
  const selectedTargetKeys = async (executor, plan, filter, operation, scope) => {
5938
- return (await executor.select().from(plan.targetTable).where(conditionOf(plan, filter, operation, scope))).map((row) => row[plan.foreignColPropName]).filter((value) => value !== void 0 && value !== null);
5968
+ return (await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, filter, operation, scope))).map((row) => row[plan.foreignColPropName]).filter((value) => value !== void 0 && value !== null);
5939
5969
  };
5940
5970
  /**
5941
5971
  * The link half of a many-to-many write: junction rows are inserted and deleted, and both
@@ -5950,7 +5980,7 @@ const createNestedWriteRuntime = (params) => {
5950
5980
  const through = plan.through;
5951
5981
  const unlinkAll = () => executor.delete(through.table).where(eq(through.sourceCol, key));
5952
5982
  const link = async (operands, operation) => {
5953
- const existing = new Set((await executor.select().from(through.table).where(eq(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
5983
+ const existing = new Set((await executor.select({ [through.targetColPropName]: through.targetCol }).from(through.table).where(eq(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
5954
5984
  const rows = [];
5955
5985
  for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
5956
5986
  existing.add(targetKey);
@@ -6011,11 +6041,11 @@ const createNestedWriteRuntime = (params) => {
6011
6041
  assertSingleOperation(plan, op);
6012
6042
  if (op["create"] !== void 0) {
6013
6043
  const values = applyContextValues(remapFromGraphQLSingleInput({ ...op["create"] }, plan.targetTable), contextValues?.(plan.targetTableName), context);
6014
- const created = (await executor.insert(plan.targetTable).values(values).returning())[0];
6044
+ const created = (await executor.insert(plan.targetTable).values(values).returning({ [plan.foreignColPropName]: plan.foreignCol }))[0];
6015
6045
  if (!created) throw drizzleError(`Drizzle-GraphQL Error: 'create' on '${relationName}' inserted no row, so there is nothing to attach.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
6016
6046
  patch[plan.localColPropName] = created[plan.foreignColPropName];
6017
6047
  } else if (op["connect"] !== void 0) {
6018
- const rows = await executor.select().from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
6048
+ const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
6019
6049
  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" });
6020
6050
  patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
6021
6051
  } else if (op["disconnect"] === true) patch[plan.localColPropName] = null;