@vantreeseba/drizzle-graphql 11.0.2 → 12.0.0

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
@@ -544,11 +544,12 @@ const isJsonColumn = (column) => (column.dataType ?? "").includes("json") && col
544
544
  * StringArray, …) so arrays with different element types never share
545
545
  * one filter input; membership operators instead of scalar comparisons
546
546
  * - "Id" → uuid-typed columns (no string pattern operators)
547
- * - "Boolean" → boolean columns
547
+ * - "Boolean" → boolean columns (equality and membership only — no ordering, no patterns)
548
548
  * - "BigInt" → bigint columns (BigInt-scalar-typed operators, no string pattern operators)
549
549
  * - "Decimal" → numeric/decimal columns (Decimal-scalar-typed operators, no string pattern operators)
550
- * - the enum GraphQL type name → enum columns (still unique per enum)
551
- * - "DateTime" → timestamp and date columns
550
+ * - the enum GraphQL type name → enum columns (still unique per enum; equality and
551
+ * membership only, like Boolean)
552
+ * - "DateTime" → timestamp and date columns (ordering kept, no string pattern operators)
552
553
  * - "Int" → integer/serial columns (no string pattern operators)
553
554
  * - "Float" → real/double columns (no string pattern operators)
554
555
  * - "String" → all other text/varchar columns
@@ -576,7 +577,8 @@ const resolveGenericFilterDescriptor = (column, columnGraphQLType) => {
576
577
  };
577
578
  if (columnGraphQLType.type instanceof graphql.GraphQLEnumType) return {
578
579
  name: columnGraphQLType.type.name,
579
- kind: "scalar"
580
+ kind: "scalar",
581
+ isEnum: true
580
582
  };
581
583
  if (columnGraphQLType.type === GraphQLBigIntString) return {
582
584
  name: "BigInt",
@@ -800,18 +802,32 @@ const arrayFilterFields = (colType, colArr) => {
800
802
  };
801
803
  /**
802
804
  * Filters whose fields omit the string pattern operators (like/notLike/ilike/notIlike/
803
- * startsWith/contains/…): they are nonsensical on opaque uuids and invalid SQL on numeric
804
- * columns. Keyed on the generic filter name (i.e. on column type) — a string-typed column
805
- * keeps the string operators whatever it is called.
805
+ * startsWith/contains/… and the `insensitive` flag that modifies them): they are nonsensical
806
+ * on opaque uuids, booleans and enum members, invalid SQL on numeric columns, and on a
807
+ * timestamp they match whatever the session happens to render the value as rather than
808
+ * anything in the query. Keyed on the generic filter name (i.e. on column type) — a
809
+ * string-typed column keeps the string operators whatever it is called. Enum filters are
810
+ * named after their enum and are recognised by `isEnum` instead.
806
811
  */
807
812
  const FILTERS_WITHOUT_STRING_OPS = /* @__PURE__ */ new Set([
808
813
  "Id",
809
814
  "Int",
810
815
  "Float",
811
816
  "BigInt",
812
- "Decimal"
817
+ "Decimal",
818
+ "Boolean",
819
+ "Date",
820
+ "DateTime"
813
821
  ]);
814
822
  /**
823
+ * Filters that additionally omit the ordering operators (lt/lte/gt/gte). Ordering two
824
+ * booleans is `eq` spelled as a puzzle (`gt: false` is `eq: true`), and ordering enum members
825
+ * compares their declaration order — an artefact of how the column was written rather than a
826
+ * fact about the domain. Timestamps and the numerics keep them: ranges are the point of
827
+ * having them. Enum filters are recognised by `isEnum`, as above.
828
+ */
829
+ const FILTERS_WITHOUT_ORDERING_OPS = /* @__PURE__ */ new Set(["Boolean"]);
830
+ /**
815
831
  * Scalars the built-in detection itself can produce. A scalar override to one of these
816
832
  * shares the built-in filter type for that scalar, so its shape must stay exactly what a
817
833
  * natural column of the scalar gets — whichever column builds the cached type first.
@@ -854,14 +870,15 @@ const resolveOverrideFilterDescriptor = (scalar) => {
854
870
  const generateColumnFilterValues = (column, tableName, columnName, cacheCtx) => {
855
871
  const columnGraphQLType = drizzleColumnToGraphQLType(column, columnName, tableName, true, false, true);
856
872
  const inputOverride = getColumnScalarOverride(column, true);
857
- const { name: genericName, kind } = inputOverride ? resolveOverrideFilterDescriptor(inputOverride) : resolveGenericFilterDescriptor(column, columnGraphQLType);
873
+ const { name: genericName, kind, isEnum } = inputOverride ? resolveOverrideFilterDescriptor(inputOverride) : resolveGenericFilterDescriptor(column, columnGraphQLType);
858
874
  const cached = cacheCtx.genericFilterCache.get(genericName);
859
875
  if (cached) return cached;
860
876
  const colType = columnGraphQLType.type;
861
877
  const colArr = new graphql.GraphQLList(new graphql.GraphQLNonNull(colType));
862
878
  const customOverrideFilter = inputOverride !== void 0 && !BUILTIN_FILTER_SCALARS.has(inputOverride);
863
879
  const underlying = (0, drizzle_orm.extractExtendedColumnType)(column);
864
- const omitStringOps = customOverrideFilter ? underlying.type !== "string" || underlying.constraint === "numeric" : FILTERS_WITHOUT_STRING_OPS.has(genericName);
880
+ const omitStringOps = customOverrideFilter ? underlying.type !== "string" || underlying.constraint === "numeric" : isEnum || FILTERS_WITHOUT_STRING_OPS.has(genericName);
881
+ const omitOrderingOps = !customOverrideFilter && (isEnum || FILTERS_WITHOUT_ORDERING_OPS.has(genericName));
865
882
  const baseFields = kind === "json" ? jsonFilterFields(column, colType, cacheCtx.typeName) : kind === "array" ? arrayFilterFields(colType, colArr) : {
866
883
  eq: {
867
884
  type: colType,
@@ -871,21 +888,23 @@ const generateColumnFilterValues = (column, tableName, columnName, cacheCtx) =>
871
888
  type: colType,
872
889
  description: "Not equal to"
873
890
  },
874
- lt: {
875
- type: colType,
876
- description: "Less than"
877
- },
878
- lte: {
879
- type: colType,
880
- description: "Less than or equal to"
881
- },
882
- gt: {
883
- type: colType,
884
- description: "Greater than"
885
- },
886
- gte: {
887
- type: colType,
888
- description: "Greater than or equal to"
891
+ ...omitOrderingOps ? {} : {
892
+ lt: {
893
+ type: colType,
894
+ description: "Less than"
895
+ },
896
+ lte: {
897
+ type: colType,
898
+ description: "Less than or equal to"
899
+ },
900
+ gt: {
901
+ type: colType,
902
+ description: "Greater than"
903
+ },
904
+ gte: {
905
+ type: colType,
906
+ description: "Greater than or equal to"
907
+ }
889
908
  },
890
909
  ...omitStringOps ? {} : {
891
910
  like: { type: graphql.GraphQLString },
@@ -2907,6 +2926,154 @@ const listFieldComplexity = (options, policy) => ({ args, childComplexity }) =>
2907
2926
  */
2908
2927
  const aggregateFieldComplexity = (options) => ({ childComplexity }) => options.aggregateCost + childComplexity;
2909
2928
  //#endregion
2929
+ //#region src/util/parse-resolve-info.ts
2930
+ /** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
2931
+ const namedTypeOf = (type) => {
2932
+ let current = type;
2933
+ while (current?.ofType) current = current.ofType;
2934
+ return current;
2935
+ };
2936
+ /**
2937
+ * Whether a field's named output type has sub-selections. Object types and interfaces expose
2938
+ * `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
2939
+ * also expose `getFields`, but an input type can never be a field's output type, so there is
2940
+ * nothing here for that to collide with.
2941
+ */
2942
+ const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
2943
+ /**
2944
+ * The fields declared on a type, or `undefined` when it has none to declare. A union is the
2945
+ * case that matters: it has no fields of its own, so a selection directly against one resolves
2946
+ * to nothing and is dropped — only the inline fragments underneath contribute.
2947
+ */
2948
+ const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
2949
+ /**
2950
+ * The runtime variable values, in the plain-map form the directive check wants. graphql 17
2951
+ * wraps them in `{ sources, coerced }`; graphql 16 hands over the map itself.
2952
+ */
2953
+ const coercedVariables = (info) => {
2954
+ const values = info.variableValues;
2955
+ if (values && typeof values === "object" && "coerced" in values) return values.coerced ?? {};
2956
+ return values ?? {};
2957
+ };
2958
+ /** The value of a `@skip` / `@include` `if:` argument — a literal or a variable reference. */
2959
+ const directiveIfValue = (info, value) => {
2960
+ if (value.kind === graphql.Kind.VARIABLE) return coercedVariables(info)[value.name.value];
2961
+ if (value.kind === graphql.Kind.BOOLEAN) return value.value;
2962
+ };
2963
+ /**
2964
+ * Whether `@skip` / `@include` on this selection exclude it. Applies to fields, fragment
2965
+ * spreads and inline fragments alike, which is where graphql puts them.
2966
+ */
2967
+ const isExcluded = (info, node) => {
2968
+ for (const directive of node.directives ?? []) {
2969
+ const directiveName = directive.name.value;
2970
+ if (directiveName !== "skip" && directiveName !== "include") continue;
2971
+ const ifArgument = directive.arguments?.find((argument) => argument.name.value === "if");
2972
+ if (!ifArgument) continue;
2973
+ const value = directiveIfValue(info, ifArgument.value);
2974
+ if (directiveName === "skip" ? !!value : !value) return true;
2975
+ }
2976
+ return false;
2977
+ };
2978
+ /** The type a fragment's `on Type` condition names, looked up in the executing schema. */
2979
+ const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
2980
+ /**
2981
+ * Introspection meta-fields are not schema fields and never reach the database. `__typename` is
2982
+ * the one that turns up in practice; `__id` is excluded from the rule because a schema is free
2983
+ * to declare a field by that name.
2984
+ */
2985
+ const isMetaField = (name) => name.startsWith("__") && name !== "__id";
2986
+ /**
2987
+ * Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
2988
+ * level, this owns the entries under `parentType`'s name.
2989
+ *
2990
+ * `activeFragments` is the set of fragment names on the current spread path. A document that
2991
+ * passed validation cannot contain a fragment cycle, but this parser also runs on trees a
2992
+ * caller assembled by hand, and an unbounded walk would hang rather than fail.
2993
+ */
2994
+ const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
2995
+ const parentTypeName = parentType?.name;
2996
+ if (!parentTypeName) return tree;
2997
+ let fields = tree[parentTypeName];
2998
+ if (!fields) {
2999
+ fields = {};
3000
+ tree[parentTypeName] = fields;
3001
+ }
3002
+ const parentFields = fieldsOf(parentType);
3003
+ for (const selection of selections) {
3004
+ if (isExcluded(info, selection)) continue;
3005
+ if (selection.kind === graphql.Kind.FIELD) {
3006
+ const name = selection.name.value;
3007
+ if (isMetaField(name)) continue;
3008
+ const fieldDef = parentFields?.[name];
3009
+ if (!fieldDef) continue;
3010
+ const fieldType = namedTypeOf(fieldDef.type);
3011
+ if (!fieldType) continue;
3012
+ const composite = isCompositeLike(fieldType);
3013
+ const alias = selection.alias?.value || name;
3014
+ let node = fields[alias];
3015
+ if (!node) {
3016
+ node = {
3017
+ name,
3018
+ alias,
3019
+ args: { ...(0, graphql.getArgumentValues)(fieldDef, selection, info.variableValues) },
3020
+ fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
3021
+ };
3022
+ fields[alias] = node;
3023
+ }
3024
+ if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
3025
+ continue;
3026
+ }
3027
+ if (!deep) continue;
3028
+ if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
3029
+ const fragmentName = selection.name.value;
3030
+ const fragment = info.fragments?.[fragmentName];
3031
+ if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
3032
+ if (activeFragments.has(fragmentName)) continue;
3033
+ const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
3034
+ if (!isCompositeLike(fragmentType)) continue;
3035
+ activeFragments.add(fragmentName);
3036
+ try {
3037
+ collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
3038
+ } finally {
3039
+ activeFragments.delete(fragmentName);
3040
+ }
3041
+ continue;
3042
+ }
3043
+ if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
3044
+ const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
3045
+ if (!isCompositeLike(fragmentType)) continue;
3046
+ collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
3047
+ }
3048
+ }
3049
+ return tree;
3050
+ };
3051
+ /** The first key of an object, without materializing the whole key array to discard it. */
3052
+ const firstKey = (object) => {
3053
+ for (const key in object) return key;
3054
+ };
3055
+ /**
3056
+ * Reads the selection under a resolver's own field out of its `info`, as a tree of
3057
+ * {@link ResolveTree} nodes.
3058
+ *
3059
+ * ```ts
3060
+ * const parsed = parseResolveInfo(info, { deep: true })!;
3061
+ * const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
3062
+ * ```
3063
+ *
3064
+ * Returns `undefined` when the resolver's field produced no node at all, which in practice
3065
+ * means `info` carried no field nodes.
3066
+ */
3067
+ const parseResolveInfo = (info, options = {}) => {
3068
+ const fieldNodes = info.fieldNodes;
3069
+ if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
3070
+ const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
3071
+ const typeKey = firstKey(tree);
3072
+ const fields = typeKey === void 0 ? void 0 : tree[typeKey];
3073
+ const fieldKey = fields && firstKey(fields);
3074
+ return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
3075
+ };
3076
+ //#endregion
2910
3077
  //#region src/util/builders/field-updates.ts
