@vantreeseba/drizzle-graphql 11.0.1 → 11.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +188 -158
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +188 -158
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3792,6 +3792,154 @@ const identifyRows = (field, args) => {
|
|
|
3792
3792
|
};
|
|
3793
3793
|
};
|
|
3794
3794
|
//#endregion
|
|
3795
|
+
//#region src/util/parse-resolve-info.ts
|
|
3796
|
+
/** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
|
|
3797
|
+
const namedTypeOf = (type) => {
|
|
3798
|
+
let current = type;
|
|
3799
|
+
while (current?.ofType) current = current.ofType;
|
|
3800
|
+
return current;
|
|
3801
|
+
};
|
|
3802
|
+
/**
|
|
3803
|
+
* Whether a field's named output type has sub-selections. Object types and interfaces expose
|
|
3804
|
+
* `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
|
|
3805
|
+
* also expose `getFields`, but an input type can never be a field's output type, so there is
|
|
3806
|
+
* nothing here for that to collide with.
|
|
3807
|
+
*/
|
|
3808
|
+
const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
|
|
3809
|
+
/**
|
|
3810
|
+
* The fields declared on a type, or `undefined` when it has none to declare. A union is the
|
|
3811
|
+
* case that matters: it has no fields of its own, so a selection directly against one resolves
|
|
3812
|
+
* to nothing and is dropped — only the inline fragments underneath contribute.
|
|
3813
|
+
*/
|
|
3814
|
+
const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
|
|
3815
|
+
/**
|
|
3816
|
+
* The runtime variable values, in the plain-map form the directive check wants. graphql 17
|
|
3817
|
+
* wraps them in `{ sources, coerced }`; graphql 16 hands over the map itself.
|
|
3818
|
+
*/
|
|
3819
|
+
const coercedVariables = (info) => {
|
|
3820
|
+
const values = info.variableValues;
|
|
3821
|
+
if (values && typeof values === "object" && "coerced" in values) return values.coerced ?? {};
|
|
3822
|
+
return values ?? {};
|
|
3823
|
+
};
|
|
3824
|
+
/** The value of a `@skip` / `@include` `if:` argument — a literal or a variable reference. */
|
|
3825
|
+
const directiveIfValue = (info, value) => {
|
|
3826
|
+
if (value.kind === graphql.Kind.VARIABLE) return coercedVariables(info)[value.name.value];
|
|
3827
|
+
if (value.kind === graphql.Kind.BOOLEAN) return value.value;
|
|
3828
|
+
};
|
|
3829
|
+
/**
|
|
3830
|
+
* Whether `@skip` / `@include` on this selection exclude it. Applies to fields, fragment
|
|
3831
|
+
* spreads and inline fragments alike, which is where graphql puts them.
|
|
3832
|
+
*/
|
|
3833
|
+
const isExcluded = (info, node) => {
|
|
3834
|
+
for (const directive of node.directives ?? []) {
|
|
3835
|
+
const directiveName = directive.name.value;
|
|
3836
|
+
if (directiveName !== "skip" && directiveName !== "include") continue;
|
|
3837
|
+
const ifArgument = directive.arguments?.find((argument) => argument.name.value === "if");
|
|
3838
|
+
if (!ifArgument) continue;
|
|
3839
|
+
const value = directiveIfValue(info, ifArgument.value);
|
|
3840
|
+
if (directiveName === "skip" ? !!value : !value) return true;
|
|
3841
|
+
}
|
|
3842
|
+
return false;
|
|
3843
|
+
};
|
|
3844
|
+
/** The type a fragment's `on Type` condition names, looked up in the executing schema. */
|
|
3845
|
+
const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
|
|
3846
|
+
/**
|
|
3847
|
+
* Introspection meta-fields are not schema fields and never reach the database. `__typename` is
|
|
3848
|
+
* the one that turns up in practice; `__id` is excluded from the rule because a schema is free
|
|
3849
|
+
* to declare a field by that name.
|
|
3850
|
+
*/
|
|
3851
|
+
const isMetaField = (name) => name.startsWith("__") && name !== "__id";
|
|
3852
|
+
/**
|
|
3853
|
+
* Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
|
|
3854
|
+
* level, this owns the entries under `parentType`'s name.
|
|
3855
|
+
*
|
|
3856
|
+
* `activeFragments` is the set of fragment names on the current spread path. A document that
|
|
3857
|
+
* passed validation cannot contain a fragment cycle, but this parser also runs on trees a
|
|
3858
|
+
* caller assembled by hand, and an unbounded walk would hang rather than fail.
|
|
3859
|
+
*/
|
|
3860
|
+
const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
|
|
3861
|
+
const parentTypeName = parentType?.name;
|
|
3862
|
+
if (!parentTypeName) return tree;
|
|
3863
|
+
let fields = tree[parentTypeName];
|
|
3864
|
+
if (!fields) {
|
|
3865
|
+
fields = {};
|
|
3866
|
+
tree[parentTypeName] = fields;
|
|
3867
|
+
}
|
|
3868
|
+
const parentFields = fieldsOf(parentType);
|
|
3869
|
+
for (const selection of selections) {
|
|
3870
|
+
if (isExcluded(info, selection)) continue;
|
|
3871
|
+
if (selection.kind === graphql.Kind.FIELD) {
|
|
3872
|
+
const name = selection.name.value;
|
|
3873
|
+
if (isMetaField(name)) continue;
|
|
3874
|
+
const fieldDef = parentFields?.[name];
|
|
3875
|
+
if (!fieldDef) continue;
|
|
3876
|
+
const fieldType = namedTypeOf(fieldDef.type);
|
|
3877
|
+
if (!fieldType) continue;
|
|
3878
|
+
const composite = isCompositeLike(fieldType);
|
|
3879
|
+
const alias = selection.alias?.value || name;
|
|
3880
|
+
let node = fields[alias];
|
|
3881
|
+
if (!node) {
|
|
3882
|
+
node = {
|
|
3883
|
+
name,
|
|
3884
|
+
alias,
|
|
3885
|
+
args: { ...(0, graphql.getArgumentValues)(fieldDef, selection, info.variableValues) },
|
|
3886
|
+
fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
|
|
3887
|
+
};
|
|
3888
|
+
fields[alias] = node;
|
|
3889
|
+
}
|
|
3890
|
+
if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
|
|
3891
|
+
continue;
|
|
3892
|
+
}
|
|
3893
|
+
if (!deep) continue;
|
|
3894
|
+
if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
|
|
3895
|
+
const fragmentName = selection.name.value;
|
|
3896
|
+
const fragment = info.fragments?.[fragmentName];
|
|
3897
|
+
if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
|
|
3898
|
+
if (activeFragments.has(fragmentName)) continue;
|
|
3899
|
+
const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
|
|
3900
|
+
if (!isCompositeLike(fragmentType)) continue;
|
|
3901
|
+
activeFragments.add(fragmentName);
|
|
3902
|
+
try {
|
|
3903
|
+
collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
3904
|
+
} finally {
|
|
3905
|
+
activeFragments.delete(fragmentName);
|
|
3906
|
+
}
|
|
3907
|
+
continue;
|
|
3908
|
+
}
|
|
3909
|
+
if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
|
|
3910
|
+
const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
|
|
3911
|
+
if (!isCompositeLike(fragmentType)) continue;
|
|
3912
|
+
collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
return tree;
|
|
3916
|
+
};
|
|
3917
|
+
/** The first key of an object, without materializing the whole key array to discard it. */
|
|
3918
|
+
const firstKey = (object) => {
|
|
3919
|
+
for (const key in object) return key;
|
|
3920
|
+
};
|
|
3921
|
+
/**
|
|
3922
|
+
* Reads the selection under a resolver's own field out of its `info`, as a tree of
|
|
3923
|
+
* {@link ResolveTree} nodes.
|
|
3924
|
+
*
|
|
3925
|
+
* ```ts
|
|
3926
|
+
* const parsed = parseResolveInfo(info, { deep: true })!;
|
|
3927
|
+
* const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
|
|
3928
|
+
* ```
|
|
3929
|
+
*
|
|
3930
|
+
* Returns `undefined` when the resolver's field produced no node at all, which in practice
|
|
3931
|
+
* means `info` carried no field nodes.
|
|
3932
|
+
*/
|
|
3933
|
+
const parseResolveInfo = (info, options = {}) => {
|
|
3934
|
+
const fieldNodes = info.fieldNodes;
|
|
3935
|
+
if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
|
|
3936
|
+
const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
|
|
3937
|
+
const typeKey = firstKey(tree);
|
|
3938
|
+
const fields = typeKey === void 0 ? void 0 : tree[typeKey];
|
|
3939
|
+
const fieldKey = fields && firstKey(fields);
|
|
3940
|
+
return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
|
|
3941
|
+
};
|
|
3942
|
+
//#endregion
|
|
3795
3943
|
//#region src/util/builders/common/relation-resolvers.ts
|
|
3796
3944
|
/**
|
|
3797
3945
|
* Fetches a to-many relation with per-parent limit/offset for ALL parents in a
|
|
@@ -3804,8 +3952,8 @@ const identifyRows = (field, args) => {
|
|
|
3804
3952
|
* Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
|
|
3805
3953
|
* Returns raw rows (NOT remapped); the caller groups + remaps them.
|
|
3806
3954
|
*/
|
|
3807
|
-
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, orderCtx, whereArgs) => {
|
|
3808
|
-
const cols = (0, drizzle_orm.getColumns)(targetTable);
|
|
3955
|
+
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs) => {
|
|
3956
|
+
const cols = columns ?? (0, drizzle_orm.getColumns)(targetTable);
|
|
3809
3957
|
const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
|
|
3810
3958
|
const orderClause = orderExprs.length ? drizzle_orm.sql` order by ${drizzle_orm.sql.join(orderExprs, drizzle_orm.sql`, `)}` : drizzle_orm.sql``;
|
|
3811
3959
|
const RN = "__drizzle_graphql_rn";
|
|
@@ -3851,12 +3999,39 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3851
3999
|
operation: "relation",
|
|
3852
4000
|
relation: relationName
|
|
3853
4001
|
};
|
|
4002
|
+
/**
|
|
4003
|
+
* The columns one batch reads: the ones the selection names, plus the ones the batch needs
|
|
4004
|
+
* whatever the client asked for — the key it groups the rows by, and the ordering columns a
|
|
4005
|
+
* row cursor is encoded from. Nested relation fields contribute their own join column
|
|
4006
|
+
* through the selection context, the same way the root select path narrows.
|
|
4007
|
+
*
|
|
4008
|
+
* `undefined` means "every column", which is what a resolver with no readable resolve info
|
|
4009
|
+
* has to fall back to.
|
|
4010
|
+
*/
|
|
4011
|
+
const selectedColumns = (info, cursorEntries) => {
|
|
4012
|
+
const parsed = info ? parseResolveInfo(info) : void 0;
|
|
4013
|
+
if (!parsed) return;
|
|
4014
|
+
const tree = Object.assign({}, ...Object.values(parsed.fieldsByTypeName));
|
|
4015
|
+
const columns = extractSelectedColumnsFromTreeSQLFormat(tree, targetTable, {
|
|
4016
|
+
tableName: targetTableName,
|
|
4017
|
+
relationMap: filterCtx?.relationMap ?? {},
|
|
4018
|
+
tables,
|
|
4019
|
+
allRelations: filterCtx?.relationMap
|
|
4020
|
+
});
|
|
4021
|
+
const targetColumns = (0, drizzle_orm.getColumns)(targetTable);
|
|
4022
|
+
columns[foreignColPropName] = foreignCol;
|
|
4023
|
+
for (const [columnName] of cursorEntries ?? []) {
|
|
4024
|
+
const column = targetColumns[columnName];
|
|
4025
|
+
if (column) columns[columnName] = column;
|
|
4026
|
+
}
|
|
4027
|
+
return columns;
|
|
4028
|
+
};
|
|
3854
4029
|
const resolve = async (parent, args, context, info) => {
|
|
3855
4030
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
3856
4031
|
const localValue = parent[localColPropName];
|
|
3857
4032
|
if (localValue == null) return isOne ? null : [];
|
|
3858
4033
|
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}`, () => {
|
|
4034
|
+
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
3860
4035
|
const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
|
|
3861
4036
|
const limit = applyLimitPolicy(requestedLimit, limitPolicy, errorCtx);
|
|
3862
4037
|
const distinct = (args ?? {}).distinct?.length ? (args ?? {}).distinct : void 0;
|
|
@@ -3871,6 +4046,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3871
4046
|
} else cursorEntries = cursorOrderingEntries(orderByArg, targetPkNames);
|
|
3872
4047
|
}
|
|
3873
4048
|
const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
|
|
4049
|
+
const columns = selectedColumns(info, cursorEntries);
|
|
3874
4050
|
const argsKey = JSON.stringify({
|
|
3875
4051
|
where: whereArg ?? null,
|
|
3876
4052
|
orderBy: orderByArg ?? null,
|
|
@@ -3879,7 +4055,8 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3879
4055
|
deleted: deleted ?? defaultDeleted ?? null,
|
|
3880
4056
|
after: after ?? null,
|
|
3881
4057
|
distinct: distinct ?? null,
|
|
3882
|
-
cursor: cursorEntries ? 1 : 0
|
|
4058
|
+
cursor: cursorEntries ? 1 : 0,
|
|
4059
|
+
columns: columns ? Object.keys(columns).sort() : null
|
|
3883
4060
|
});
|
|
3884
4061
|
return {
|
|
3885
4062
|
orderByArg,
|
|
@@ -3887,6 +4064,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3887
4064
|
distinct,
|
|
3888
4065
|
cursorEntries,
|
|
3889
4066
|
cursorValues,
|
|
4067
|
+
columns,
|
|
3890
4068
|
loaderKey: `${tableName}::${relationName}::${argsKey}`
|
|
3891
4069
|
};
|
|
3892
4070
|
});
|
|
@@ -3910,9 +4088,9 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3910
4088
|
whereCondition = (0, drizzle_orm.and)(whereCondition, primaryKeyRestriction(targetTable, targetPkNames, keys));
|
|
3911
4089
|
}
|
|
3912
4090
|
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);
|
|
4091
|
+
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, columns, relationFilterCtx(filterCtx, targetTableName), whereArg);
|
|
3914
4092
|
else {
|
|
3915
|
-
let q = executor.select().from(targetTable).where(whereCondition);
|
|
4093
|
+
let q = (columns ? executor.select(columns) : executor.select()).from(targetTable).where(whereCondition);
|
|
3916
4094
|
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
|
|
3917
4095
|
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
3918
4096
|
rows = await q;
|
|
@@ -4927,154 +5105,6 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
4927
5105
|
};
|
|
4928
5106
|
};
|
|
4929
5107
|
//#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
5108
|
//#region src/util/builders/aggregates.ts
|
|
5079
5109
|
/** Operations that aggregate over a set of column values. `count` is handled separately (whole rows). */
|
|
5080
5110
|
const AGGREGATE_OPS = [
|
|
@@ -5959,7 +5989,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
5959
5989
|
};
|
|
5960
5990
|
/** The values the junction stores for the target rows one operand selects. */
|
|
5961
5991
|
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);
|
|
5992
|
+
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
5993
|
};
|
|
5964
5994
|
/**
|
|
5965
5995
|
* The link half of a many-to-many write: junction rows are inserted and deleted, and both
|
|
@@ -5974,7 +6004,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
5974
6004
|
const through = plan.through;
|
|
5975
6005
|
const unlinkAll = () => executor.delete(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key));
|
|
5976
6006
|
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]));
|
|
6007
|
+
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
6008
|
const rows = [];
|
|
5979
6009
|
for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
|
|
5980
6010
|
existing.add(targetKey);
|
|
@@ -6035,11 +6065,11 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6035
6065
|
assertSingleOperation(plan, op);
|
|
6036
6066
|
if (op["create"] !== void 0) {
|
|
6037
6067
|
const values = applyContextValues(remapFromGraphQLSingleInput({ ...op["create"] }, plan.targetTable), contextValues?.(plan.targetTableName), context);
|
|
6038
|
-
const created = (await executor.insert(plan.targetTable).values(values).returning())[0];
|
|
6068
|
+
const created = (await executor.insert(plan.targetTable).values(values).returning({ [plan.foreignColPropName]: plan.foreignCol }))[0];
|
|
6039
6069
|
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
6070
|
patch[plan.localColPropName] = created[plan.foreignColPropName];
|
|
6041
6071
|
} else if (op["connect"] !== void 0) {
|
|
6042
|
-
const rows = await executor.select().from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
6072
|
+
const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
6043
6073
|
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
6074
|
patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
|
|
6045
6075
|
} else if (op["disconnect"] === true) patch[plan.localColPropName] = null;
|