@vantreeseba/drizzle-graphql 11.0.1 → 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/README.md +45 -1
- package/dist/index.cjs +469 -368
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +469 -368
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -520,11 +520,12 @@ const isJsonColumn = (column) => (column.dataType ?? "").includes("json") && col
|
|
|
520
520
|
* StringArray, …) so arrays with different element types never share
|
|
521
521
|
* one filter input; membership operators instead of scalar comparisons
|
|
522
522
|
* - "Id" → uuid-typed columns (no string pattern operators)
|
|
523
|
-
* - "Boolean" → boolean columns
|
|
523
|
+
* - "Boolean" → boolean columns (equality and membership only — no ordering, no patterns)
|
|
524
524
|
* - "BigInt" → bigint columns (BigInt-scalar-typed operators, no string pattern operators)
|
|
525
525
|
* - "Decimal" → numeric/decimal columns (Decimal-scalar-typed operators, no string pattern operators)
|
|
526
|
-
* - the enum GraphQL type name → enum columns (still unique per enum
|
|
527
|
-
*
|
|
526
|
+
* - the enum GraphQL type name → enum columns (still unique per enum; equality and
|
|
527
|
+
* membership only, like Boolean)
|
|
528
|
+
* - "DateTime" → timestamp and date columns (ordering kept, no string pattern operators)
|
|
528
529
|
* - "Int" → integer/serial columns (no string pattern operators)
|
|
529
530
|
* - "Float" → real/double columns (no string pattern operators)
|
|
530
531
|
* - "String" → all other text/varchar columns
|
|
@@ -552,7 +553,8 @@ const resolveGenericFilterDescriptor = (column, columnGraphQLType) => {
|
|
|
552
553
|
};
|
|
553
554
|
if (columnGraphQLType.type instanceof GraphQLEnumType) return {
|
|
554
555
|
name: columnGraphQLType.type.name,
|
|
555
|
-
kind: "scalar"
|
|
556
|
+
kind: "scalar",
|
|
557
|
+
isEnum: true
|
|
556
558
|
};
|
|
557
559
|
if (columnGraphQLType.type === GraphQLBigIntString) return {
|
|
558
560
|
name: "BigInt",
|
|
@@ -776,18 +778,32 @@ const arrayFilterFields = (colType, colArr) => {
|
|
|
776
778
|
};
|
|
777
779
|
/**
|
|
778
780
|
* Filters whose fields omit the string pattern operators (like/notLike/ilike/notIlike/
|
|
779
|
-
* startsWith/contains/…
|
|
780
|
-
*
|
|
781
|
-
*
|
|
781
|
+
* startsWith/contains/… and the `insensitive` flag that modifies them): they are nonsensical
|
|
782
|
+
* on opaque uuids, booleans and enum members, invalid SQL on numeric columns, and on a
|
|
783
|
+
* timestamp they match whatever the session happens to render the value as rather than
|
|
784
|
+
* anything in the query. Keyed on the generic filter name (i.e. on column type) — a
|
|
785
|
+
* string-typed column keeps the string operators whatever it is called. Enum filters are
|
|
786
|
+
* named after their enum and are recognised by `isEnum` instead.
|
|
782
787
|
*/
|
|
783
788
|
const FILTERS_WITHOUT_STRING_OPS = /* @__PURE__ */ new Set([
|
|
784
789
|
"Id",
|
|
785
790
|
"Int",
|
|
786
791
|
"Float",
|
|
787
792
|
"BigInt",
|
|
788
|
-
"Decimal"
|
|
793
|
+
"Decimal",
|
|
794
|
+
"Boolean",
|
|
795
|
+
"Date",
|
|
796
|
+
"DateTime"
|
|
789
797
|
]);
|
|
790
798
|
/**
|
|
799
|
+
* Filters that additionally omit the ordering operators (lt/lte/gt/gte). Ordering two
|
|
800
|
+
* booleans is `eq` spelled as a puzzle (`gt: false` is `eq: true`), and ordering enum members
|
|
801
|
+
* compares their declaration order — an artefact of how the column was written rather than a
|
|
802
|
+
* fact about the domain. Timestamps and the numerics keep them: ranges are the point of
|
|
803
|
+
* having them. Enum filters are recognised by `isEnum`, as above.
|
|
804
|
+
*/
|
|
805
|
+
const FILTERS_WITHOUT_ORDERING_OPS = /* @__PURE__ */ new Set(["Boolean"]);
|
|
806
|
+
/**
|
|
791
807
|
* Scalars the built-in detection itself can produce. A scalar override to one of these
|
|
792
808
|
* shares the built-in filter type for that scalar, so its shape must stay exactly what a
|
|
793
809
|
* natural column of the scalar gets — whichever column builds the cached type first.
|
|
@@ -830,14 +846,15 @@ const resolveOverrideFilterDescriptor = (scalar) => {
|
|
|
830
846
|
const generateColumnFilterValues = (column, tableName, columnName, cacheCtx) => {
|
|
831
847
|
const columnGraphQLType = drizzleColumnToGraphQLType(column, columnName, tableName, true, false, true);
|
|
832
848
|
const inputOverride = getColumnScalarOverride(column, true);
|
|
833
|
-
const { name: genericName, kind } = inputOverride ? resolveOverrideFilterDescriptor(inputOverride) : resolveGenericFilterDescriptor(column, columnGraphQLType);
|
|
849
|
+
const { name: genericName, kind, isEnum } = inputOverride ? resolveOverrideFilterDescriptor(inputOverride) : resolveGenericFilterDescriptor(column, columnGraphQLType);
|
|
834
850
|
const cached = cacheCtx.genericFilterCache.get(genericName);
|
|
835
851
|
if (cached) return cached;
|
|
836
852
|
const colType = columnGraphQLType.type;
|
|
837
853
|
const colArr = new GraphQLList(new GraphQLNonNull(colType));
|
|
838
854
|
const customOverrideFilter = inputOverride !== void 0 && !BUILTIN_FILTER_SCALARS.has(inputOverride);
|
|
839
855
|
const underlying = extractExtendedColumnType(column);
|
|
840
|
-
const omitStringOps = customOverrideFilter ? underlying.type !== "string" || underlying.constraint === "numeric" : FILTERS_WITHOUT_STRING_OPS.has(genericName);
|
|
856
|
+
const omitStringOps = customOverrideFilter ? underlying.type !== "string" || underlying.constraint === "numeric" : isEnum || FILTERS_WITHOUT_STRING_OPS.has(genericName);
|
|
857
|
+
const omitOrderingOps = !customOverrideFilter && (isEnum || FILTERS_WITHOUT_ORDERING_OPS.has(genericName));
|
|
841
858
|
const baseFields = kind === "json" ? jsonFilterFields(column, colType, cacheCtx.typeName) : kind === "array" ? arrayFilterFields(colType, colArr) : {
|
|
842
859
|
eq: {
|
|
843
860
|
type: colType,
|
|
@@ -847,21 +864,23 @@ const generateColumnFilterValues = (column, tableName, columnName, cacheCtx) =>
|
|
|
847
864
|
type: colType,
|
|
848
865
|
description: "Not equal to"
|
|
849
866
|
},
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
867
|
+
...omitOrderingOps ? {} : {
|
|
868
|
+
lt: {
|
|
869
|
+
type: colType,
|
|
870
|
+
description: "Less than"
|
|
871
|
+
},
|
|
872
|
+
lte: {
|
|
873
|
+
type: colType,
|
|
874
|
+
description: "Less than or equal to"
|
|
875
|
+
},
|
|
876
|
+
gt: {
|
|
877
|
+
type: colType,
|
|
878
|
+
description: "Greater than"
|
|
879
|
+
},
|
|
880
|
+
gte: {
|
|
881
|
+
type: colType,
|
|
882
|
+
description: "Greater than or equal to"
|
|
883
|
+
}
|
|
865
884
|
},
|
|
866
885
|
...omitStringOps ? {} : {
|
|
867
886
|
like: { type: GraphQLString },
|
|
@@ -2883,6 +2902,154 @@ const listFieldComplexity = (options, policy) => ({ args, childComplexity }) =>
|
|
|
2883
2902
|
*/
|
|
2884
2903
|
const aggregateFieldComplexity = (options) => ({ childComplexity }) => options.aggregateCost + childComplexity;
|
|
2885
2904
|
//#endregion
|
|
2905
|
+
//#region src/util/parse-resolve-info.ts
|
|
2906
|
+
/** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
|
|
2907
|
+
const namedTypeOf = (type) => {
|
|
2908
|
+
let current = type;
|
|
2909
|
+
while (current?.ofType) current = current.ofType;
|
|
2910
|
+
return current;
|
|
2911
|
+
};
|
|
2912
|
+
/**
|
|
2913
|
+
* Whether a field's named output type has sub-selections. Object types and interfaces expose
|
|
2914
|
+
* `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
|
|
2915
|
+
* also expose `getFields`, but an input type can never be a field's output type, so there is
|
|
2916
|
+
* nothing here for that to collide with.
|
|
2917
|
+
*/
|
|
2918
|
+
const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
|
|
2919
|
+
/**
|
|
2920
|
+
* The fields declared on a type, or `undefined` when it has none to declare. A union is the
|
|
2921
|
+
* case that matters: it has no fields of its own, so a selection directly against one resolves
|
|
2922
|
+
* to nothing and is dropped — only the inline fragments underneath contribute.
|
|
2923
|
+
*/
|
|
2924
|
+
const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
|
|
2925
|
+
/**
|
|
2926
|
+
* The runtime variable values, in the plain-map form the directive check wants. graphql 17
|
|
2927
|
+
* wraps them in `{ sources, coerced }`; graphql 16 hands over the map itself.
|
|
2928
|
+
*/
|
|
2929
|
+
const coercedVariables = (info) => {
|
|
2930
|
+
const values = info.variableValues;
|
|
2931
|
+
if (values && typeof values === "object" && "coerced" in values) return values.coerced ?? {};
|
|
2932
|
+
return values ?? {};
|
|
2933
|
+
};
|
|
2934
|
+
/** The value of a `@skip` / `@include` `if:` argument — a literal or a variable reference. */
|
|
2935
|
+
const directiveIfValue = (info, value) => {
|
|
2936
|
+
if (value.kind === Kind.VARIABLE) return coercedVariables(info)[value.name.value];
|
|
2937
|
+
if (value.kind === Kind.BOOLEAN) return value.value;
|
|
2938
|
+
};
|
|
2939
|
+
/**
|
|
2940
|
+
* Whether `@skip` / `@include` on this selection exclude it. Applies to fields, fragment
|
|
2941
|
+
* spreads and inline fragments alike, which is where graphql puts them.
|
|
2942
|
+
*/
|
|
2943
|
+
const isExcluded = (info, node) => {
|
|
2944
|
+
for (const directive of node.directives ?? []) {
|
|
2945
|
+
const directiveName = directive.name.value;
|
|
2946
|
+
if (directiveName !== "skip" && directiveName !== "include") continue;
|
|
2947
|
+
const ifArgument = directive.arguments?.find((argument) => argument.name.value === "if");
|
|
2948
|
+
if (!ifArgument) continue;
|
|
2949
|
+
const value = directiveIfValue(info, ifArgument.value);
|
|
2950
|
+
if (directiveName === "skip" ? !!value : !value) return true;
|
|
2951
|
+
}
|
|
2952
|
+
return false;
|
|
2953
|
+
};
|
|
2954
|
+
/** The type a fragment's `on Type` condition names, looked up in the executing schema. */
|
|
2955
|
+
const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
|
|
2956
|
+
/**
|
|
2957
|
+
* Introspection meta-fields are not schema fields and never reach the database. `__typename` is
|
|
2958
|
+
* the one that turns up in practice; `__id` is excluded from the rule because a schema is free
|
|
2959
|
+
* to declare a field by that name.
|
|
2960
|
+
*/
|
|
2961
|
+
const isMetaField = (name) => name.startsWith("__") && name !== "__id";
|
|
2962
|
+
/**
|
|
2963
|
+
* Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
|
|
2964
|
+
* level, this owns the entries under `parentType`'s name.
|
|
2965
|
+
*
|
|
2966
|
+
* `activeFragments` is the set of fragment names on the current spread path. A document that
|
|
2967
|
+
* passed validation cannot contain a fragment cycle, but this parser also runs on trees a
|
|
2968
|
+
* caller assembled by hand, and an unbounded walk would hang rather than fail.
|
|
2969
|
+
*/
|
|
2970
|
+
const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
|
|
2971
|
+
const parentTypeName = parentType?.name;
|
|
2972
|
+
if (!parentTypeName) return tree;
|
|
2973
|
+
let fields = tree[parentTypeName];
|
|
2974
|
+
if (!fields) {
|
|
2975
|
+
fields = {};
|
|
2976
|
+
tree[parentTypeName] = fields;
|
|
2977
|
+
}
|
|
2978
|
+
const parentFields = fieldsOf(parentType);
|
|
2979
|
+
for (const selection of selections) {
|
|
2980
|
+
if (isExcluded(info, selection)) continue;
|
|
2981
|
+
if (selection.kind === Kind.FIELD) {
|
|
2982
|
+
const name = selection.name.value;
|
|
2983
|
+
if (isMetaField(name)) continue;
|
|
2984
|
+
const fieldDef = parentFields?.[name];
|
|
2985
|
+
if (!fieldDef) continue;
|
|
2986
|
+
const fieldType = namedTypeOf(fieldDef.type);
|
|
2987
|
+
if (!fieldType) continue;
|
|
2988
|
+
const composite = isCompositeLike(fieldType);
|
|
2989
|
+
const alias = selection.alias?.value || name;
|
|
2990
|
+
let node = fields[alias];
|
|
2991
|
+
if (!node) {
|
|
2992
|
+
node = {
|
|
2993
|
+
name,
|
|
2994
|
+
alias,
|
|
2995
|
+
args: { ...getArgumentValues(fieldDef, selection, info.variableValues) },
|
|
2996
|
+
fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
|
|
2997
|
+
};
|
|
2998
|
+
fields[alias] = node;
|
|
2999
|
+
}
|
|
3000
|
+
if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
|
|
3001
|
+
continue;
|
|
3002
|
+
}
|
|
3003
|
+
if (!deep) continue;
|
|
3004
|
+
if (selection.kind === Kind.FRAGMENT_SPREAD) {
|
|
3005
|
+
const fragmentName = selection.name.value;
|
|
3006
|
+
const fragment = info.fragments?.[fragmentName];
|
|
3007
|
+
if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
|
|
3008
|
+
if (activeFragments.has(fragmentName)) continue;
|
|
3009
|
+
const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
|
|
3010
|
+
if (!isCompositeLike(fragmentType)) continue;
|
|
3011
|
+
activeFragments.add(fragmentName);
|
|
3012
|
+
try {
|
|
3013
|
+
collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
3014
|
+
} finally {
|
|
3015
|
+
activeFragments.delete(fragmentName);
|
|
3016
|
+
}
|
|
3017
|
+
continue;
|
|
3018
|
+
}
|
|
3019
|
+
if (selection.kind === Kind.INLINE_FRAGMENT) {
|
|
3020
|
+
const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
|
|
3021
|
+
if (!isCompositeLike(fragmentType)) continue;
|
|
3022
|
+
collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
return tree;
|
|
3026
|
+
};
|
|
3027
|
+
/** The first key of an object, without materializing the whole key array to discard it. */
|
|
3028
|
+
const firstKey = (object) => {
|
|
3029
|
+
for (const key in object) return key;
|
|
3030
|
+
};
|
|
3031
|
+
/**
|
|
3032
|
+
* Reads the selection under a resolver's own field out of its `info`, as a tree of
|
|
3033
|
+
* {@link ResolveTree} nodes.
|
|
3034
|
+
*
|
|
3035
|
+
* ```ts
|
|
3036
|
+
* const parsed = parseResolveInfo(info, { deep: true })!;
|
|
3037
|
+
* const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
|
|
3038
|
+
* ```
|
|
3039
|
+
*
|
|
3040
|
+
* Returns `undefined` when the resolver's field produced no node at all, which in practice
|
|
3041
|
+
* means `info` carried no field nodes.
|
|
3042
|
+
*/
|
|
3043
|
+
const parseResolveInfo = (info, options = {}) => {
|
|
3044
|
+
const fieldNodes = info.fieldNodes;
|
|
3045
|
+
if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
|
|
3046
|
+
const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
|
|
3047
|
+
const typeKey = firstKey(tree);
|
|
3048
|
+
const fields = typeKey === void 0 ? void 0 : tree[typeKey];
|
|
3049
|
+
const fieldKey = fields && firstKey(fields);
|
|
3050
|
+
return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
|
|
3051
|
+
};
|
|
3052
|
+
//#endregion
|
|
2886
3053
|
//#region src/util/builders/field-updates.ts
|
|
2887
3054
|
/**
|
|
2888
3055
|
* The scalars arithmetic is offered on. Decimal and BigInt transport as strings but are
|
|
@@ -3215,6 +3382,127 @@ const pruneNonEagerRelations = (relationMap, shouldEagerLoad) => {
|
|
|
3215
3382
|
for (const [tableName, rels] of Object.entries(relationMap)) out[tableName] = Object.fromEntries(Object.entries(rels).filter(([relationName]) => shouldEagerLoad(tableName, relationName)));
|
|
3216
3383
|
return out;
|
|
3217
3384
|
};
|
|
3385
|
+
//#endregion
|
|
3386
|
+
//#region src/util/builders/common/select-runtime.ts
|
|
3387
|
+
/**
|
|
3388
|
+
* Runs the relational-query-builder select shared by every dialect's `generateSelect*`
|
|
3389
|
+
* resolver: selected columns + offset/limit + aliased orderBy/where callbacks + the eager
|
|
3390
|
+
* `with:` relation params, then remaps the result. `single` switches between
|
|
3391
|
+
* findFirst/findMany (and the single path omits `limit`). The PG fallback for tables
|
|
3392
|
+
* without RQB support stays in pg.ts; this covers the common RQB path for all three.
|
|
3393
|
+
*/
|
|
3394
|
+
const runRelationalSelect = async (opts) => {
|
|
3395
|
+
const { queryBase, tables, tableName, table, relationMap, typeName, typeNameMapper, parsedInfo, offset, orderBy, where, single, filterCtx, pkNames, after, scope, deleted } = opts;
|
|
3396
|
+
const distinct = opts.distinct?.length ? opts.distinct : void 0;
|
|
3397
|
+
const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
3398
|
+
let cursorEntries;
|
|
3399
|
+
if (!single && (after != null || cursorSelected)) {
|
|
3400
|
+
if (after != null && distinct) throw drizzleError("'after' cannot be combined with 'distinct'.", { code: "DRIZZLE_INVALID_CURSOR" });
|
|
3401
|
+
const cursorObstacle = orderByCursorObstacle(orderBy);
|
|
3402
|
+
if (cursorObstacle) {
|
|
3403
|
+
if (after != null) throw drizzleError(cursorObstacle, { code: "DRIZZLE_INVALID_CURSOR" });
|
|
3404
|
+
} else if (!pkNames?.length) {
|
|
3405
|
+
if (after != null) throw drizzleError(`Table ${tableName} has no primary key, so cursor pagination cannot be used on it.`, { code: "DRIZZLE_INVALID_CURSOR" });
|
|
3406
|
+
} else cursorEntries = cursorOrderingEntries(orderBy, pkNames);
|
|
3407
|
+
}
|
|
3408
|
+
const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
|
|
3409
|
+
const needsDefaultOrder = single || offset != null || opts.limit != null;
|
|
3410
|
+
let distinctKeys;
|
|
3411
|
+
if (distinct) {
|
|
3412
|
+
distinctKeys = await selectDistinctKeys({
|
|
3413
|
+
db: opts.db,
|
|
3414
|
+
table,
|
|
3415
|
+
tableName,
|
|
3416
|
+
distinct,
|
|
3417
|
+
pkNames: pkNames ?? [],
|
|
3418
|
+
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
|
|
3419
|
+
orderBy,
|
|
3420
|
+
limit: single ? 1 : opts.limit,
|
|
3421
|
+
offset
|
|
3422
|
+
});
|
|
3423
|
+
if (!distinctKeys.length) return single ? void 0 : [];
|
|
3424
|
+
}
|
|
3425
|
+
const params = {
|
|
3426
|
+
columns: extractSelectedColumnsFromTree(parsedInfo.fieldsByTypeName[typeName], table, {
|
|
3427
|
+
tableName,
|
|
3428
|
+
relationMap,
|
|
3429
|
+
tables,
|
|
3430
|
+
allRelations: filterCtx?.relationMap
|
|
3431
|
+
}),
|
|
3432
|
+
offset: distinctKeys ? void 0 : offset,
|
|
3433
|
+
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,
|
|
3434
|
+
where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, 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,
|
|
3435
|
+
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
3436
|
+
typeNameMapper,
|
|
3437
|
+
filterCtx,
|
|
3438
|
+
limits: opts.limits,
|
|
3439
|
+
scope,
|
|
3440
|
+
defaultOrderBy: opts.defaultOrderBy,
|
|
3441
|
+
resolveName: opts.resolveName
|
|
3442
|
+
}) : void 0
|
|
3443
|
+
};
|
|
3444
|
+
if (single) {
|
|
3445
|
+
const result = await queryBase.findFirst(params);
|
|
3446
|
+
return result ? remapToGraphQLSingleOutput(result, tableName, table, relationMap) : void 0;
|
|
3447
|
+
}
|
|
3448
|
+
if (cursorEntries && cursorSelected) for (const [column] of cursorEntries) params.columns[column] = true;
|
|
3449
|
+
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
3450
|
+
const result = await queryBase.findMany(params);
|
|
3451
|
+
if (cursorEntries && cursorSelected) attachRowCursors(result, cursorEntries);
|
|
3452
|
+
return remapToGraphQLArrayOutput(result, tableName, table, relationMap);
|
|
3453
|
+
};
|
|
3454
|
+
/**
|
|
3455
|
+
* After a mutation, re-fetch the mutated rows through the relational query builder so the
|
|
3456
|
+
* selected relations are eagerly loaded in a single query, then merge those relations onto
|
|
3457
|
+
* the `.returning()` rows — making the per-field BatchLoader fallback unnecessary.
|
|
3458
|
+
*
|
|
3459
|
+
* `withParams` is the pre-computed relation selection (from extractRelationsParams); the
|
|
3460
|
+
* caller only invokes this when relations are actually selected, so it also gates whether
|
|
3461
|
+
* the PK was forced into RETURNING.
|
|
3462
|
+
*
|
|
3463
|
+
* Falls back to the original `.returning()` rows (relations then resolve via the
|
|
3464
|
+
* field-level BatchLoader) when the table has no RQB support, no primary key columns can be
|
|
3465
|
+
* determined, or the re-fetch fails. Supports single- and multi-column primary keys.
|
|
3466
|
+
*/
|
|
3467
|
+
const eagerLoadMutationRelations = async (db, tableName, rows, pkNames, withParams) => {
|
|
3468
|
+
if (!rows.length || !pkNames.length || !withParams || !Object.keys(withParams).length) return rows;
|
|
3469
|
+
const queryBase = db.query?.[tableName];
|
|
3470
|
+
if (!queryBase) return rows;
|
|
3471
|
+
const keyableRows = rows.filter((row) => pkNames.every((n) => row[n] != null));
|
|
3472
|
+
if (!keyableRows.length) return rows;
|
|
3473
|
+
const pkColumns = {};
|
|
3474
|
+
for (const pk of pkNames) pkColumns[pk] = true;
|
|
3475
|
+
const relationNames = Object.keys(withParams);
|
|
3476
|
+
const keyOf = (row) => JSON.stringify(pkNames.map((n) => typeof row[n] === "bigint" ? row[n].toString() : row[n]));
|
|
3477
|
+
let whereRaw;
|
|
3478
|
+
if (pkNames.length === 1) {
|
|
3479
|
+
const pkName = pkNames[0];
|
|
3480
|
+
const ids = keyableRows.map((r) => r[pkName]);
|
|
3481
|
+
whereRaw = (aliased) => inArray(aliased[pkName], ids);
|
|
3482
|
+
} else whereRaw = (aliased) => {
|
|
3483
|
+
const lhs = sql.join(pkNames.map((n) => sql`${aliased[n]}`), sql`, `);
|
|
3484
|
+
const tuples = sql.join(keyableRows.map((row) => sql`(${sql.join(pkNames.map((n) => sql`${row[n]}`), sql`, `)})`), sql`, `);
|
|
3485
|
+
return sql`(${lhs}) in (${tuples})`;
|
|
3486
|
+
};
|
|
3487
|
+
let enriched;
|
|
3488
|
+
try {
|
|
3489
|
+
enriched = await queryBase.findMany({
|
|
3490
|
+
columns: pkColumns,
|
|
3491
|
+
where: { RAW: whereRaw },
|
|
3492
|
+
with: withParams
|
|
3493
|
+
});
|
|
3494
|
+
} catch (err) {
|
|
3495
|
+
console.warn(`[drizzle-graphql] eager-loading relations for a "${tableName}" mutation failed; falling back to lazy resolution.`, err);
|
|
3496
|
+
return rows;
|
|
3497
|
+
}
|
|
3498
|
+
const byKey = new Map(enriched.map((e) => [keyOf(e), e]));
|
|
3499
|
+
for (const row of rows) {
|
|
3500
|
+
const match = byKey.get(keyOf(row));
|
|
3501
|
+
if (!match) continue;
|
|
3502
|
+
for (const rel of relationNames) row[rel] = match[rel];
|
|
3503
|
+
}
|
|
3504
|
+
return rows;
|
|
3505
|
+
};
|
|
3218
3506
|
const createMutationTxCtx = (options) => options ? {
|
|
3219
3507
|
timeoutMs: options.timeoutMs,
|
|
3220
3508
|
fieldNames: /* @__PURE__ */ new Set(),
|
|
@@ -3393,12 +3681,6 @@ const hardDeleteArg = {
|
|
|
3393
3681
|
defaultValue: false,
|
|
3394
3682
|
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."
|
|
3395
3683
|
};
|
|
3396
|
-
/**
|
|
3397
|
-
* Computes the RETURNING columns and relation selection for a mutation resolver: extracts
|
|
3398
|
-
* the selected scalar columns, determines whether any relations were selected, and only
|
|
3399
|
-
* then forces the primary key into the column set (so the post-mutation eager-load can
|
|
3400
|
-
* re-key rows). Returns everything the resolver needs to decide whether to eager-load.
|
|
3401
|
-
*/
|
|
3402
3684
|
const prepareMutationRelationColumns = (params) => {
|
|
3403
3685
|
const { relationMap, tables, tableName, typeNameMapper, table, pkNames, parsedInfo, resolveName } = params;
|
|
3404
3686
|
const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
|
|
@@ -3422,6 +3704,38 @@ const prepareMutationRelationColumns = (params) => {
|
|
|
3422
3704
|
};
|
|
3423
3705
|
};
|
|
3424
3706
|
/**
|
|
3707
|
+
* The prologue every write resolver runs: parse the resolve info, then work out the columns
|
|
3708
|
+
* to return and the relations to eager-load from it. `parsedInfo` comes back too — the delete
|
|
3709
|
+
* resolver reads its own columns off the tree rather than taking the prepared set.
|
|
3710
|
+
*/
|
|
3711
|
+
const mutationSelection = (info, params) => {
|
|
3712
|
+
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
3713
|
+
return {
|
|
3714
|
+
parsedInfo,
|
|
3715
|
+
...prepareMutationRelationColumns({
|
|
3716
|
+
...params,
|
|
3717
|
+
parsedInfo
|
|
3718
|
+
})
|
|
3719
|
+
};
|
|
3720
|
+
};
|
|
3721
|
+
/**
|
|
3722
|
+
* The `where` a write runs under: the caller's filters — required when the mutation refuses to
|
|
3723
|
+
* run unbounded — with the row scope ANDed on last, so a supplied filter can only narrow the
|
|
3724
|
+
* scope and never widen it. An out-of-scope row is simply not matched.
|
|
3725
|
+
*
|
|
3726
|
+
* `deleted` states which soft-delete mode the match reads at, for the writes that need one:
|
|
3727
|
+
* `restore` sees only marked rows, a hard delete has to see them too.
|
|
3728
|
+
*/
|
|
3729
|
+
const scopedWhere = (params) => {
|
|
3730
|
+
const { scope, tableName, table, where, relationCtx, required, deleted } = params;
|
|
3731
|
+
return withScope(scope, tableName, table, required ? extractRequiredFilters(table, tableName, where, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0, deleted);
|
|
3732
|
+
};
|
|
3733
|
+
/**
|
|
3734
|
+
* The epilogue: the written rows with their selected relations attached, or the rows as they
|
|
3735
|
+
* came back when the selection asked for no relation at all.
|
|
3736
|
+
*/
|
|
3737
|
+
const withEagerRelations = (executor, tableName, rows, pkNames, withParams, hasRelations) => hasRelations ? eagerLoadMutationRelations(executor, tableName, rows, pkNames, withParams) : rows;
|
|
3738
|
+
/**
|
|
3425
3739
|
* The per-entry input of `update<Table>Many`: `{ where, set }`, reusing the table's
|
|
3426
3740
|
* update `set` input and filter input. Shared by all three dialect builders.
|
|
3427
3741
|
*/
|
|
@@ -3521,8 +3835,14 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
|
|
|
3521
3835
|
try {
|
|
3522
3836
|
return await runMutation(db, context, info, txCtx, async (executor) => {
|
|
3523
3837
|
const { where, set } = args;
|
|
3524
|
-
const
|
|
3525
|
-
|
|
3838
|
+
const filters = scopedWhere({
|
|
3839
|
+
scope: void 0,
|
|
3840
|
+
tableName,
|
|
3841
|
+
table,
|
|
3842
|
+
where,
|
|
3843
|
+
relationCtx: relationFilterCtx(filterCtx, tableName),
|
|
3844
|
+
required: requireWhere
|
|
3845
|
+
});
|
|
3526
3846
|
let query;
|
|
3527
3847
|
if (kind === "delete") query = executor.delete(table);
|
|
3528
3848
|
else {
|
|
@@ -3780,8 +4100,8 @@ const identifyRows = (field, args) => {
|
|
|
3780
4100
|
* Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
|
|
3781
4101
|
* Returns raw rows (NOT remapped); the caller groups + remaps them.
|
|
3782
4102
|
*/
|
|
3783
|
-
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, orderCtx, whereArgs) => {
|
|
3784
|
-
const cols = getColumns(targetTable);
|
|
4103
|
+
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs) => {
|
|
4104
|
+
const cols = columns ?? getColumns(targetTable);
|
|
3785
4105
|
const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
|
|
3786
4106
|
const orderClause = orderExprs.length ? sql` order by ${sql.join(orderExprs, sql`, `)}` : sql``;
|
|
3787
4107
|
const RN = "__drizzle_graphql_rn";
|
|
@@ -3827,12 +4147,39 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3827
4147
|
operation: "relation",
|
|
3828
4148
|
relation: relationName
|
|
3829
4149
|
};
|
|
4150
|
+
/**
|
|
4151
|
+
* The columns one batch reads: the ones the selection names, plus the ones the batch needs
|
|
4152
|
+
* whatever the client asked for — the key it groups the rows by, and the ordering columns a
|
|
4153
|
+
* row cursor is encoded from. Nested relation fields contribute their own join column
|
|
4154
|
+
* through the selection context, the same way the root select path narrows.
|
|
4155
|
+
*
|
|
4156
|
+
* `undefined` means "every column", which is what a resolver with no readable resolve info
|
|
4157
|
+
* has to fall back to.
|
|
4158
|
+
*/
|
|
4159
|
+
const selectedColumns = (info, cursorEntries) => {
|
|
4160
|
+
const parsed = info ? parseResolveInfo(info) : void 0;
|
|
4161
|
+
if (!parsed) return;
|
|
4162
|
+
const tree = Object.assign({}, ...Object.values(parsed.fieldsByTypeName));
|
|
4163
|
+
const columns = extractSelectedColumnsFromTreeSQLFormat(tree, targetTable, {
|
|
4164
|
+
tableName: targetTableName,
|
|
4165
|
+
relationMap: filterCtx?.relationMap ?? {},
|
|
4166
|
+
tables,
|
|
4167
|
+
allRelations: filterCtx?.relationMap
|
|
4168
|
+
});
|
|
4169
|
+
const targetColumns = getColumns(targetTable);
|
|
4170
|
+
columns[foreignColPropName] = foreignCol;
|
|
4171
|
+
for (const [columnName] of cursorEntries ?? []) {
|
|
4172
|
+
const column = targetColumns[columnName];
|
|
4173
|
+
if (column) columns[columnName] = column;
|
|
4174
|
+
}
|
|
4175
|
+
return columns;
|
|
4176
|
+
};
|
|
3830
4177
|
const resolve = async (parent, args, context, info) => {
|
|
3831
4178
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
3832
4179
|
const localValue = parent[localColPropName];
|
|
3833
4180
|
if (localValue == null) return isOne ? null : [];
|
|
3834
4181
|
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}`, () => {
|
|
4182
|
+
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
3836
4183
|
const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
|
|
3837
4184
|
const limit = applyLimitPolicy(requestedLimit, limitPolicy, errorCtx);
|
|
3838
4185
|
const distinct = (args ?? {}).distinct?.length ? (args ?? {}).distinct : void 0;
|
|
@@ -3847,6 +4194,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3847
4194
|
} else cursorEntries = cursorOrderingEntries(orderByArg, targetPkNames);
|
|
3848
4195
|
}
|
|
3849
4196
|
const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
|
|
4197
|
+
const columns = selectedColumns(info, cursorEntries);
|
|
3850
4198
|
const argsKey = JSON.stringify({
|
|
3851
4199
|
where: whereArg ?? null,
|
|
3852
4200
|
orderBy: orderByArg ?? null,
|
|
@@ -3855,7 +4203,8 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3855
4203
|
deleted: deleted ?? defaultDeleted ?? null,
|
|
3856
4204
|
after: after ?? null,
|
|
3857
4205
|
distinct: distinct ?? null,
|
|
3858
|
-
cursor: cursorEntries ? 1 : 0
|
|
4206
|
+
cursor: cursorEntries ? 1 : 0,
|
|
4207
|
+
columns: columns ? Object.keys(columns).sort() : null
|
|
3859
4208
|
});
|
|
3860
4209
|
return {
|
|
3861
4210
|
orderByArg,
|
|
@@ -3863,6 +4212,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3863
4212
|
distinct,
|
|
3864
4213
|
cursorEntries,
|
|
3865
4214
|
cursorValues,
|
|
4215
|
+
columns,
|
|
3866
4216
|
loaderKey: `${tableName}::${relationName}::${argsKey}`
|
|
3867
4217
|
};
|
|
3868
4218
|
});
|
|
@@ -3886,9 +4236,9 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3886
4236
|
whereCondition = and(whereCondition, primaryKeyRestriction(targetTable, targetPkNames, keys));
|
|
3887
4237
|
}
|
|
3888
4238
|
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);
|
|
4239
|
+
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, columns, relationFilterCtx(filterCtx, targetTableName), whereArg);
|
|
3890
4240
|
else {
|
|
3891
|
-
let q = executor.select().from(targetTable).where(whereCondition);
|
|
4241
|
+
let q = (columns ? executor.select(columns) : executor.select()).from(targetTable).where(whereCondition);
|
|
3892
4242
|
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
|
|
3893
4243
|
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
3894
4244
|
rows = await q;
|
|
@@ -3959,165 +4309,44 @@ const computeResolverFieldNames = (tableName, typeNameMapper, prefixes, suffixes
|
|
|
3959
4309
|
const restoreSingleFieldName = `${restoreBase}${writeSingleSuffix}`;
|
|
3960
4310
|
return {
|
|
3961
4311
|
typeName,
|
|
3962
|
-
listFieldName,
|
|
3963
|
-
singleFieldName,
|
|
3964
|
-
aggregateFieldName,
|
|
3965
|
-
groupByFieldName,
|
|
3966
|
-
createArrayFieldName,
|
|
3967
|
-
createSingleFieldName,
|
|
3968
|
-
upsertArrayFieldName,
|
|
3969
|
-
upsertSingleFieldName,
|
|
3970
|
-
updateFieldName,
|
|
3971
|
-
updateManyFieldName,
|
|
3972
|
-
updateSingleFieldName,
|
|
3973
|
-
updateCountFieldName: `${prefixes.update}${pluralNoun}Count`,
|
|
3974
|
-
deleteFieldName,
|
|
3975
|
-
deleteSingleFieldName,
|
|
3976
|
-
restoreFieldName,
|
|
3977
|
-
restoreSingleFieldName,
|
|
3978
|
-
deleteCountFieldName: `${prefixes.delete}${pluralNoun}Count`
|
|
3979
|
-
};
|
|
3980
|
-
};
|
|
3981
|
-
//#endregion
|
|
3982
|
-
//#region src/util/builders/common/root-fields.ts
|
|
3983
|
-
/**
|
|
3984
|
-
* Registers one generated root field, refusing to overwrite a name already taken.
|
|
3985
|
-
*
|
|
3986
|
-
* A generated name is the table name run through `typeNameMapper`, a prefix and a suffix,
|
|
3987
|
-
* and the pluralization each operation applies — so whether two operations collide is a
|
|
3988
|
-
* property of the finished names, not of any one piece of the config. `buildSchema` checks
|
|
3989
|
-
* what it can up front (identical list and single suffixes with no mapper), but that check
|
|
3990
|
-
* compares the suffix *strings*, which cannot see a collision the rest of the naming rules
|
|
3991
|
-
* produce: `{ list: 'All', single: '' }` used to give both insert mutations the name
|
|
3992
|
-
* `createUsers`, and the array insert simply disappeared from the schema.
|
|
3993
|
-
*
|
|
3994
|
-
* Assigning into the record silently kept the last registration. Throwing here means a
|
|
3995
|
-
* naming config that cannot work says so at build time, naming the field it lost.
|
|
3996
|
-
*/
|
|
3997
|
-
const defineRootField = (fields, kind, name, config) => {
|
|
3998
|
-
if (Object.getOwnPropertyDescriptor(fields, name) !== void 0) throw new Error(`Drizzle-GraphQL Error: two generated ${kind} fields are both named '${name}'. Generated names come from the table name plus config.prefixes, config.suffixes and config.typeNameMapper — some combination of those gives two operations the same name. Adjust one of them so each operation gets its own.`);
|
|
3999
|
-
fields[name] = config;
|
|
4000
|
-
};
|
|
4001
|
-
//#endregion
|
|
4002
|
-
//#region src/util/builders/common/select-runtime.ts
|
|
4003
|
-
/**
|
|
4004
|
-
* Runs the relational-query-builder select shared by every dialect's `generateSelect*`
|
|
4005
|
-
* resolver: selected columns + offset/limit + aliased orderBy/where callbacks + the eager
|
|
4006
|
-
* `with:` relation params, then remaps the result. `single` switches between
|
|
4007
|
-
* findFirst/findMany (and the single path omits `limit`). The PG fallback for tables
|
|
4008
|
-
* without RQB support stays in pg.ts; this covers the common RQB path for all three.
|
|
4009
|
-
*/
|
|
4010
|
-
const runRelationalSelect = async (opts) => {
|
|
4011
|
-
const { queryBase, tables, tableName, table, relationMap, typeName, typeNameMapper, parsedInfo, offset, orderBy, where, single, filterCtx, pkNames, after, scope, deleted } = opts;
|
|
4012
|
-
const distinct = opts.distinct?.length ? opts.distinct : void 0;
|
|
4013
|
-
const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
4014
|
-
let cursorEntries;
|
|
4015
|
-
if (!single && (after != null || cursorSelected)) {
|
|
4016
|
-
if (after != null && distinct) throw drizzleError("'after' cannot be combined with 'distinct'.", { code: "DRIZZLE_INVALID_CURSOR" });
|
|
4017
|
-
const cursorObstacle = orderByCursorObstacle(orderBy);
|
|
4018
|
-
if (cursorObstacle) {
|
|
4019
|
-
if (after != null) throw drizzleError(cursorObstacle, { code: "DRIZZLE_INVALID_CURSOR" });
|
|
4020
|
-
} else if (!pkNames?.length) {
|
|
4021
|
-
if (after != null) throw drizzleError(`Table ${tableName} has no primary key, so cursor pagination cannot be used on it.`, { code: "DRIZZLE_INVALID_CURSOR" });
|
|
4022
|
-
} else cursorEntries = cursorOrderingEntries(orderBy, pkNames);
|
|
4023
|
-
}
|
|
4024
|
-
const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
|
|
4025
|
-
const needsDefaultOrder = single || offset != null || opts.limit != null;
|
|
4026
|
-
let distinctKeys;
|
|
4027
|
-
if (distinct) {
|
|
4028
|
-
distinctKeys = await selectDistinctKeys({
|
|
4029
|
-
db: opts.db,
|
|
4030
|
-
table,
|
|
4031
|
-
tableName,
|
|
4032
|
-
distinct,
|
|
4033
|
-
pkNames: pkNames ?? [],
|
|
4034
|
-
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
|
|
4035
|
-
orderBy,
|
|
4036
|
-
limit: single ? 1 : opts.limit,
|
|
4037
|
-
offset
|
|
4038
|
-
});
|
|
4039
|
-
if (!distinctKeys.length) return single ? void 0 : [];
|
|
4040
|
-
}
|
|
4041
|
-
const params = {
|
|
4042
|
-
columns: extractSelectedColumnsFromTree(parsedInfo.fieldsByTypeName[typeName], table, {
|
|
4043
|
-
tableName,
|
|
4044
|
-
relationMap,
|
|
4045
|
-
tables,
|
|
4046
|
-
allRelations: filterCtx?.relationMap
|
|
4047
|
-
}),
|
|
4048
|
-
offset: distinctKeys ? void 0 : offset,
|
|
4049
|
-
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,
|
|
4050
|
-
where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, 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,
|
|
4051
|
-
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
4052
|
-
typeNameMapper,
|
|
4053
|
-
filterCtx,
|
|
4054
|
-
limits: opts.limits,
|
|
4055
|
-
scope,
|
|
4056
|
-
defaultOrderBy: opts.defaultOrderBy,
|
|
4057
|
-
resolveName: opts.resolveName
|
|
4058
|
-
}) : void 0
|
|
4312
|
+
listFieldName,
|
|
4313
|
+
singleFieldName,
|
|
4314
|
+
aggregateFieldName,
|
|
4315
|
+
groupByFieldName,
|
|
4316
|
+
createArrayFieldName,
|
|
4317
|
+
createSingleFieldName,
|
|
4318
|
+
upsertArrayFieldName,
|
|
4319
|
+
upsertSingleFieldName,
|
|
4320
|
+
updateFieldName,
|
|
4321
|
+
updateManyFieldName,
|
|
4322
|
+
updateSingleFieldName,
|
|
4323
|
+
updateCountFieldName: `${prefixes.update}${pluralNoun}Count`,
|
|
4324
|
+
deleteFieldName,
|
|
4325
|
+
deleteSingleFieldName,
|
|
4326
|
+
restoreFieldName,
|
|
4327
|
+
restoreSingleFieldName,
|
|
4328
|
+
deleteCountFieldName: `${prefixes.delete}${pluralNoun}Count`
|
|
4059
4329
|
};
|
|
4060
|
-
if (single) {
|
|
4061
|
-
const result = await queryBase.findFirst(params);
|
|
4062
|
-
return result ? remapToGraphQLSingleOutput(result, tableName, table, relationMap) : void 0;
|
|
4063
|
-
}
|
|
4064
|
-
if (cursorEntries && cursorSelected) for (const [column] of cursorEntries) params.columns[column] = true;
|
|
4065
|
-
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
4066
|
-
const result = await queryBase.findMany(params);
|
|
4067
|
-
if (cursorEntries && cursorSelected) attachRowCursors(result, cursorEntries);
|
|
4068
|
-
return remapToGraphQLArrayOutput(result, tableName, table, relationMap);
|
|
4069
4330
|
};
|
|
4331
|
+
//#endregion
|
|
4332
|
+
//#region src/util/builders/common/root-fields.ts
|
|
4070
4333
|
/**
|
|
4071
|
-
*
|
|
4072
|
-
* selected relations are eagerly loaded in a single query, then merge those relations onto
|
|
4073
|
-
* the `.returning()` rows — making the per-field BatchLoader fallback unnecessary.
|
|
4334
|
+
* Registers one generated root field, refusing to overwrite a name already taken.
|
|
4074
4335
|
*
|
|
4075
|
-
*
|
|
4076
|
-
*
|
|
4077
|
-
* the
|
|
4336
|
+
* A generated name is the table name run through `typeNameMapper`, a prefix and a suffix,
|
|
4337
|
+
* and the pluralization each operation applies — so whether two operations collide is a
|
|
4338
|
+
* property of the finished names, not of any one piece of the config. `buildSchema` checks
|
|
4339
|
+
* what it can up front (identical list and single suffixes with no mapper), but that check
|
|
4340
|
+
* compares the suffix *strings*, which cannot see a collision the rest of the naming rules
|
|
4341
|
+
* produce: `{ list: 'All', single: '' }` used to give both insert mutations the name
|
|
4342
|
+
* `createUsers`, and the array insert simply disappeared from the schema.
|
|
4078
4343
|
*
|
|
4079
|
-
*
|
|
4080
|
-
*
|
|
4081
|
-
* determined, or the re-fetch fails. Supports single- and multi-column primary keys.
|
|
4344
|
+
* Assigning into the record silently kept the last registration. Throwing here means a
|
|
4345
|
+
* naming config that cannot work says so at build time, naming the field it lost.
|
|
4082
4346
|
*/
|
|
4083
|
-
const
|
|
4084
|
-
if (
|
|
4085
|
-
|
|
4086
|
-
if (!queryBase) return rows;
|
|
4087
|
-
const keyableRows = rows.filter((row) => pkNames.every((n) => row[n] != null));
|
|
4088
|
-
if (!keyableRows.length) return rows;
|
|
4089
|
-
const pkColumns = {};
|
|
4090
|
-
for (const pk of pkNames) pkColumns[pk] = true;
|
|
4091
|
-
const relationNames = Object.keys(withParams);
|
|
4092
|
-
const keyOf = (row) => JSON.stringify(pkNames.map((n) => typeof row[n] === "bigint" ? row[n].toString() : row[n]));
|
|
4093
|
-
let whereRaw;
|
|
4094
|
-
if (pkNames.length === 1) {
|
|
4095
|
-
const pkName = pkNames[0];
|
|
4096
|
-
const ids = keyableRows.map((r) => r[pkName]);
|
|
4097
|
-
whereRaw = (aliased) => inArray(aliased[pkName], ids);
|
|
4098
|
-
} else whereRaw = (aliased) => {
|
|
4099
|
-
const lhs = sql.join(pkNames.map((n) => sql`${aliased[n]}`), sql`, `);
|
|
4100
|
-
const tuples = sql.join(keyableRows.map((row) => sql`(${sql.join(pkNames.map((n) => sql`${row[n]}`), sql`, `)})`), sql`, `);
|
|
4101
|
-
return sql`(${lhs}) in (${tuples})`;
|
|
4102
|
-
};
|
|
4103
|
-
let enriched;
|
|
4104
|
-
try {
|
|
4105
|
-
enriched = await queryBase.findMany({
|
|
4106
|
-
columns: pkColumns,
|
|
4107
|
-
where: { RAW: whereRaw },
|
|
4108
|
-
with: withParams
|
|
4109
|
-
});
|
|
4110
|
-
} catch (err) {
|
|
4111
|
-
console.warn(`[drizzle-graphql] eager-loading relations for a "${tableName}" mutation failed; falling back to lazy resolution.`, err);
|
|
4112
|
-
return rows;
|
|
4113
|
-
}
|
|
4114
|
-
const byKey = new Map(enriched.map((e) => [keyOf(e), e]));
|
|
4115
|
-
for (const row of rows) {
|
|
4116
|
-
const match = byKey.get(keyOf(row));
|
|
4117
|
-
if (!match) continue;
|
|
4118
|
-
for (const rel of relationNames) row[rel] = match[rel];
|
|
4119
|
-
}
|
|
4120
|
-
return rows;
|
|
4347
|
+
const defineRootField = (fields, kind, name, config) => {
|
|
4348
|
+
if (Object.getOwnPropertyDescriptor(fields, name) !== void 0) throw new Error(`Drizzle-GraphQL Error: two generated ${kind} fields are both named '${name}'. Generated names come from the table name plus config.prefixes, config.suffixes and config.typeNameMapper — some combination of those gives two operations the same name. Adjust one of them so each operation gets its own.`);
|
|
4349
|
+
fields[name] = config;
|
|
4121
4350
|
};
|
|
4122
4351
|
//#endregion
|
|
4123
4352
|
//#region src/util/builders/common/table-inputs.ts
|
|
@@ -4903,154 +5132,6 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
4903
5132
|
};
|
|
4904
5133
|
};
|
|
4905
5134
|
//#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
5135
|
//#region src/util/builders/aggregates.ts
|
|
5055
5136
|
/** Operations that aggregate over a set of column values. `count` is handled separately (whole rows). */
|
|
5056
5137
|
const AGGREGATE_OPS = [
|
|
@@ -5935,7 +6016,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
5935
6016
|
};
|
|
5936
6017
|
/** The values the junction stores for the target rows one operand selects. */
|
|
5937
6018
|
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);
|
|
6019
|
+
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
6020
|
};
|
|
5940
6021
|
/**
|
|
5941
6022
|
* The link half of a many-to-many write: junction rows are inserted and deleted, and both
|
|
@@ -5950,7 +6031,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
5950
6031
|
const through = plan.through;
|
|
5951
6032
|
const unlinkAll = () => executor.delete(through.table).where(eq(through.sourceCol, key));
|
|
5952
6033
|
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]));
|
|
6034
|
+
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
6035
|
const rows = [];
|
|
5955
6036
|
for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
|
|
5956
6037
|
existing.add(targetKey);
|
|
@@ -6011,11 +6092,11 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6011
6092
|
assertSingleOperation(plan, op);
|
|
6012
6093
|
if (op["create"] !== void 0) {
|
|
6013
6094
|
const values = applyContextValues(remapFromGraphQLSingleInput({ ...op["create"] }, plan.targetTable), contextValues?.(plan.targetTableName), context);
|
|
6014
|
-
const created = (await executor.insert(plan.targetTable).values(values).returning())[0];
|
|
6095
|
+
const created = (await executor.insert(plan.targetTable).values(values).returning({ [plan.foreignColPropName]: plan.foreignCol }))[0];
|
|
6015
6096
|
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
6097
|
patch[plan.localColPropName] = created[plan.foreignColPropName];
|
|
6017
6098
|
} else if (op["connect"] !== void 0) {
|
|
6018
|
-
const rows = await executor.select().from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
6099
|
+
const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
6019
6100
|
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
6101
|
patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
|
|
6021
6102
|
} else if (op["disconnect"] === true) patch[plan.localColPropName] = null;
|
|
@@ -6553,7 +6634,14 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
|
|
|
6553
6634
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
6554
6635
|
await before();
|
|
6555
6636
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
6556
|
-
const filters =
|
|
6637
|
+
const filters = scopedWhere({
|
|
6638
|
+
scope,
|
|
6639
|
+
tableName,
|
|
6640
|
+
table,
|
|
6641
|
+
where,
|
|
6642
|
+
relationCtx,
|
|
6643
|
+
required: single || requireWhere
|
|
6644
|
+
});
|
|
6557
6645
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
6558
6646
|
let query = executor.update(table).set(input);
|
|
6559
6647
|
if (filters) query = query.where(filters);
|
|
@@ -6635,7 +6723,15 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
|
|
|
6635
6723
|
const hard = canHardDelete && args.hard === true;
|
|
6636
6724
|
const scope = policies?.scope?.(context);
|
|
6637
6725
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
6638
|
-
const filters =
|
|
6726
|
+
const filters = scopedWhere({
|
|
6727
|
+
scope,
|
|
6728
|
+
tableName,
|
|
6729
|
+
table,
|
|
6730
|
+
where,
|
|
6731
|
+
relationCtx,
|
|
6732
|
+
required: single || requireWhere,
|
|
6733
|
+
deleted: restore ? "ONLY" : hard ? "INCLUDE" : void 0
|
|
6734
|
+
});
|
|
6639
6735
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
6640
6736
|
let query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
|
|
6641
6737
|
if (filters) query = query.where(filters);
|
|
@@ -6863,8 +6959,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6863
6959
|
const contextColumns = policies?.contextValues?.(tableName);
|
|
6864
6960
|
const scope = policies?.scope?.(context);
|
|
6865
6961
|
const input = nestedEntries ? [] : applyContextValuesAll(remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : args.values, table), contextColumns, context);
|
|
6866
|
-
const
|
|
6867
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
6962
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
6868
6963
|
relationMap,
|
|
6869
6964
|
tables,
|
|
6870
6965
|
tableName,
|
|
@@ -6873,7 +6968,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6873
6968
|
resolveName,
|
|
6874
6969
|
table,
|
|
6875
6970
|
pkNames,
|
|
6876
|
-
parsedInfo,
|
|
6877
6971
|
limits,
|
|
6878
6972
|
scope,
|
|
6879
6973
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -6894,7 +6988,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6894
6988
|
context
|
|
6895
6989
|
}) : await runInsert(executor, input);
|
|
6896
6990
|
await after(result);
|
|
6897
|
-
const enriched =
|
|
6991
|
+
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
6898
6992
|
return remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
6899
6993
|
}
|
|
6900
6994
|
});
|
|
@@ -6918,8 +7012,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6918
7012
|
const contextColumns = policies?.contextValues?.(tableName);
|
|
6919
7013
|
const scope = policies?.scope?.(context);
|
|
6920
7014
|
const input = nestedEntry ? {} : applyContextValues(remapFromGraphQLSingleInput(entry ? entry.columns : args.values, table), contextColumns, context);
|
|
6921
|
-
const
|
|
6922
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7015
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
6923
7016
|
relationMap,
|
|
6924
7017
|
tables,
|
|
6925
7018
|
tableName,
|
|
@@ -6928,7 +7021,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6928
7021
|
resolveName,
|
|
6929
7022
|
table,
|
|
6930
7023
|
pkNames,
|
|
6931
|
-
parsedInfo,
|
|
6932
7024
|
limits,
|
|
6933
7025
|
scope,
|
|
6934
7026
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -6953,7 +7045,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6953
7045
|
if (!conflictDoNothing) throw drizzleError("The insert returned no row.", { code: "DRIZZLE_NO_ROW_RETURNED" });
|
|
6954
7046
|
return;
|
|
6955
7047
|
}
|
|
6956
|
-
const enriched =
|
|
7048
|
+
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
6957
7049
|
return remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap);
|
|
6958
7050
|
}
|
|
6959
7051
|
});
|
|
@@ -6991,8 +7083,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6991
7083
|
const contextColumns = policies?.contextValues?.(tableName);
|
|
6992
7084
|
const scope = policies?.scope?.(context);
|
|
6993
7085
|
const input = nestedEntries ? [] : applyContextValuesAll(remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : supplied, table), contextColumns, context);
|
|
6994
|
-
const
|
|
6995
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7086
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
6996
7087
|
relationMap,
|
|
6997
7088
|
tables,
|
|
6998
7089
|
tableName,
|
|
@@ -7001,7 +7092,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7001
7092
|
resolveName,
|
|
7002
7093
|
table,
|
|
7003
7094
|
pkNames,
|
|
7004
|
-
parsedInfo,
|
|
7005
7095
|
limits,
|
|
7006
7096
|
scope,
|
|
7007
7097
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -7038,7 +7128,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7038
7128
|
}) : await runUpsert(executor, input);
|
|
7039
7129
|
await after(result);
|
|
7040
7130
|
if (single && !result[0]) return;
|
|
7041
|
-
const enriched =
|
|
7131
|
+
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
7042
7132
|
return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
7043
7133
|
}
|
|
7044
7134
|
});
|
|
@@ -7062,8 +7152,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7062
7152
|
const { where, set } = args;
|
|
7063
7153
|
const scope = policies?.scope?.(context);
|
|
7064
7154
|
await before();
|
|
7065
|
-
const
|
|
7066
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7155
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
7067
7156
|
relationMap,
|
|
7068
7157
|
tables,
|
|
7069
7158
|
tableName,
|
|
@@ -7072,7 +7161,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7072
7161
|
resolveName,
|
|
7073
7162
|
table,
|
|
7074
7163
|
pkNames,
|
|
7075
|
-
parsedInfo,
|
|
7076
7164
|
limits,
|
|
7077
7165
|
scope,
|
|
7078
7166
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -7082,7 +7170,14 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7082
7170
|
const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
|
|
7083
7171
|
if (!Object.keys(input).length && !nestedOps) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7084
7172
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7085
|
-
const filters =
|
|
7173
|
+
const filters = scopedWhere({
|
|
7174
|
+
scope,
|
|
7175
|
+
tableName,
|
|
7176
|
+
table,
|
|
7177
|
+
where,
|
|
7178
|
+
relationCtx,
|
|
7179
|
+
required: single || requireWhere
|
|
7180
|
+
});
|
|
7086
7181
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
7087
7182
|
const returning = nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns;
|
|
7088
7183
|
const runUpdate = async (target, values) => {
|
|
@@ -7104,7 +7199,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7104
7199
|
await after(result);
|
|
7105
7200
|
if (single && result.length > 1) throw drizzleError("'where' matched more than one row!", { code: "DRIZZLE_MULTI_ROW_MATCH" });
|
|
7106
7201
|
if (single && !result[0]) return;
|
|
7107
|
-
const enriched =
|
|
7202
|
+
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
7108
7203
|
return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
7109
7204
|
}
|
|
7110
7205
|
});
|
|
@@ -7146,7 +7241,15 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7146
7241
|
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
7147
7242
|
const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
|
|
7148
7243
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7149
|
-
const filters =
|
|
7244
|
+
const filters = scopedWhere({
|
|
7245
|
+
scope,
|
|
7246
|
+
tableName,
|
|
7247
|
+
table,
|
|
7248
|
+
where,
|
|
7249
|
+
relationCtx,
|
|
7250
|
+
required: single || requireWhere,
|
|
7251
|
+
deleted: restore ? "ONLY" : hard ? "INCLUDE" : void 0
|
|
7252
|
+
});
|
|
7150
7253
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
7151
7254
|
let query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
|
|
7152
7255
|
if (filters) query = query.where(filters);
|
|
@@ -7475,8 +7578,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7475
7578
|
await before();
|
|
7476
7579
|
const scope = policies?.scope?.(context);
|
|
7477
7580
|
const contextColumns = policies?.contextValues?.(tableName);
|
|
7478
|
-
const
|
|
7479
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7581
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
7480
7582
|
relationMap,
|
|
7481
7583
|
tables,
|
|
7482
7584
|
tableName,
|
|
@@ -7485,7 +7587,6 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7485
7587
|
resolveName,
|
|
7486
7588
|
table,
|
|
7487
7589
|
pkNames,
|
|
7488
|
-
parsedInfo,
|
|
7489
7590
|
limits,
|
|
7490
7591
|
scope,
|
|
7491
7592
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -7514,7 +7615,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7514
7615
|
});
|
|
7515
7616
|
const flatRows = perEntry.flat();
|
|
7516
7617
|
await after(flatRows);
|
|
7517
|
-
const enriched =
|
|
7618
|
+
const enriched = await withEagerRelations(executor, tableName, flatRows, pkNames, withParams, hasRelations);
|
|
7518
7619
|
const output = [];
|
|
7519
7620
|
let offset = 0;
|
|
7520
7621
|
for (const rows of perEntry) {
|