2911
3078
  /**
2912
3079
  * The scalars arithmetic is offered on. Decimal and BigInt transport as strings but are
@@ -3239,75 +3406,196 @@ const pruneNonEagerRelations = (relationMap, shouldEagerLoad) => {
3239
3406
  for (const [tableName, rels] of Object.entries(relationMap)) out[tableName] = Object.fromEntries(Object.entries(rels).filter(([relationName]) => shouldEagerLoad(tableName, relationName)));
3240
3407
  return out;
3241
3408
  };
3242
- const createMutationTxCtx = (options) => options ? {
3243
- timeoutMs: options.timeoutMs,
3244
- fieldNames: /* @__PURE__ */ new Set(),
3245
- states: /* @__PURE__ */ new WeakMap()
3246
- } : void 0;
3247
- const includedByDirectives = (node, variables) => {
3248
- for (const directive of node.directives ?? []) {
3249
- const name = directive.name.value;
3250
- if (name !== "skip" && name !== "include") continue;
3251
- const ifArg = directive.arguments?.find((arg) => arg.name.value === "if");
3252
- let value;
3253
- if (ifArg?.value.kind === "BooleanValue") value = ifArg.value.value;
3254
- else if (ifArg?.value.kind === "Variable") value = variables?.[ifArg.value.name.value];
3255
- if (name === "skip" && value === true) return false;
3256
- if (name === "include" && value !== true) return false;
3257
- }
3258
- return true;
3259
- };
3409
+ //#endregion
3410
+ //#region src/util/builders/common/select-runtime.ts
3260
3411
  /**
3261
- * Collects the response keys of every root mutation field the executor will run, expanding
3262
- * fragments and honoring `@skip` / `@include`. Distinct response keys (alias ?? name) are
3263
- * counted, matching graphql-js field collection, so a field selected twice under one key is
3264
- * still one resolver call. Returns `false` when the document selects a root mutation field
3265
- * this build did not generate — its completion cannot be tracked, so the caller must not
3266
- * open a transaction it might never be able to close.
3412
+ * Runs the relational-query-builder select shared by every dialect's `generateSelect*`
3413
+ * resolver: selected columns + offset/limit + aliased orderBy/where callbacks + the eager
3414
+ * `with:` relation params, then remaps the result. `single` switches between
3415
+ * findFirst/findMany (and the single path omits `limit`). The PG fallback for tables
3416
+ * without RQB support stays in pg.ts; this covers the common RQB path for all three.
3267
3417
  */
