@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.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
|
-
*
|
|
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/…
|
|
804
|
-
*
|
|
805
|
-
*
|
|
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
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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,6 +3406,127 @@ 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
|
};
|
|
3409
|
+
//#endregion
|
|
3410
|
+
//#region src/util/builders/common/select-runtime.ts
|
|
3411
|
+
/**
|
|
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.
|
|
3417
|
+
*/
|
|
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);
|
|
3431
|
+
}
|
|
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
|
+
};
|
|
3242
3530
|
const createMutationTxCtx = (options) => options ? {
|
|
3243
3531
|
timeoutMs: options.timeoutMs,
|
|
3244
3532
|
fieldNames: /* @__PURE__ */ new Set(),
|
|
@@ -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
|
|
3549
|
-
|
|
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 {
|
|
@@ -3804,8 +4124,8 @@ const identifyRows = (field, args) => {
|
|
|
3804
4124
|
* Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
|
|
3805
4125
|
* Returns raw rows (NOT remapped); the caller groups + remaps them.
|
|
3806
4126
|
*/
|
|
3807
|
-
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, orderCtx, whereArgs) => {
|
|
3808
|
-
const cols = (0, drizzle_orm.getColumns)(targetTable);
|
|
4127
|
+
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs) => {
|
|
4128
|
+
const cols = columns ?? (0, drizzle_orm.getColumns)(targetTable);
|
|
3809
4129
|
const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
|
|
3810
4130
|
const orderClause = orderExprs.length ? drizzle_orm.sql` order by ${drizzle_orm.sql.join(orderExprs, drizzle_orm.sql`, `)}` : drizzle_orm.sql``;
|
|
3811
4131
|
const RN = "__drizzle_graphql_rn";
|
|
@@ -3851,12 +4171,39 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3851
4171
|
operation: "relation",
|
|
3852
4172
|
relation: relationName
|
|
3853
4173
|
};
|
|
4174
|
+
/**
|
|
4175
|
+
* The columns one batch reads: the ones the selection names, plus the ones the batch needs
|
|
4176
|
+
* whatever the client asked for — the key it groups the rows by, and the ordering columns a
|
|
4177
|
+
* row cursor is encoded from. Nested relation fields contribute their own join column
|
|
4178
|
+
* through the selection context, the same way the root select path narrows.
|
|
4179
|
+
*
|
|
4180
|
+
* `undefined` means "every column", which is what a resolver with no readable resolve info
|
|
4181
|
+
* has to fall back to.
|
|
4182
|
+
*/
|
|
4183
|
+
const selectedColumns = (info, cursorEntries) => {
|
|
4184
|
+
const parsed = info ? parseResolveInfo(info) : void 0;
|
|
4185
|
+
if (!parsed) return;
|
|
4186
|
+
const tree = Object.assign({}, ...Object.values(parsed.fieldsByTypeName));
|
|
4187
|
+
const columns = extractSelectedColumnsFromTreeSQLFormat(tree, targetTable, {
|
|
4188
|
+
tableName: targetTableName,
|
|
4189
|
+
relationMap: filterCtx?.relationMap ?? {},
|
|
4190
|
+
tables,
|
|
4191
|
+
allRelations: filterCtx?.relationMap
|
|
4192
|
+
});
|
|
4193
|
+
const targetColumns = (0, drizzle_orm.getColumns)(targetTable);
|
|
4194
|
+
columns[foreignColPropName] = foreignCol;
|
|
4195
|
+
for (const [columnName] of cursorEntries ?? []) {
|
|
4196
|
+
const column = targetColumns[columnName];
|
|
4197
|
+
if (column) columns[columnName] = column;
|
|
4198
|
+
}
|
|
4199
|
+
return columns;
|
|
4200
|
+
};
|
|
3854
4201
|
const resolve = async (parent, args, context, info) => {
|
|
3855
4202
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
3856
4203
|
const localValue = parent[localColPropName];
|
|
3857
4204
|
if (localValue == null) return isOne ? null : [];
|
|
3858
4205
|
const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
|
|
3859
|
-
const { orderByArg, limit, distinct, cursorEntries, cursorValues, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
4206
|
+
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
3860
4207
|
const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
|
|
3861
4208
|
const limit = applyLimitPolicy(requestedLimit, limitPolicy, errorCtx);
|
|
3862
4209
|
const distinct = (args ?? {}).distinct?.length ? (args ?? {}).distinct : void 0;
|
|
@@ -3871,6 +4218,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3871
4218
|
} else cursorEntries = cursorOrderingEntries(orderByArg, targetPkNames);
|
|
3872
4219
|
}
|
|
3873
4220
|
const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
|
|
4221
|
+
const columns = selectedColumns(info, cursorEntries);
|
|
3874
4222
|
const argsKey = JSON.stringify({
|
|
3875
4223
|
where: whereArg ?? null,
|
|
3876
4224
|
orderBy: orderByArg ?? null,
|
|
@@ -3879,7 +4227,8 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3879
4227
|
deleted: deleted ?? defaultDeleted ?? null,
|
|
3880
4228
|
after: after ?? null,
|
|
3881
4229
|
distinct: distinct ?? null,
|
|
3882
|
-
cursor: cursorEntries ? 1 : 0
|
|
4230
|
+
cursor: cursorEntries ? 1 : 0,
|
|
4231
|
+
columns: columns ? Object.keys(columns).sort() : null
|
|
3883
4232
|
});
|
|
3884
4233
|
return {
|
|
3885
4234
|
orderByArg,
|
|
@@ -3887,6 +4236,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3887
4236
|
distinct,
|
|
3888
4237
|
cursorEntries,
|
|
3889
4238
|
cursorValues,
|
|
4239
|
+
columns,
|
|
3890
4240
|
loaderKey: `${tableName}::${relationName}::${argsKey}`
|
|
3891
4241
|
};
|
|
3892
4242
|
});
|
|
@@ -3910,9 +4260,9 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3910
4260
|
whereCondition = (0, drizzle_orm.and)(whereCondition, primaryKeyRestriction(targetTable, targetPkNames, keys));
|
|
3911
4261
|
}
|
|
3912
4262
|
let rows;
|
|
3913
|
-
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, relationFilterCtx(filterCtx, targetTableName), whereArg);
|
|
4263
|
+
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, columns, relationFilterCtx(filterCtx, targetTableName), whereArg);
|
|
3914
4264
|
else {
|
|
3915
|
-
let q = executor.select().from(targetTable).where(whereCondition);
|
|
4265
|
+
let q = (columns ? executor.select(columns) : executor.select()).from(targetTable).where(whereCondition);
|
|
3916
4266
|
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
|
|
3917
4267
|
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
3918
4268
|
rows = await q;
|
|
@@ -3983,165 +4333,44 @@ const computeResolverFieldNames = (tableName, typeNameMapper, prefixes, suffixes
|
|
|
3983
4333
|
const restoreSingleFieldName = `${restoreBase}${writeSingleSuffix}`;
|
|
3984
4334
|
return {
|
|
3985
4335
|
typeName,
|
|
3986
|
-
listFieldName,
|
|
3987
|
-
singleFieldName,
|
|
3988
|
-
aggregateFieldName,
|
|
3989
|
-
groupByFieldName,
|
|
3990
|
-
createArrayFieldName,
|
|
3991
|
-
createSingleFieldName,
|
|
3992
|
-
upsertArrayFieldName,
|
|
3993
|
-
upsertSingleFieldName,
|
|
3994
|
-
updateFieldName,
|
|
3995
|
-
updateManyFieldName,
|
|
3996
|
-
updateSingleFieldName,
|
|
3997
|
-
updateCountFieldName: `${prefixes.update}${pluralNoun}Count`,
|
|
3998
|
-
deleteFieldName,
|
|
3999
|
-
deleteSingleFieldName,
|
|
4000
|
-
restoreFieldName,
|
|
4001
|
-
restoreSingleFieldName,
|
|
4002
|
-
deleteCountFieldName: `${prefixes.delete}${pluralNoun}Count`
|
|
4003
|
-
};
|
|
4004
|
-
};
|
|
4005
|
-
//#endregion
|
|
4006
|
-
//#region src/util/builders/common/root-fields.ts
|
|
4007
|
-
/**
|
|
4008
|
-
* Registers one generated root field, refusing to overwrite a name already taken.
|
|
4009
|
-
*
|
|
4010
|
-
* A generated name is the table name run through `typeNameMapper`, a prefix and a suffix,
|
|
4011
|
-
* and the pluralization each operation applies — so whether two operations collide is a
|
|
4012
|
-
* property of the finished names, not of any one piece of the config. `buildSchema` checks
|
|
4013
|
-
* what it can up front (identical list and single suffixes with no mapper), but that check
|
|
4014
|
-
* compares the suffix *strings*, which cannot see a collision the rest of the naming rules
|
|
4015
|
-
* produce: `{ list: 'All', single: '' }` used to give both insert mutations the name
|
|
4016
|
-
* `createUsers`, and the array insert simply disappeared from the schema.
|
|
4017
|
-
*
|
|
4018
|
-
* Assigning into the record silently kept the last registration. Throwing here means a
|
|
4019
|
-
* naming config that cannot work says so at build time, naming the field it lost.
|
|
4020
|
-
*/
|
|
4021
|
-
const defineRootField = (fields, kind, name, config) => {
|
|
4022
|
-
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.`);
|
|
4023
|
-
fields[name] = config;
|
|
4024
|
-
};
|
|
4025
|
-
//#endregion
|
|
4026
|
-
//#region src/util/builders/common/select-runtime.ts
|
|
4027
|
-
/**
|
|
4028
|
-
* Runs the relational-query-builder select shared by every dialect's `generateSelect*`
|
|
4029
|
-
* resolver: selected columns + offset/limit + aliased orderBy/where callbacks + the eager
|
|
4030
|
-
* `with:` relation params, then remaps the result. `single` switches between
|
|
4031
|
-
* findFirst/findMany (and the single path omits `limit`). The PG fallback for tables
|
|
4032
|
-
* without RQB support stays in pg.ts; this covers the common RQB path for all three.
|
|
4033
|
-
*/
|
|
4034
|
-
const runRelationalSelect = async (opts) => {
|
|
4035
|
-
const { queryBase, tables, tableName, table, relationMap, typeName, typeNameMapper, parsedInfo, offset, orderBy, where, single, filterCtx, pkNames, after, scope, deleted } = opts;
|
|
4036
|
-
const distinct = opts.distinct?.length ? opts.distinct : void 0;
|
|
4037
|
-
const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
4038
|
-
let cursorEntries;
|
|
4039
|
-
if (!single && (after != null || cursorSelected)) {
|
|
4040
|
-
if (after != null && distinct) throw drizzleError("'after' cannot be combined with 'distinct'.", { code: "DRIZZLE_INVALID_CURSOR" });
|
|
4041
|
-
const cursorObstacle = orderByCursorObstacle(orderBy);
|
|
4042
|
-
if (cursorObstacle) {
|
|
4043
|
-
if (after != null) throw drizzleError(cursorObstacle, { code: "DRIZZLE_INVALID_CURSOR" });
|
|
4044
|
-
} else if (!pkNames?.length) {
|
|
4045
|
-
if (after != null) throw drizzleError(`Table ${tableName} has no primary key, so cursor pagination cannot be used on it.`, { code: "DRIZZLE_INVALID_CURSOR" });
|
|
4046
|
-
} else cursorEntries = cursorOrderingEntries(orderBy, pkNames);
|
|
4047
|
-
}
|
|
4048
|
-
const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
|
|
4049
|
-
const needsDefaultOrder = single || offset != null || opts.limit != null;
|
|
4050
|
-
let distinctKeys;
|
|
4051
|
-
if (distinct) {
|
|
4052
|
-
distinctKeys = await selectDistinctKeys({
|
|
4053
|
-
db: opts.db,
|
|
4054
|
-
table,
|
|
4055
|
-
tableName,
|
|
4056
|
-
distinct,
|
|
4057
|
-
pkNames: pkNames ?? [],
|
|
4058
|
-
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
|
|
4059
|
-
orderBy,
|
|
4060
|
-
limit: single ? 1 : opts.limit,
|
|
4061
|
-
offset
|
|
4062
|
-
});
|
|
4063
|
-
if (!distinctKeys.length) return single ? void 0 : [];
|
|
4064
|
-
}
|
|
4065
|
-
const params = {
|
|
4066
|
-
columns: extractSelectedColumnsFromTree(parsedInfo.fieldsByTypeName[typeName], table, {
|
|
4067
|
-
tableName,
|
|
4068
|
-
relationMap,
|
|
4069
|
-
tables,
|
|
4070
|
-
allRelations: filterCtx?.relationMap
|
|
4071
|
-
}),
|
|
4072
|
-
offset: distinctKeys ? void 0 : offset,
|
|
4073
|
-
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,
|
|
4074
|
-
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,
|
|
4075
|
-
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
4076
|
-
typeNameMapper,
|
|
4077
|
-
filterCtx,
|
|
4078
|
-
limits: opts.limits,
|
|
4079
|
-
scope,
|
|
4080
|
-
defaultOrderBy: opts.defaultOrderBy,
|
|
4081
|
-
resolveName: opts.resolveName
|
|
4082
|
-
}) : void 0
|
|
4336
|
+
listFieldName,
|
|
4337
|
+
singleFieldName,
|
|
4338
|
+
aggregateFieldName,
|
|
4339
|
+
groupByFieldName,
|
|
4340
|
+
createArrayFieldName,
|
|
4341
|
+
createSingleFieldName,
|
|
4342
|
+
upsertArrayFieldName,
|
|
4343
|
+
upsertSingleFieldName,
|
|
4344
|
+
updateFieldName,
|
|
4345
|
+
updateManyFieldName,
|
|
4346
|
+
updateSingleFieldName,
|
|
4347
|
+
updateCountFieldName: `${prefixes.update}${pluralNoun}Count`,
|
|
4348
|
+
deleteFieldName,
|
|
4349
|
+
deleteSingleFieldName,
|
|
4350
|
+
restoreFieldName,
|
|
4351
|
+
restoreSingleFieldName,
|
|
4352
|
+
deleteCountFieldName: `${prefixes.delete}${pluralNoun}Count`
|
|
4083
4353
|
};
|
|
4084
|
-
if (single) {
|
|
4085
|
-
const result = await queryBase.findFirst(params);
|
|
4086
|
-
return result ? remapToGraphQLSingleOutput(result, tableName, table, relationMap) : void 0;
|
|
4087
|
-
}
|
|
4088
|
-
if (cursorEntries && cursorSelected) for (const [column] of cursorEntries) params.columns[column] = true;
|
|
4089
|
-
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
4090
|
-
const result = await queryBase.findMany(params);
|
|
4091
|
-
if (cursorEntries && cursorSelected) attachRowCursors(result, cursorEntries);
|
|
4092
|
-
return remapToGraphQLArrayOutput(result, tableName, table, relationMap);
|
|
4093
4354
|
};
|
|
4355
|
+
//#endregion
|
|
4356
|
+
//#region src/util/builders/common/root-fields.ts
|
|
4094
4357
|
/**
|
|
4095
|
-
*
|
|
4096
|
-
* selected relations are eagerly loaded in a single query, then merge those relations onto
|
|
4097
|
-
* the `.returning()` rows — making the per-field BatchLoader fallback unnecessary.
|
|
4358
|
+
* Registers one generated root field, refusing to overwrite a name already taken.
|
|
4098
4359
|
*
|
|
4099
|
-
*
|
|
4100
|
-
*
|
|
4101
|
-
* the
|
|
4360
|
+
* A generated name is the table name run through `typeNameMapper`, a prefix and a suffix,
|
|
4361
|
+
* and the pluralization each operation applies — so whether two operations collide is a
|
|
4362
|
+
* property of the finished names, not of any one piece of the config. `buildSchema` checks
|
|
4363
|
+
* what it can up front (identical list and single suffixes with no mapper), but that check
|
|
4364
|
+
* compares the suffix *strings*, which cannot see a collision the rest of the naming rules
|
|
4365
|
+
* produce: `{ list: 'All', single: '' }` used to give both insert mutations the name
|
|
4366
|
+
* `createUsers`, and the array insert simply disappeared from the schema.
|
|
4102
4367
|
*
|
|
4103
|
-
*
|
|
4104
|
-
*
|
|
4105
|
-
* determined, or the re-fetch fails. Supports single- and multi-column primary keys.
|
|
4368
|
+
* Assigning into the record silently kept the last registration. Throwing here means a
|
|
4369
|
+
* naming config that cannot work says so at build time, naming the field it lost.
|
|
4106
4370
|
*/
|
|
4107
|
-
const
|
|
4108
|
-
if (
|
|
4109
|
-
|
|
4110
|
-
if (!queryBase) return rows;
|
|
4111
|
-
const keyableRows = rows.filter((row) => pkNames.every((n) => row[n] != null));
|
|
4112
|
-
if (!keyableRows.length) return rows;
|
|
4113
|
-
const pkColumns = {};
|
|
4114
|
-
for (const pk of pkNames) pkColumns[pk] = true;
|
|
4115
|
-
const relationNames = Object.keys(withParams);
|
|
4116
|
-
const keyOf = (row) => JSON.stringify(pkNames.map((n) => typeof row[n] === "bigint" ? row[n].toString() : row[n]));
|
|
4117
|
-
let whereRaw;
|
|
4118
|
-
if (pkNames.length === 1) {
|
|
4119
|
-
const pkName = pkNames[0];
|
|
4120
|
-
const ids = keyableRows.map((r) => r[pkName]);
|
|
4121
|
-
whereRaw = (aliased) => (0, drizzle_orm.inArray)(aliased[pkName], ids);
|
|
4122
|
-
} else whereRaw = (aliased) => {
|
|
4123
|
-
const lhs = drizzle_orm.sql.join(pkNames.map((n) => drizzle_orm.sql`${aliased[n]}`), drizzle_orm.sql`, `);
|
|
4124
|
-
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`, `);
|
|
4125
|
-
return drizzle_orm.sql`(${lhs}) in (${tuples})`;
|
|
4126
|
-
};
|
|
4127
|
-
let enriched;
|
|
4128
|
-
try {
|
|
4129
|
-
enriched = await queryBase.findMany({
|
|
4130
|
-
columns: pkColumns,
|
|
4131
|
-
where: { RAW: whereRaw },
|
|
4132
|
-
with: withParams
|
|
4133
|
-
});
|
|
4134
|
-
} catch (err) {
|
|
4135
|
-
console.warn(`[drizzle-graphql] eager-loading relations for a "${tableName}" mutation failed; falling back to lazy resolution.`, err);
|
|
4136
|
-
return rows;
|
|
4137
|
-
}
|
|
4138
|
-
const byKey = new Map(enriched.map((e) => [keyOf(e), e]));
|
|
4139
|
-
for (const row of rows) {
|
|
4140
|
-
const match = byKey.get(keyOf(row));
|
|
4141
|
-
if (!match) continue;
|
|
4142
|
-
for (const rel of relationNames) row[rel] = match[rel];
|
|
4143
|
-
}
|
|
4144
|
-
return rows;
|
|
4371
|
+
const defineRootField = (fields, kind, name, config) => {
|
|
4372
|
+
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.`);
|
|
4373
|
+
fields[name] = config;
|
|
4145
4374
|
};
|
|
4146
4375
|
//#endregion
|
|
4147
4376
|
//#region src/util/builders/common/table-inputs.ts
|
|
@@ -4927,154 +5156,6 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
4927
5156
|
};
|
|
4928
5157
|
};
|
|
4929
5158
|
//#endregion
|
|
4930
|
-
//#region src/util/parse-resolve-info.ts
|
|
4931
|
-
/** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
|
|
4932
|
-
const namedTypeOf = (type) => {
|
|
4933
|
-
let current = type;
|
|
4934
|
-
while (current?.ofType) current = current.ofType;
|
|
4935
|
-
return current;
|
|
4936
|
-
};
|
|
4937
|
-
/**
|
|
4938
|
-
* Whether a field's named output type has sub-selections. Object types and interfaces expose
|
|
4939
|
-
* `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
|
|
4940
|
-
* also expose `getFields`, but an input type can never be a field's output type, so there is
|
|
4941
|
-
* nothing here for that to collide with.
|
|
4942
|
-
*/
|
|
4943
|
-
const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
|
|
4944
|
-
/**
|
|
4945
|
-
* The fields declared on a type, or `undefined` when it has none to declare. A union is the
|
|
4946
|
-
* case that matters: it has no fields of its own, so a selection directly against one resolves
|
|
4947
|
-
* to nothing and is dropped — only the inline fragments underneath contribute.
|
|
4948
|
-
*/
|
|
4949
|
-
const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
|
|
4950
|
-
/**
|
|
4951
|
-
* The runtime variable values, in the plain-map form the directive check wants. graphql 17
|
|
4952
|
-
* wraps them in `{ sources, coerced }`; graphql 16 hands over the map itself.
|
|
4953
|
-
*/
|
|
4954
|
-
const coercedVariables = (info) => {
|
|
4955
|
-
const values = info.variableValues;
|
|
4956
|
-
if (values && typeof values === "object" && "coerced" in values) return values.coerced ?? {};
|
|
4957
|
-
return values ?? {};
|
|
4958
|
-
};
|
|
4959
|
-
/** The value of a `@skip` / `@include` `if:` argument — a literal or a variable reference. */
|
|
4960
|
-
const directiveIfValue = (info, value) => {
|
|
4961
|
-
if (value.kind === graphql.Kind.VARIABLE) return coercedVariables(info)[value.name.value];
|
|
4962
|
-
if (value.kind === graphql.Kind.BOOLEAN) return value.value;
|
|
4963
|
-
};
|
|
4964
|
-
/**
|
|
4965
|
-
* Whether `@skip` / `@include` on this selection exclude it. Applies to fields, fragment
|
|
4966
|
-
* spreads and inline fragments alike, which is where graphql puts them.
|
|
4967
|
-
*/
|
|
4968
|
-
const isExcluded = (info, node) => {
|
|
4969
|
-
for (const directive of node.directives ?? []) {
|
|
4970
|
-
const directiveName = directive.name.value;
|
|
4971
|
-
if (directiveName !== "skip" && directiveName !== "include") continue;
|
|
4972
|
-
const ifArgument = directive.arguments?.find((argument) => argument.name.value === "if");
|
|
4973
|
-
if (!ifArgument) continue;
|
|
4974
|
-
const value = directiveIfValue(info, ifArgument.value);
|
|
4975
|
-
if (directiveName === "skip" ? !!value : !value) return true;
|
|
4976
|
-
}
|
|
4977
|
-
return false;
|
|
4978
|
-
};
|
|
4979
|
-
/** The type a fragment's `on Type` condition names, looked up in the executing schema. */
|
|
4980
|
-
const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
|
|
4981
|
-
/**
|
|
4982
|
-
* Introspection meta-fields are not schema fields and never reach the database. `__typename` is
|
|
4983
|
-
* the one that turns up in practice; `__id` is excluded from the rule because a schema is free
|
|
4984
|
-
* to declare a field by that name.
|
|
4985
|
-
*/
|
|
4986
|
-
const isMetaField = (name) => name.startsWith("__") && name !== "__id";
|
|
4987
|
-
/**
|
|
4988
|
-
* Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
|
|
4989
|
-
* level, this owns the entries under `parentType`'s name.
|
|
4990
|
-
*
|
|
4991
|
-
* `activeFragments` is the set of fragment names on the current spread path. A document that
|
|
4992
|
-
* passed validation cannot contain a fragment cycle, but this parser also runs on trees a
|
|
4993
|
-
* caller assembled by hand, and an unbounded walk would hang rather than fail.
|
|
4994
|
-
*/
|
|
4995
|
-
const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
|
|
4996
|
-
const parentTypeName = parentType?.name;
|
|
4997
|
-
if (!parentTypeName) return tree;
|
|
4998
|
-
let fields = tree[parentTypeName];
|
|
4999
|
-
if (!fields) {
|
|
5000
|
-
fields = {};
|
|
5001
|
-
tree[parentTypeName] = fields;
|
|
5002
|
-
}
|
|
5003
|
-
const parentFields = fieldsOf(parentType);
|
|
5004
|
-
for (const selection of selections) {
|
|
5005
|
-
if (isExcluded(info, selection)) continue;
|
|
5006
|
-
if (selection.kind === graphql.Kind.FIELD) {
|
|
5007
|
-
const name = selection.name.value;
|
|
5008
|
-
if (isMetaField(name)) continue;
|
|
5009
|
-
const fieldDef = parentFields?.[name];
|
|
5010
|
-
if (!fieldDef) continue;
|
|
5011
|
-
const fieldType = namedTypeOf(fieldDef.type);
|
|
5012
|
-
if (!fieldType) continue;
|
|
5013
|
-
const composite = isCompositeLike(fieldType);
|
|
5014
|
-
const alias = selection.alias?.value || name;
|
|
5015
|
-
let node = fields[alias];
|
|
5016
|
-
if (!node) {
|
|
5017
|
-
node = {
|
|
5018
|
-
name,
|
|
5019
|
-
alias,
|
|
5020
|
-
args: { ...(0, graphql.getArgumentValues)(fieldDef, selection, info.variableValues) },
|
|
5021
|
-
fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
|
|
5022
|
-
};
|
|
5023
|
-
fields[alias] = node;
|
|
5024
|
-
}
|
|
5025
|
-
if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
|
|
5026
|
-
continue;
|
|
5027
|
-
}
|
|
5028
|
-
if (!deep) continue;
|
|
5029
|
-
if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
|
|
5030
|
-
const fragmentName = selection.name.value;
|
|
5031
|
-
const fragment = info.fragments?.[fragmentName];
|
|
5032
|
-
if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
|
|
5033
|
-
if (activeFragments.has(fragmentName)) continue;
|
|
5034
|
-
const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
|
|
5035
|
-
if (!isCompositeLike(fragmentType)) continue;
|
|
5036
|
-
activeFragments.add(fragmentName);
|
|
5037
|
-
try {
|
|
5038
|
-
collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
5039
|
-
} finally {
|
|
5040
|
-
activeFragments.delete(fragmentName);
|
|
5041
|
-
}
|
|
5042
|
-
continue;
|
|
5043
|
-
}
|
|
5044
|
-
if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
|
|
5045
|
-
const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
|
|
5046
|
-
if (!isCompositeLike(fragmentType)) continue;
|
|
5047
|
-
collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
5048
|
-
}
|
|
5049
|
-
}
|
|
5050
|
-
return tree;
|
|
5051
|
-
};
|
|
5052
|
-
/** The first key of an object, without materializing the whole key array to discard it. */
|
|
5053
|
-
const firstKey = (object) => {
|
|
5054
|
-
for (const key in object) return key;
|
|
5055
|
-
};
|
|
5056
|
-
/**
|
|
5057
|
-
* Reads the selection under a resolver's own field out of its `info`, as a tree of
|
|
5058
|
-
* {@link ResolveTree} nodes.
|
|
5059
|
-
*
|
|
5060
|
-
* ```ts
|
|
5061
|
-
* const parsed = parseResolveInfo(info, { deep: true })!;
|
|
5062
|
-
* const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
|
|
5063
|
-
* ```
|
|
5064
|
-
*
|
|
5065
|
-
* Returns `undefined` when the resolver's field produced no node at all, which in practice
|
|
5066
|
-
* means `info` carried no field nodes.
|
|
5067
|
-
*/
|
|
5068
|
-
const parseResolveInfo = (info, options = {}) => {
|
|
5069
|
-
const fieldNodes = info.fieldNodes;
|
|
5070
|
-
if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
|
|
5071
|
-
const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
|
|
5072
|
-
const typeKey = firstKey(tree);
|
|
5073
|
-
const fields = typeKey === void 0 ? void 0 : tree[typeKey];
|
|
5074
|
-
const fieldKey = fields && firstKey(fields);
|
|
5075
|
-
return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
|
|
5076
|
-
};
|
|
5077
|
-
//#endregion
|
|
5078
5159
|
//#region src/util/builders/aggregates.ts
|
|
5079
5160
|
/** Operations that aggregate over a set of column values. `count` is handled separately (whole rows). */
|
|
5080
5161
|
const AGGREGATE_OPS = [
|
|
@@ -5959,7 +6040,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
5959
6040
|
};
|
|
5960
6041
|
/** The values the junction stores for the target rows one operand selects. */
|
|
5961
6042
|
const selectedTargetKeys = async (executor, plan, filter, operation, scope) => {
|
|
5962
|
-
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);
|
|
6043
|
+
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);
|
|
5963
6044
|
};
|
|
5964
6045
|
/**
|
|
5965
6046
|
* The link half of a many-to-many write: junction rows are inserted and deleted, and both
|
|
@@ -5974,7 +6055,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
5974
6055
|
const through = plan.through;
|
|
5975
6056
|
const unlinkAll = () => executor.delete(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key));
|
|
5976
6057
|
const link = async (operands, operation) => {
|
|
5977
|
-
const existing = new Set((await executor.select().from(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
|
|
6058
|
+
const existing = new Set((await executor.select({ [through.targetColPropName]: through.targetCol }).from(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
|
|
5978
6059
|
const rows = [];
|
|
5979
6060
|
for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
|
|
5980
6061
|
existing.add(targetKey);
|
|
@@ -6035,11 +6116,11 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6035
6116
|
assertSingleOperation(plan, op);
|
|
6036
6117
|
if (op["create"] !== void 0) {
|
|
6037
6118
|
const values = applyContextValues(remapFromGraphQLSingleInput({ ...op["create"] }, plan.targetTable), contextValues?.(plan.targetTableName), context);
|
|
6038
|
-
const created = (await executor.insert(plan.targetTable).values(values).returning())[0];
|
|
6119
|
+
const created = (await executor.insert(plan.targetTable).values(values).returning({ [plan.foreignColPropName]: plan.foreignCol }))[0];
|
|
6039
6120
|
if (!created) throw drizzleError(`Drizzle-GraphQL Error: 'create' on '${relationName}' inserted no row, so there is nothing to attach.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
|
|
6040
6121
|
patch[plan.localColPropName] = created[plan.foreignColPropName];
|
|
6041
6122
|
} else if (op["connect"] !== void 0) {
|
|
6042
|
-
const rows = await executor.select().from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
6123
|
+
const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
6043
6124
|
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" });
|
|
6044
6125
|
patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
|
|
6045
6126
|
} else if (op["disconnect"] === true) patch[plan.localColPropName] = null;
|
|
@@ -6577,7 +6658,14 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
|
|
|
6577
6658
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
6578
6659
|
await before();
|
|
6579
6660
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
6580
|
-
const filters =
|
|
6661
|
+
const filters = scopedWhere({
|
|
6662
|
+
scope,
|
|
6663
|
+
tableName,
|
|
6664
|
+
table,
|
|
6665
|
+
where,
|
|
6666
|
+
relationCtx,
|
|
6667
|
+
required: single || requireWhere
|
|
6668
|
+
});
|
|
6581
6669
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
6582
6670
|
let query = executor.update(table).set(input);
|
|
6583
6671
|
if (filters) query = query.where(filters);
|
|
@@ -6659,7 +6747,15 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
|
|
|
6659
6747
|
const hard = canHardDelete && args.hard === true;
|
|
6660
6748
|
const scope = policies?.scope?.(context);
|
|
6661
6749
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
6662
|
-
const filters =
|
|
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
|
+
});
|
|
6663
6759
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
6664
6760
|
let query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
|
|
6665
6761
|
if (filters) query = query.where(filters);
|
|
@@ -6887,8 +6983,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6887
6983
|
const contextColumns = policies?.contextValues?.(tableName);
|
|
6888
6984
|
const scope = policies?.scope?.(context);
|
|
6889
6985
|
const input = nestedEntries ? [] : applyContextValuesAll(remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : args.values, table), contextColumns, context);
|
|
6890
|
-
const
|
|
6891
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
6986
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
6892
6987
|
relationMap,
|
|
6893
6988
|
tables,
|
|
6894
6989
|
tableName,
|
|
@@ -6897,7 +6992,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6897
6992
|
resolveName,
|
|
6898
6993
|
table,
|
|
6899
6994
|
pkNames,
|
|
6900
|
-
parsedInfo,
|
|
6901
6995
|
limits,
|
|
6902
6996
|
scope,
|
|
6903
6997
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -6918,7 +7012,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6918
7012
|
context
|
|
6919
7013
|
}) : await runInsert(executor, input);
|
|
6920
7014
|
await after(result);
|
|
6921
|
-
const enriched =
|
|
7015
|
+
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
6922
7016
|
return remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
6923
7017
|
}
|
|
6924
7018
|
});
|
|
@@ -6942,8 +7036,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6942
7036
|
const contextColumns = policies?.contextValues?.(tableName);
|
|
6943
7037
|
const scope = policies?.scope?.(context);
|
|
6944
7038
|
const input = nestedEntry ? {} : applyContextValues(remapFromGraphQLSingleInput(entry ? entry.columns : args.values, table), contextColumns, context);
|
|
6945
|
-
const
|
|
6946
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7039
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
6947
7040
|
relationMap,
|
|
6948
7041
|
tables,
|
|
6949
7042
|
tableName,
|
|
@@ -6952,7 +7045,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6952
7045
|
resolveName,
|
|
6953
7046
|
table,
|
|
6954
7047
|
pkNames,
|
|
6955
|
-
parsedInfo,
|
|
6956
7048
|
limits,
|
|
6957
7049
|
scope,
|
|
6958
7050
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -6977,7 +7069,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6977
7069
|
if (!conflictDoNothing) throw drizzleError("The insert returned no row.", { code: "DRIZZLE_NO_ROW_RETURNED" });
|
|
6978
7070
|
return;
|
|
6979
7071
|
}
|
|
6980
|
-
const enriched =
|
|
7072
|
+
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
6981
7073
|
return remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap);
|
|
6982
7074
|
}
|
|
6983
7075
|
});
|
|
@@ -7015,8 +7107,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7015
7107
|
const contextColumns = policies?.contextValues?.(tableName);
|
|
7016
7108
|
const scope = policies?.scope?.(context);
|
|
7017
7109
|
const input = nestedEntries ? [] : applyContextValuesAll(remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : supplied, table), contextColumns, context);
|
|
7018
|
-
const
|
|
7019
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7110
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
7020
7111
|
relationMap,
|
|
7021
7112
|
tables,
|
|
7022
7113
|
tableName,
|
|
@@ -7025,7 +7116,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7025
7116
|
resolveName,
|
|
7026
7117
|
table,
|
|
7027
7118
|
pkNames,
|
|
7028
|
-
parsedInfo,
|
|
7029
7119
|
limits,
|
|
7030
7120
|
scope,
|
|
7031
7121
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -7062,7 +7152,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7062
7152
|
}) : await runUpsert(executor, input);
|
|
7063
7153
|
await after(result);
|
|
7064
7154
|
if (single && !result[0]) return;
|
|
7065
|
-
const enriched =
|
|
7155
|
+
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
7066
7156
|
return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
7067
7157
|
}
|
|
7068
7158
|
});
|
|
@@ -7086,8 +7176,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7086
7176
|
const { where, set } = args;
|
|
7087
7177
|
const scope = policies?.scope?.(context);
|
|
7088
7178
|
await before();
|
|
7089
|
-
const
|
|
7090
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7179
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
7091
7180
|
relationMap,
|
|
7092
7181
|
tables,
|
|
7093
7182
|
tableName,
|
|
@@ -7096,7 +7185,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7096
7185
|
resolveName,
|
|
7097
7186
|
table,
|
|
7098
7187
|
pkNames,
|
|
7099
|
-
parsedInfo,
|
|
7100
7188
|
limits,
|
|
7101
7189
|
scope,
|
|
7102
7190
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -7106,7 +7194,14 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7106
7194
|
const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
|
|
7107
7195
|
if (!Object.keys(input).length && !nestedOps) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7108
7196
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7109
|
-
const filters =
|
|
7197
|
+
const filters = scopedWhere({
|
|
7198
|
+
scope,
|
|
7199
|
+
tableName,
|
|
7200
|
+
table,
|
|
7201
|
+
where,
|
|
7202
|
+
relationCtx,
|
|
7203
|
+
required: single || requireWhere
|
|
7204
|
+
});
|
|
7110
7205
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
7111
7206
|
const returning = nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns;
|
|
7112
7207
|
const runUpdate = async (target, values) => {
|
|
@@ -7128,7 +7223,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7128
7223
|
await after(result);
|
|
7129
7224
|
if (single && result.length > 1) throw drizzleError("'where' matched more than one row!", { code: "DRIZZLE_MULTI_ROW_MATCH" });
|
|
7130
7225
|
if (single && !result[0]) return;
|
|
7131
|
-
const enriched =
|
|
7226
|
+
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
7132
7227
|
return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
7133
7228
|
}
|
|
7134
7229
|
});
|
|
@@ -7170,7 +7265,15 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7170
7265
|
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
7171
7266
|
const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
|
|
7172
7267
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7173
|
-
const filters =
|
|
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
|
+
});
|
|
7174
7277
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
7175
7278
|
let query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
|
|
7176
7279
|
if (filters) query = query.where(filters);
|
|
@@ -7499,8 +7602,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7499
7602
|
await before();
|
|
7500
7603
|
const scope = policies?.scope?.(context);
|
|
7501
7604
|
const contextColumns = policies?.contextValues?.(tableName);
|
|
7502
|
-
const
|
|
7503
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7605
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
7504
7606
|
relationMap,
|
|
7505
7607
|
tables,
|
|
7506
7608
|
tableName,
|
|
@@ -7509,7 +7611,6 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7509
7611
|
resolveName,
|
|
7510
7612
|
table,
|
|
7511
7613
|
pkNames,
|
|
7512
|
-
parsedInfo,
|
|
7513
7614
|
limits,
|
|
7514
7615
|
scope,
|
|
7515
7616
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -7538,7 +7639,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7538
7639
|
});
|
|
7539
7640
|
const flatRows = perEntry.flat();
|
|
7540
7641
|
await after(flatRows);
|
|
7541
|
-
const enriched =
|
|
7642
|
+
const enriched = await withEagerRelations(executor, tableName, flatRows, pkNames, withParams, hasRelations);
|
|
7542
7643
|
const output = [];
|
|
7543
7644
|
let offset = 0;
|
|
7544
7645
|
for (const rows of perEntry) {
|