3268
- const collectRootMutationKeys = (selectionSet, fragments, variables, known, keys, visitedFragments) => {
3269
- for (const selection of selectionSet.selections) {
3270
- if (!includedByDirectives(selection, variables)) continue;
3271
- if (selection.kind === "Field") {
3272
- const name = selection.name.value;
3273
- if (name === "__typename") continue;
3274
- if (!known.has(name)) return false;
3275
- keys.add(selection.alias?.value ?? name);
3276
- } else if (selection.kind === "InlineFragment") {
3277
- if (!collectRootMutationKeys(selection.selectionSet, fragments, variables, known, keys, visitedFragments)) return false;
3278
- } else if (selection.kind === "FragmentSpread") {
3279
- const fragmentName = selection.name.value;
3280
- if (visitedFragments.has(fragmentName)) continue;
3281
- visitedFragments.add(fragmentName);
3282
- const fragment = fragments?.[fragmentName];
3283
- if (!fragment) return false;
3284
- if (!collectRootMutationKeys(fragment.selectionSet, fragments, variables, known, keys, visitedFragments)) return false;
3285
- }
3418
+ const runRelationalSelect = async (opts) => {
3419
+ const { queryBase, tables, tableName, table, relationMap, typeName, typeNameMapper, parsedInfo, offset, orderBy, where, single, filterCtx, pkNames, after, scope, deleted } = opts;
3420
+ const distinct = opts.distinct?.length ? opts.distinct : void 0;
3421
+ const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
3422
+ let cursorEntries;
3423
+ if (!single && (after != null || cursorSelected)) {
3424
+ if (after != null && distinct) throw drizzleError("'after' cannot be combined with 'distinct'.", { code: "DRIZZLE_INVALID_CURSOR" });
3425
+ const cursorObstacle = orderByCursorObstacle(orderBy);
3426
+ if (cursorObstacle) {
3427
+ if (after != null) throw drizzleError(cursorObstacle, { code: "DRIZZLE_INVALID_CURSOR" });
3428
+ } else if (!pkNames?.length) {
3429
+ if (after != null) throw drizzleError(`Table ${tableName} has no primary key, so cursor pagination cannot be used on it.`, { code: "DRIZZLE_INVALID_CURSOR" });
3430
+ } else cursorEntries = cursorOrderingEntries(orderBy, pkNames);
3286
3431
  }
3287
- return true;
3288
- };
3289
- const abortedBatchError = () => drizzleError("Drizzle-GraphQL Error: Mutation was not executed because an earlier mutation in the same request failed and the shared transaction was rolled back.", { code: "DRIZZLE_TRANSACTION_ABORTED" });
3290
- /** Opens the request-wide transaction and wires up latch, inactivity timer and cleanup. */
3291
- const openSharedTx = (db, context, txCtx, stateKey, expected) => {
3292
- let release;
3293
- let rejectLatch;
3294
- const latch = new Promise((resolve, reject) => {
3295
- release = resolve;
3296
- rejectLatch = reject;
3297
- });
3298
- let readyResolve;
3299
- let readyReject;
3300
- const ready = new Promise((resolve, reject) => {
3301
- readyResolve = resolve;
3302
- readyReject = reject;
3303
- });
3304
- ready.catch(() => {});
3305
- const canStash = context !== null && typeof context === "object" && !Object.isFrozen(context);
3306
- const settled = db.transaction(async (tx) => {
3307
- if (canStash) context[drizzleExecutorKey] = tx;
3308
- readyResolve(tx);
3309
- await latch;
3310
- });
3432
+ const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
3433
+ const needsDefaultOrder = single || offset != null || opts.limit != null;
3434
+ let distinctKeys;
3435
+ if (distinct) {
3436
+ distinctKeys = await selectDistinctKeys({
3437
+ db: opts.db,
3438
+ table,
3439
+ tableName,
3440
+ distinct,
3441
+ pkNames: pkNames ?? [],
3442
+ where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
3443
+ orderBy,
3444
+ limit: single ? 1 : opts.limit,
3445
+ offset
3446
+ });
3447
+ if (!distinctKeys.length) return single ? void 0 : [];
3448
+ }
3449
+ const params = {
3450
+ columns: extractSelectedColumnsFromTree(parsedInfo.fieldsByTypeName[typeName], table, {
3451
+ tableName,
3452
+ relationMap,
3453
+ tables,
3454
+ allRelations: filterCtx?.relationMap
3455
+ }),
3456
+ offset: distinctKeys ? void 0 : offset,
3457
+ orderBy: distinctKeys ? (aliasedTable) => [...orderBy ? extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : [], ...primaryKeyOrderExprs(aliasedTable, pkNames)] : cursorEntries ? (aliasedTable) => cursorOrderExprs(aliasedTable, cursorEntries) : orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : needsDefaultOrder && pkNames?.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0,
3458
+ where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, (0, drizzle_orm.and)(where ? extractFilters(aliasedTable, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, cursorValues ? buildCursorCondition(aliasedTable, cursorEntries, cursorValues, opts.nullOrdering ?? "nulls-smallest") : void 0), deleted) } : void 0,
3459
+ with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
3460
+ typeNameMapper,
3461
+ filterCtx,
3462
+ limits: opts.limits,
3463
+ scope,
3464
+ defaultOrderBy: opts.defaultOrderBy,
3465
+ resolveName: opts.resolveName
3466
+ }) : void 0
3467
+ };
3468
+ if (single) {
3469
+ const result = await queryBase.findFirst(params);
3470
+ return result ? remapToGraphQLSingleOutput(result, tableName, table, relationMap) : void 0;
3471
+ }
3472
+ if (cursorEntries && cursorSelected) for (const [column] of cursorEntries) params.columns[column] = true;
3473
+ params.limit = distinctKeys ? void 0 : opts.limit;
3474
+ const result = await queryBase.findMany(params);
3475
+ if (cursorEntries && cursorSelected) attachRowCursors(result, cursorEntries);
3476
+ return remapToGraphQLArrayOutput(result, tableName, table, relationMap);
3477
+ };
3478
+ /**
3479
+ * After a mutation, re-fetch the mutated rows through the relational query builder so the
3480
+ * selected relations are eagerly loaded in a single query, then merge those relations onto
3481
+ * the `.returning()` rows — making the per-field BatchLoader fallback unnecessary.
3482
+ *
3483
+ * `withParams` is the pre-computed relation selection (from extractRelationsParams); the
3484
+ * caller only invokes this when relations are actually selected, so it also gates whether
3485
+ * the PK was forced into RETURNING.
3486
+ *
3487
+ * Falls back to the original `.returning()` rows (relations then resolve via the
3488
+ * field-level BatchLoader) when the table has no RQB support, no primary key columns can be
3489
+ * determined, or the re-fetch fails. Supports single- and multi-column primary keys.
3490
+ */
3491
+ const eagerLoadMutationRelations = async (db, tableName, rows, pkNames, withParams) => {
3492
+ if (!rows.length || !pkNames.length || !withParams || !Object.keys(withParams).length) return rows;
3493
+ const queryBase = db.query?.[tableName];
3494
+ if (!queryBase) return rows;
3495
+ const keyableRows = rows.filter((row) => pkNames.every((n) => row[n] != null));
3496
+ if (!keyableRows.length) return rows;
3497
+ const pkColumns = {};
3498
+ for (const pk of pkNames) pkColumns[pk] = true;
3499
+ const relationNames = Object.keys(withParams);
3500
+ const keyOf = (row) => JSON.stringify(pkNames.map((n) => typeof row[n] === "bigint" ? row[n].toString() : row[n]));
3501
+ let whereRaw;
3502
+ if (pkNames.length === 1) {
3503
+ const pkName = pkNames[0];
3504
+ const ids = keyableRows.map((r) => r[pkName]);
3505
+ whereRaw = (aliased) => (0, drizzle_orm.inArray)(aliased[pkName], ids);
3506
+ } else whereRaw = (aliased) => {
3507
+ const lhs = drizzle_orm.sql.join(pkNames.map((n) => drizzle_orm.sql`${aliased[n]}`), drizzle_orm.sql`, `);
3508
+ const tuples = drizzle_orm.sql.join(keyableRows.map((row) => drizzle_orm.sql`(${drizzle_orm.sql.join(pkNames.map((n) => drizzle_orm.sql`${row[n]}`), drizzle_orm.sql`, `)})`), drizzle_orm.sql`, `);
3509
+ return drizzle_orm.sql`(${lhs}) in (${tuples})`;
3510
+ };
3511
+ let enriched;
3512
+ try {
3513
+ enriched = await queryBase.findMany({
3514
+ columns: pkColumns,
3515
+ where: { RAW: whereRaw },
3516
+ with: withParams
3517
+ });
3518
+ } catch (err) {
3519
+ console.warn(`[drizzle-graphql] eager-loading relations for a "${tableName}" mutation failed; falling back to lazy resolution.`, err);
3520
+ return rows;
3521
+ }
3522
+ const byKey = new Map(enriched.map((e) => [keyOf(e), e]));
3523
+ for (const row of rows) {
3524
+ const match = byKey.get(keyOf(row));
3525
+ if (!match) continue;
3526
+ for (const rel of relationNames) row[rel] = match[rel];
3527
+ }
3528
+ return rows;
3529
+ };
3530
+ const createMutationTxCtx = (options) => options ? {
3531
+ timeoutMs: options.timeoutMs,
3532
+ fieldNames: /* @__PURE__ */ new Set(),
3533
+ states: /* @__PURE__ */ new WeakMap()
3534
+ } : void 0;
3535
+ const includedByDirectives = (node, variables) => {
3536
+ for (const directive of node.directives ?? []) {
3537
+ const name = directive.name.value;
3538
+ if (name !== "skip" && name !== "include") continue;
3539
+ const ifArg = directive.arguments?.find((arg) => arg.name.value === "if");
3540
+ let value;
3541
+ if (ifArg?.value.kind === "BooleanValue") value = ifArg.value.value;
3542
+ else if (ifArg?.value.kind === "Variable") value = variables?.[ifArg.value.name.value];
3543
+ if (name === "skip" && value === true) return false;
3544
+ if (name === "include" && value !== true) return false;
3545
+ }
3546
+ return true;
3547
+ };
3548
+ /**
3549
+ * Collects the response keys of every root mutation field the executor will run, expanding
3550
+ * fragments and honoring `@skip` / `@include`. Distinct response keys (alias ?? name) are
3551
+ * counted, matching graphql-js field collection, so a field selected twice under one key is
3552
+ * still one resolver call. Returns `false` when the document selects a root mutation field
3553
+ * this build did not generate — its completion cannot be tracked, so the caller must not
3554
+ * open a transaction it might never be able to close.
3555
+ */
3556
+ const collectRootMutationKeys = (selectionSet, fragments, variables, known, keys, visitedFragments) => {
3557
+ for (const selection of selectionSet.selections) {
3558
+ if (!includedByDirectives(selection, variables)) continue;
3559
+ if (selection.kind === "Field") {
3560
+ const name = selection.name.value;
3561
+ if (name === "__typename") continue;
3562
+ if (!known.has(name)) return false;
3563
+ keys.add(selection.alias?.value ?? name);
3564
+ } else if (selection.kind === "InlineFragment") {
3565
+ if (!collectRootMutationKeys(selection.selectionSet, fragments, variables, known, keys, visitedFragments)) return false;
3566
+ } else if (selection.kind === "FragmentSpread") {
3567
+ const fragmentName = selection.name.value;
3568
+ if (visitedFragments.has(fragmentName)) continue;
3569
+ visitedFragments.add(fragmentName);
3570
+ const fragment = fragments?.[fragmentName];
3571
+ if (!fragment) return false;
3572
+ if (!collectRootMutationKeys(fragment.selectionSet, fragments, variables, known, keys, visitedFragments)) return false;
3573
+ }
3574
+ }
3575
+ return true;
3576
+ };
3577
+ const abortedBatchError = () => drizzleError("Drizzle-GraphQL Error: Mutation was not executed because an earlier mutation in the same request failed and the shared transaction was rolled back.", { code: "DRIZZLE_TRANSACTION_ABORTED" });
3578
+ /** Opens the request-wide transaction and wires up latch, inactivity timer and cleanup. */
3579
+ const openSharedTx = (db, context, txCtx, stateKey, expected) => {
3580
+ let release;
3581
+ let rejectLatch;
3582
+ const latch = new Promise((resolve, reject) => {
3583
+ release = resolve;
3584
+ rejectLatch = reject;
3585
+ });
3586
+ let readyResolve;
3587
+ let readyReject;
3588
+ const ready = new Promise((resolve, reject) => {
3589
+ readyResolve = resolve;
3590
+ readyReject = reject;
3591
+ });
3592
+ ready.catch(() => {});
3593
+ const canStash = context !== null && typeof context === "object" && !Object.isFrozen(context);
3594
+ const settled = db.transaction(async (tx) => {
3595
+ if (canStash) context[drizzleExecutorKey] = tx;
3596
+ readyResolve(tx);
3597
+ await latch;
3598
+ });
3311
3599
  let timer;
3312
3600
  const clearTimer = () => {
3313
3601
  if (timer !== void 0) {
@@ -3417,12 +3705,6 @@ const hardDeleteArg = {
3417
3705
  defaultValue: false,
3418
3706
  description: "Remove the matched rows instead of marking them deleted. Reads at `INCLUDE`, so it reaches rows that are already marked; a `scope` still confines what it can reach."
3419
3707
  };
3420
- /**
3421
- * Computes the RETURNING columns and relation selection for a mutation resolver: extracts
3422
- * the selected scalar columns, determines whether any relations were selected, and only
3423
- * then forces the primary key into the column set (so the post-mutation eager-load can
3424
- * re-key rows). Returns everything the resolver needs to decide whether to eager-load.
3425
- */
3426
3708
  const prepareMutationRelationColumns = (params) => {
3427
3709
  const { relationMap, tables, tableName, typeNameMapper, table, pkNames, parsedInfo, resolveName } = params;
3428
3710
  const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
@@ -3446,6 +3728,38 @@ const prepareMutationRelationColumns = (params) => {
3446
3728
  };
3447
3729
  };
3448
3730
  /**
3731
+ * The prologue every write resolver runs: parse the resolve info, then work out the columns
3732
+ * to return and the relations to eager-load from it. `parsedInfo` comes back too — the delete
3733
+ * resolver reads its own columns off the tree rather than taking the prepared set.
3734
+ */
3735
+ const mutationSelection = (info, params) => {
3736
+ const parsedInfo = parseResolveInfo(info, { deep: true });
3737
+ return {
3738
+ parsedInfo,
3739
+ ...prepareMutationRelationColumns({
3740
+ ...params,
3741
+ parsedInfo
3742
+ })
3743
+ };
3744
+ };
3745
+ /**
3746
+ * The `where` a write runs under: the caller's filters — required when the mutation refuses to
3747
+ * run unbounded — with the row scope ANDed on last, so a supplied filter can only narrow the
3748
+ * scope and never widen it. An out-of-scope row is simply not matched.
3749
+ *
3750
+ * `deleted` states which soft-delete mode the match reads at, for the writes that need one:
3751
+ * `restore` sees only marked rows, a hard delete has to see them too.
3752
+ */
3753
+ const scopedWhere = (params) => {
3754
+ const { scope, tableName, table, where, relationCtx, required, deleted } = params;
3755
+ return withScope(scope, tableName, table, required ? extractRequiredFilters(table, tableName, where, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0, deleted);
3756
+ };
3757
+ /**
3758
+ * The epilogue: the written rows with their selected relations attached, or the rows as they
3759
+ * came back when the selection asked for no relation at all.
3760
+ */
3761
+ const withEagerRelations = (executor, tableName, rows, pkNames, withParams, hasRelations) => hasRelations ? eagerLoadMutationRelations(executor, tableName, rows, pkNames, withParams) : rows;
3762
+ /**
3449
3763
  * The per-entry input of `update<Table>Many`: `{ where, set }`, reusing the table's
3450
3764
  * update `set` input and filter input. Shared by all three dialect builders.
3451
3765
  */
@@ -3545,8 +3859,14 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
3545
3859
  try {
3546
3860
  return await runMutation(db, context, info, txCtx, async (executor) => {
3547
3861
  const { where, set } = args;
3548
- const relationCtx = relationFilterCtx(filterCtx, tableName);
3549
- const filters = requireWhere ? extractRequiredFilters(table, tableName, where, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0;
3862
+ const filters = scopedWhere({
3863
+ scope: void 0,
3864
+ tableName,
3865
+ table,
3866
+ where,
3867
+ relationCtx: relationFilterCtx(filterCtx, tableName),
3868
+ required: requireWhere
3869
+ });
3550
3870
  let query;
3551
3871
  if (kind === "delete") query = executor.delete(table);
3552
3872
  else {
@@ -3684,260 +4004,112 @@ const relationFieldExtension = (params) => ({
3684
4004
  });
3685
4005
  /** Pulls the primary-key values out of one `values` entry; `undefined` if any are missing. */
3686
4006
  const keyFromValues = (entry, primaryKey) => {
3687
- if (!entry || typeof entry !== "object") return;
3688
- const row = {};
3689
- for (const column of primaryKey) {
3690
- const value = entry[column];
3691
- if (value === void 0 || value === null) return;
3692
- row[column] = value;
3693
- }
3694
- return row;
3695
- };
3696
- /**
3697
- * Pulls primary-key values out of a filter argument.
3698
- *
3699
- * Only top-level equality counts: `{ id: { eq } }` names one row, `{ id: { inArray } }`
3700
- * names a list, and a composite key needs `eq` on every one of its columns. A boolean
3701
- * group (`OR` / `AND` / `NOT`) or any other operator on a key column means the filter is
3702
- * not a key lookup, and the rows are reported as not identified.
3703
- */
3704
- const keysFromWhere = (where, primaryKey) => {
3705
- const none = {
3706
- rows: [],
3707
- complete: false
3708
- };
3709
- if (!where || typeof where !== "object" || Array.isArray(where)) return none;
3710
- const filters = where;
3711
- if (filters.OR || filters.AND || filters.NOT) return none;
3712
- if (primaryKey.length > 1) {
3713
- const row = {};
3714
- for (const column of primaryKey) {
3715
- const operators = filters[column];
3716
- if (!operators || typeof operators !== "object" || operators.eq === void 0) return none;
3717
- row[column] = operators.eq;
3718
- }
3719
- return {
3720
- rows: [row],
3721
- complete: Object.keys(filters).length === primaryKey.length
3722
- };
3723
- }
3724
- const column = primaryKey[0];
3725
- if (!column) return none;
3726
- const operators = filters[column];
3727
- if (!operators || typeof operators !== "object") return none;
3728
- const exact = Object.keys(filters).length === 1 && Object.keys(operators).length === 1;
3729
- if (operators.eq !== void 0) return {
3730
- rows: [{ [column]: operators.eq }],
3731
- complete: exact
3732
- };
3733
- if (Array.isArray(operators.inArray)) return {
3734
- rows: operators.inArray.map((value) => ({ [column]: value })),
3735
- complete: exact
3736
- };
3737
- return none;
3738
- };
3739
- /**
3740
- * The rows a generated field's arguments are about, as primary-key values.
3741
- *
3742
- * The row identity of a mutation lives in a different argument for every operation —
3743
- * `where` for update and delete, `values` for insert and upsert, a per-entry `where` inside
3744
- * `updates` for the batch update — and each of those has both a one-row and a many-row
3745
- * spelling. This reads whichever one applies:
3746
- *
3747
- * ```ts
3748
- * const field = info.parentType.getFields()[info.fieldName];
3749
- * const target = identifyRows(field, args);
3750
- * if (target?.complete) await invalidate(target.table, target.rows);
3751
- * ```
3752
- *
3753
- * Returns `undefined` for a field this library did not generate, or an object type. Check
3754
- * `complete` before treating `rows` as the affected set — see {@link IdentifiedRows}.
3755
- */
3756
- const identifyRows = (field, args) => {
3757
- const meta = drizzleExtension(field);
3758
- if (!isDrizzleFieldExtension(meta)) return;
3759
- const { table, primaryKey, targetArg } = meta;
3760
- const empty = {
3761
- table,
3762
- primaryKey,
3763
- rows: [],
3764
- complete: false
3765
- };
3766
- if (!primaryKey.length || !targetArg) return empty;
3767
- const target = args?.[targetArg];
3768
- if (targetArg === "values") {
3769
- const entries = Array.isArray(target) ? target : target === void 0 ? [] : [target];
3770
- const rows = entries.map((entry) => keyFromValues(entry, primaryKey));
3771
- return {
3772
- table,
3773
- primaryKey,
3774
- rows: rows.filter((row) => !!row),
3775
- complete: entries.length > 0 && rows.every(Boolean)
3776
- };
3777
- }
3778
- if (targetArg === "updates") {
3779
- if (!Array.isArray(target)) return empty;
3780
- const perEntry = target.map((entry) => keysFromWhere(entry?.where, primaryKey));
3781
- return {
3782
- table,
3783
- primaryKey,
3784
- rows: perEntry.flatMap((entry) => entry.rows),
3785
- complete: target.length > 0 && perEntry.every((entry) => entry.complete)
3786
- };
3787
- }
3788
- return {
3789
- table,
3790
- primaryKey,
3791
- ...keysFromWhere(target, primaryKey)
3792
- };
3793
- };
3794
- //#endregion
3795
- //#region src/util/parse-resolve-info.ts
3796
- /** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
3797
- const namedTypeOf = (type) => {
3798
- let current = type;
3799
- while (current?.ofType) current = current.ofType;
3800
- return current;
3801
- };
3802
- /**
3803
- * Whether a field's named output type has sub-selections. Object types and interfaces expose
3804
- * `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
3805
- * also expose `getFields`, but an input type can never be a field's output type, so there is
3806
- * nothing here for that to collide with.
3807
- */
3808
- const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
3809
- /**
3810
- * The fields declared on a type, or `undefined` when it has none to declare. A union is the
3811
- * case that matters: it has no fields of its own, so a selection directly against one resolves
3812
- * to nothing and is dropped — only the inline fragments underneath contribute.
3813
- */
3814
- const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
3815
- /**
3816
- * The runtime variable values, in the plain-map form the directive check wants. graphql 17
3817
- * wraps them in `{ sources, coerced }`; graphql 16 hands over the map itself.
3818
- */
3819
- const coercedVariables = (info) => {
3820
- const values = info.variableValues;
3821
- if (values && typeof values === "object" && "coerced" in values) return values.coerced ?? {};
3822
- return values ?? {};
3823
- };
3824
- /** The value of a `@skip` / `@include` `if:` argument — a literal or a variable reference. */
3825
- const directiveIfValue = (info, value) => {
3826
- if (value.kind === graphql.Kind.VARIABLE) return coercedVariables(info)[value.name.value];
3827
- if (value.kind === graphql.Kind.BOOLEAN) return value.value;
3828
- };
3829
- /**
3830
- * Whether `@skip` / `@include` on this selection exclude it. Applies to fields, fragment
3831
- * spreads and inline fragments alike, which is where graphql puts them.
3832
- */
3833
- const isExcluded = (info, node) => {
3834
- for (const directive of node.directives ?? []) {
3835
- const directiveName = directive.name.value;
3836
- if (directiveName !== "skip" && directiveName !== "include") continue;
3837
- const ifArgument = directive.arguments?.find((argument) => argument.name.value === "if");
3838
- if (!ifArgument) continue;
3839
- const value = directiveIfValue(info, ifArgument.value);
3840
- if (directiveName === "skip" ? !!value : !value) return true;
3841
- }
3842
- return false;
3843
- };
3844
- /** The type a fragment's `on Type` condition names, looked up in the executing schema. */
3845
- const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
3846
- /**
3847
- * Introspection meta-fields are not schema fields and never reach the database. `__typename` is
3848
- * the one that turns up in practice; `__id` is excluded from the rule because a schema is free
3849
- * to declare a field by that name.
3850
- */
3851
- const isMetaField = (name) => name.startsWith("__") && name !== "__id";
3852
- /**
3853
- * Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
3854
- * level, this owns the entries under `parentType`'s name.
3855
- *
3856
- * `activeFragments` is the set of fragment names on the current spread path. A document that
3857
- * passed validation cannot contain a fragment cycle, but this parser also runs on trees a
3858
- * caller assembled by hand, and an unbounded walk would hang rather than fail.
3859
- */
3860
- const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
3861
- const parentTypeName = parentType?.name;
3862
- if (!parentTypeName) return tree;
3863
- let fields = tree[parentTypeName];
3864
- if (!fields) {
3865
- fields = {};
3866
- tree[parentTypeName] = fields;
3867
- }
3868
- const parentFields = fieldsOf(parentType);
3869
- for (const selection of selections) {
3870
- if (isExcluded(info, selection)) continue;
3871
- if (selection.kind === graphql.Kind.FIELD) {
3872
- const name = selection.name.value;
3873
- if (isMetaField(name)) continue;
3874
- const fieldDef = parentFields?.[name];
3875
- if (!fieldDef) continue;
3876
- const fieldType = namedTypeOf(fieldDef.type);
3877
- if (!fieldType) continue;
3878
- const composite = isCompositeLike(fieldType);
3879
- const alias = selection.alias?.value || name;
3880
- let node = fields[alias];
3881
- if (!node) {
3882
- node = {
3883
- name,
3884
- alias,
3885
- args: { ...(0, graphql.getArgumentValues)(fieldDef, selection, info.variableValues) },
3886
- fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
3887
- };
3888
- fields[alias] = node;
3889
- }
3890
- if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
3891
- continue;
3892
- }
3893
- if (!deep) continue;
3894
- if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
3895
- const fragmentName = selection.name.value;
3896
- const fragment = info.fragments?.[fragmentName];
3897
- if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
3898
- if (activeFragments.has(fragmentName)) continue;
3899
- const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
3900
- if (!isCompositeLike(fragmentType)) continue;
3901
- activeFragments.add(fragmentName);
3902
- try {
3903
- collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
3904
- } finally {
3905
- activeFragments.delete(fragmentName);
3906
- }
3907
- continue;
3908
- }
3909
- if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
3910
- const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
3911
- if (!isCompositeLike(fragmentType)) continue;
3912
- collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
3913
- }
4007
+ if (!entry || typeof entry !== "object") return;
4008
+ const row = {};
4009
+ for (const column of primaryKey) {
4010
+ const value = entry[column];
4011
+ if (value === void 0 || value === null) return;
4012
+ row[column] = value;
3914
4013
  }
3915
- return tree;
4014
+ return row;
3916
4015
  };
3917
- /** The first key of an object, without materializing the whole key array to discard it. */
3918
- const firstKey = (object) => {
3919
- for (const key in object) return key;
4016
+ /**
4017
+ * Pulls primary-key values out of a filter argument.
4018
+ *
4019
+ * Only top-level equality counts: `{ id: { eq } }` names one row, `{ id: { inArray } }`
4020
+ * names a list, and a composite key needs `eq` on every one of its columns. A boolean
4021
+ * group (`OR` / `AND` / `NOT`) or any other operator on a key column means the filter is
4022
+ * not a key lookup, and the rows are reported as not identified.
4023
+ */
4024
+ const keysFromWhere = (where, primaryKey) => {
4025
+ const none = {
4026
+ rows: [],
4027
+ complete: false
4028
+ };
4029
+ if (!where || typeof where !== "object" || Array.isArray(where)) return none;
4030
+ const filters = where;
4031
+ if (filters.OR || filters.AND || filters.NOT) return none;
4032
+ if (primaryKey.length > 1) {
4033
+ const row = {};
4034
+ for (const column of primaryKey) {
4035
+ const operators = filters[column];
4036
+ if (!operators || typeof operators !== "object" || operators.eq === void 0) return none;
4037
+ row[column] = operators.eq;
4038
+ }
4039
+ return {
4040
+ rows: [row],
4041
+ complete: Object.keys(filters).length === primaryKey.length
4042
+ };
4043
+ }
4044
+ const column = primaryKey[0];
4045
+ if (!column) return none;
4046
+ const operators = filters[column];
4047
+ if (!operators || typeof operators !== "object") return none;
4048
+ const exact = Object.keys(filters).length === 1 && Object.keys(operators).length === 1;
4049
+ if (operators.eq !== void 0) return {
4050
+ rows: [{ [column]: operators.eq }],
4051
+ complete: exact
4052
+ };
4053
+ if (Array.isArray(operators.inArray)) return {
4054
+ rows: operators.inArray.map((value) => ({ [column]: value })),
4055
+ complete: exact
4056
+ };
4057
+ return none;
3920
4058
  };
3921
4059
  /**
3922
- * Reads the selection under a resolver's own field out of its `info`, as a tree of
3923
- * {@link ResolveTree} nodes.
4060
+ * The rows a generated field's arguments are about, as primary-key values.
4061
+ *
4062
+ * The row identity of a mutation lives in a different argument for every operation —
4063
+ * `where` for update and delete, `values` for insert and upsert, a per-entry `where` inside
4064
+ * `updates` for the batch update — and each of those has both a one-row and a many-row
4065
+ * spelling. This reads whichever one applies:
3924
4066
  *
3925
4067
  * ```ts
3926
- * const parsed = parseResolveInfo(info, { deep: true })!;
3927
- * const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
4068
+ * const field = info.parentType.getFields()[info.fieldName];
4069
+ * const target = identifyRows(field, args);
4070
+ * if (target?.complete) await invalidate(target.table, target.rows);
3928
4071
  * ```
3929
4072
  *
3930
- * Returns `undefined` when the resolver's field produced no node at all, which in practice
3931
- * means `info` carried no field nodes.
4073
+ * Returns `undefined` for a field this library did not generate, or an object type. Check
4074
+ * `complete` before treating `rows` as the affected set — see {@link IdentifiedRows}.
3932
4075
  */
3933
- const parseResolveInfo = (info, options = {}) => {
3934
- const fieldNodes = info.fieldNodes;
3935
- if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
3936
- const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
3937
- const typeKey = firstKey(tree);
3938
- const fields = typeKey === void 0 ? void 0 : tree[typeKey];
3939
- const fieldKey = fields && firstKey(fields);
3940
- return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
4076
+ const identifyRows = (field, args) => {
4077
+ const meta = drizzleExtension(field);
4078
+ if (!isDrizzleFieldExtension(meta)) return;
4079
+ const { table, primaryKey, targetArg } = meta;
4080
+ const empty = {
4081
+ table,
4082
+ primaryKey,
4083
+ rows: [],
4084
+ complete: false
4085
+ };
4086
+ if (!primaryKey.length || !targetArg) return empty;
4087
+ const target = args?.[targetArg];
4088
+ if (targetArg === "values") {
4089
+ const entries = Array.isArray(target) ? target : target === void 0 ? [] : [target];
4090
+ const rows = entries.map((entry) => keyFromValues(entry, primaryKey));
4091
+ return {
4092
+ table,
4093
+ primaryKey,
4094
+ rows: rows.filter((row) => !!row),
4095
+ complete: entries.length > 0 && rows.every(Boolean)
4096
+ };
4097
+ }
4098
+ if (targetArg === "updates") {
4099
+ if (!Array.isArray(target)) return empty;
4100
+ const perEntry = target.map((entry) => keysFromWhere(entry?.where, primaryKey));
4101
+ return {
4102
+ table,
4103
+ primaryKey,
4104
+ rows: perEntry.flatMap((entry) => entry.rows),
4105
+ complete: target.length > 0 && perEntry.every((entry) => entry.complete)
4106
+ };
4107
+ }
4108
+ return {
4109
+ table,
4110
+ primaryKey,
4111
+ ...keysFromWhere(target, primaryKey)
4112
+ };
3941
4113
  };
3942
4114
  //#endregion
3943
4115
  //#region src/util/builders/common/relation-resolvers.ts
@@ -4201,127 +4373,6 @@ const defineRootField = (fields, kind, name, config) => {
4201
4373
  fields[name] = config;
4202
4374
  };
4203
4375
  //#endregion
4204
- //#region src/util/builders/common/select-runtime.ts
4205
- /**
4206
- * Runs the relational-query-builder select shared by every dialect's `generateSelect*`
4207
- * resolver: selected columns + offset/limit + aliased orderBy/where callbacks + the eager
4208
- * `with:` relation params, then remaps the result. `single` switches between
4209
- * findFirst/findMany (and the single path omits `limit`). The PG fallback for tables
4210
- * without RQB support stays in pg.ts; this covers the common RQB path for all three.
4211
- */
4212
- const runRelationalSelect = async (opts) => {
4213
- const { queryBase, tables, tableName, table, relationMap, typeName, typeNameMapper, parsedInfo, offset, orderBy, where, single, filterCtx, pkNames, after, scope, deleted } = opts;
4214
- const distinct = opts.distinct?.length ? opts.distinct : void 0;
4215
- const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
4216
- let cursorEntries;
4217
- if (!single && (after != null || cursorSelected)) {
4218
- if (after != null && distinct) throw drizzleError("'after' cannot be combined with 'distinct'.", { code: "DRIZZLE_INVALID_CURSOR" });
4219
- const cursorObstacle = orderByCursorObstacle(orderBy);
4220
- if (cursorObstacle) {
4221
- if (after != null) throw drizzleError(cursorObstacle, { code: "DRIZZLE_INVALID_CURSOR" });
4222
- } else if (!pkNames?.length) {
4223
- if (after != null) throw drizzleError(`Table ${tableName} has no primary key, so cursor pagination cannot be used on it.`, { code: "DRIZZLE_INVALID_CURSOR" });
4224
- } else cursorEntries = cursorOrderingEntries(orderBy, pkNames);
4225
- }
4226
- const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
4227
- const needsDefaultOrder = single || offset != null || opts.limit != null;
4228
- let distinctKeys;
4229
- if (distinct) {
4230
- distinctKeys = await selectDistinctKeys({
4231
- db: opts.db,
4232
- table,
4233
- tableName,
4234
- distinct,
4235
- pkNames: pkNames ?? [],
4236
- where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
4237
- orderBy,
4238
- limit: single ? 1 : opts.limit,
4239
- offset
4240
- });
4241
- if (!distinctKeys.length) return single ? void 0 : [];
4242
- }
4243
- const params = {
4244
- columns: extractSelectedColumnsFromTree(parsedInfo.fieldsByTypeName[typeName], table, {
4245
- tableName,
4246
- relationMap,
4247
- tables,
4248
- allRelations: filterCtx?.relationMap
4249
- }),
4250
- offset: distinctKeys ? void 0 : offset,
4251
- orderBy: distinctKeys ? (aliasedTable) => [...orderBy ? extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : [], ...primaryKeyOrderExprs(aliasedTable, pkNames)] : cursorEntries ? (aliasedTable) => cursorOrderExprs(aliasedTable, cursorEntries) : orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : needsDefaultOrder && pkNames?.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0,
4252
- where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, (0, drizzle_orm.and)(where ? extractFilters(aliasedTable, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, cursorValues ? buildCursorCondition(aliasedTable, cursorEntries, cursorValues, opts.nullOrdering ?? "nulls-smallest") : void 0), deleted) } : void 0,
4253
- with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
4254
- typeNameMapper,
4255
- filterCtx,
4256
- limits: opts.limits,
4257
- scope,
4258
- defaultOrderBy: opts.defaultOrderBy,
4259
- resolveName: opts.resolveName
4260
- }) : void 0
4261
- };
4262
- if (single) {
4263
- const result = await queryBase.findFirst(params);
4264
- return result ? remapToGraphQLSingleOutput(result, tableName, table, relationMap) : void 0;
4265
- }
4266
- if (cursorEntries && cursorSelected) for (const [column] of cursorEntries) params.columns[column] = true;
4267
- params.limit = distinctKeys ? void 0 : opts.limit;
4268
- const result = await queryBase.findMany(params);
4269
- if (cursorEntries && cursorSelected) attachRowCursors(result, cursorEntries);
4270
- return remapToGraphQLArrayOutput(result, tableName, table, relationMap);
4271
- };
4272
- /**
4273
- * After a mutation, re-fetch the mutated rows through the relational query builder so the
4274
- * selected relations are eagerly loaded in a single query, then merge those relations onto
4275
- * the `.returning()` rows — making the per-field BatchLoader fallback unnecessary.
4276
- *
4277
- * `withParams` is the pre-computed relation selection (from extractRelationsParams); the
4278
- * caller only invokes this when relations are actually selected, so it also gates whether
4279
- * the PK was forced into RETURNING.
4280
- *
4281
- * Falls back to the original `.returning()` rows (relations then resolve via the
4282
- * field-level BatchLoader) when the table has no RQB support, no primary key columns can be
4283
- * determined, or the re-fetch fails. Supports single- and multi-column primary keys.
4284
- */
4285
- const eagerLoadMutationRelations = async (db, tableName, rows, pkNames, withParams) => {
4286
- if (!rows.length || !pkNames.length || !withParams || !Object.keys(withParams).length) return rows;
4287
- const queryBase = db.query?.[tableName];
4288
- if (!queryBase) return rows;
4289
- const keyableRows = rows.filter((row) => pkNames.every((n) => row[n] != null));
4290
- if (!keyableRows.length) return rows;
4291
- const pkColumns = {};
4292
- for (const pk of pkNames) pkColumns[pk] = true;
4293
- const relationNames = Object.keys(withParams);
4294
- const keyOf = (row) => JSON.stringify(pkNames.map((n) => typeof row[n] === "bigint" ? row[n].toString() : row[n]));
4295
- let whereRaw;
4296
- if (pkNames.length === 1) {
4297
- const pkName = pkNames[0];
4298
- const ids = keyableRows.map((r) => r[pkName]);
4299
- whereRaw = (aliased) => (0, drizzle_orm.inArray)(aliased[pkName], ids);
4300
- } else whereRaw = (aliased) => {
4301
- const lhs = drizzle_orm.sql.join(pkNames.map((n) => drizzle_orm.sql`${aliased[n]}`), drizzle_orm.sql`, `);
4302
- const tuples = drizzle_orm.sql.join(keyableRows.map((row) => drizzle_orm.sql`(${drizzle_orm.sql.join(pkNames.map((n) => drizzle_orm.sql`${row[n]}`), drizzle_orm.sql`, `)})`), drizzle_orm.sql`, `);
4303
- return drizzle_orm.sql`(${lhs}) in (${tuples})`;
4304
- };
4305
- let enriched;
4306
- try {
4307
- enriched = await queryBase.findMany({
4308
- columns: pkColumns,
4309
- where: { RAW: whereRaw },
4310
- with: withParams
4311
- });
4312
- } catch (err) {
4313
- console.warn(`[drizzle-graphql] eager-loading relations for a "${tableName}" mutation failed; falling back to lazy resolution.`, err);
4314
- return rows;
4315
- }
4316
- const byKey = new Map(enriched.map((e) => [keyOf(e), e]));
4317
- for (const row of rows) {
4318
- const match = byKey.get(keyOf(row));
4319
- if (!match) continue;
4320
- for (const rel of relationNames) row[rel] = match[rel];
4321
- }
4322
- return rows;
4323
- };
4324
- //#endregion
4325
4376
  //#region src/util/builders/common/table-inputs.ts
4326
4377
  const orderMap = /* @__PURE__ */ new WeakMap();
4327
4378
  const generateTableOrderCached = (table, innerOrder) => {
@@ -6607,7 +6658,14 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
6607
6658
  if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
6608
6659
  await before();
6609
6660
  const relationCtx = relationFilterCtx(filterCtx, tableName);
6610
- const filters = withScope(scope, tableName, table, single || requireWhere ? extractRequiredFilters(table, tableName, where, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0);
6661
+ const filters = scopedWhere({
6662
+ scope,
6663
+ tableName,
6664
+ table,
6665
+ where,
6666
+ relationCtx,
6667
+ required: single || requireWhere
6668
+ });
6611
6669
  if (single) await assertSingleMatch(executor, table, filters);
6612
6670
  let query = executor.update(table).set(input);
6613
6671
  if (filters) query = query.where(filters);
@@ -6689,7 +6747,15 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
6689
6747
  const hard = canHardDelete && args.hard === true;
6690
6748
  const scope = policies?.scope?.(context);
6691
6749
  const relationCtx = relationFilterCtx(filterCtx, tableName);
6692
- const filters = withScope(scope, tableName, table, single || requireWhere ? extractRequiredFilters(table, tableName, where, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0, restore ? "ONLY" : hard ? "INCLUDE" : void 0);
6750
+ const filters = scopedWhere({
6751
+ scope,
6752
+ tableName,
6753
+ table,
6754
+ where,
6755
+ relationCtx,
6756
+ required: single || requireWhere,
6757
+ deleted: restore ? "ONLY" : hard ? "INCLUDE" : void 0
6758
+ });
6693
6759
  if (single) await assertSingleMatch(executor, table, filters);
6694
6760
  let query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
6695
6761
  if (filters) query = query.where(filters);
@@ -6917,8 +6983,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
6917
6983
  const contextColumns = policies?.contextValues?.(tableName);
6918
6984
  const scope = policies?.scope?.(context);
6919
6985
  const input = nestedEntries ? [] : applyContextValuesAll(remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : args.values, table), contextColumns, context);
6920
- const parsedInfo = parseResolveInfo(info, { deep: true });
6921
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6986
+ const { columns, hasRelations, withParams } = mutationSelection(info, {
6922
6987
  relationMap,
6923
6988
  tables,
6924
6989
  tableName,
@@ -6927,7 +6992,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
6927
6992
  resolveName,
6928
6993
  table,
6929
6994
  pkNames,
6930
- parsedInfo,
6931
6995
  limits,
6932
6996
  scope,
6933
6997
  defaultOrderBy: policies?.defaultOrderBy
@@ -6948,7 +7012,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
6948
7012
  context
6949
7013
  }) : await runInsert(executor, input);
6950
7014
  await after(result);
6951
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
7015
+ const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
6952
7016
  return remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
6953
7017
  }
6954
7018
  });
@@ -6972,8 +7036,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
6972
7036
  const contextColumns = policies?.contextValues?.(tableName);
6973
7037
  const scope = policies?.scope?.(context);
6974
7038
  const input = nestedEntry ? {} : applyContextValues(remapFromGraphQLSingleInput(entry ? entry.columns : args.values, table), contextColumns, context);
6975
- const parsedInfo = parseResolveInfo(info, { deep: true });
6976
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7039
+ const { columns, hasRelations, withParams } = mutationSelection(info, {
6977
7040
  relationMap,
6978
7041
  tables,
6979
7042
  tableName,
@@ -6982,7 +7045,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
6982
7045
  resolveName,
6983
7046
  table,
6984
7047
  pkNames,
6985
- parsedInfo,
6986
7048
  limits,
6987
7049
  scope,
6988
7050
  defaultOrderBy: policies?.defaultOrderBy
@@ -7007,7 +7069,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7007
7069
  if (!conflictDoNothing) throw drizzleError("The insert returned no row.", { code: "DRIZZLE_NO_ROW_RETURNED" });
7008
7070
  return;
7009
7071
  }
7010
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
7072
+ const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
7011
7073
  return remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap);
7012
7074
  }
7013
7075
  });
@@ -7045,8 +7107,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7045
7107
  const contextColumns = policies?.contextValues?.(tableName);
7046
7108
  const scope = policies?.scope?.(context);
7047
7109
  const input = nestedEntries ? [] : applyContextValuesAll(remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : supplied, table), contextColumns, context);
7048
- const parsedInfo = parseResolveInfo(info, { deep: true });
7049
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7110
+ const { columns, hasRelations, withParams } = mutationSelection(info, {
7050
7111
  relationMap,
7051
7112
  tables,
7052
7113
  tableName,
@@ -7055,7 +7116,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7055
7116
  resolveName,
7056
7117
  table,
7057
7118
  pkNames,
7058
- parsedInfo,
7059
7119
  limits,
7060
7120
  scope,
7061
7121
  defaultOrderBy: policies?.defaultOrderBy
@@ -7092,7 +7152,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7092
7152
  }) : await runUpsert(executor, input);
7093
7153
  await after(result);
7094
7154
  if (single && !result[0]) return;
7095
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
7155
+ const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
7096
7156
  return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
7097
7157
  }
7098
7158
  });
@@ -7116,8 +7176,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7116
7176
  const { where, set } = args;
7117
7177
  const scope = policies?.scope?.(context);
7118
7178
  await before();
7119
- const parsedInfo = parseResolveInfo(info, { deep: true });
7120
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7179
+ const { columns, hasRelations, withParams } = mutationSelection(info, {
7121
7180
  relationMap,
7122
7181
  tables,
7123
7182
  tableName,
@@ -7126,7 +7185,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7126
7185
  resolveName,
7127
7186
  table,
7128
7187
  pkNames,
7129
- parsedInfo,
7130
7188
  limits,
7131
7189
  scope,
7132
7190
  defaultOrderBy: policies?.defaultOrderBy
@@ -7136,7 +7194,14 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7136
7194
  const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
7137
7195
  if (!Object.keys(input).length && !nestedOps) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
7138
7196
  const relationCtx = relationFilterCtx(filterCtx, tableName);
7139
- const filters = withScope(scope, tableName, table, single || requireWhere ? extractRequiredFilters(table, tableName, where, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0);
7197
+ const filters = scopedWhere({
7198
+ scope,
7199
+ tableName,
7200
+ table,
7201
+ where,
7202
+ relationCtx,
7203
+ required: single || requireWhere
7204
+ });
7140
7205
  if (single) await assertSingleMatch(executor, table, filters);
7141
7206
  const returning = nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns;
7142
7207
  const runUpdate = async (target, values) => {
@@ -7158,7 +7223,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7158
7223
  await after(result);
7159
7224
  if (single && result.length > 1) throw drizzleError("'where' matched more than one row!", { code: "DRIZZLE_MULTI_ROW_MATCH" });
7160
7225
  if (single && !result[0]) return;
7161
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
7226
+ const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
7162
7227
  return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
7163
7228
  }
7164
7229
  });
@@ -7200,7 +7265,15 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
7200
7265
  const parsedInfo = parseResolveInfo(info, { deep: true });
7201
7266
  const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
7202
7267
  const relationCtx = relationFilterCtx(filterCtx, tableName);
7203
- const filters = withScope(scope, tableName, table, single || requireWhere ? extractRequiredFilters(table, tableName, where, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0, restore ? "ONLY" : hard ? "INCLUDE" : void 0);
7268
+ const filters = scopedWhere({
7269
+ scope,
7270
+ tableName,
7271
+ table,
7272
+ where,
7273
+ relationCtx,
7274
+ required: single || requireWhere,
7275
+ deleted: restore ? "ONLY" : hard ? "INCLUDE" : void 0
7276
+ });
7204
7277
  if (single) await assertSingleMatch(executor, table, filters);
7205
7278
  let query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
7206
7279
  if (filters) query = query.where(filters);
@@ -7529,8 +7602,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
7529
7602
  await before();
7530
7603
  const scope = policies?.scope?.(context);
7531
7604
  const contextColumns = policies?.contextValues?.(tableName);
7532
- const parsedInfo = parseResolveInfo(info, { deep: true });
7533
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7605
+ const { columns, hasRelations, withParams } = mutationSelection(info, {
7534
7606
  relationMap,
7535
7607
  tables,
7536
7608
  tableName,
@@ -7539,7 +7611,6 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
7539
7611
  resolveName,
7540
7612
  table,
7541
7613
  pkNames,
7542
- parsedInfo,
7543
7614
  limits,
7544
7615
  scope,
7545
7616
  defaultOrderBy: policies?.defaultOrderBy
@@ -7568,7 +7639,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
7568
7639
  });
7569
7640
  const flatRows = perEntry.flat();
7570
7641
  await after(flatRows);
7571
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, flatRows, pkNames, withParams) : flatRows;
7642
+ const enriched = await withEagerRelations(executor, tableName, flatRows, pkNames, withParams, hasRelations);
7572
7643
  const output = [];
7573
7644
  let offset = 0;
7574
7645
  for (const rows of perEntry) {