feat(graphql): improve non-nullability in query result types (#11952)

### What?
Makes several fields and list item types in query results (e.g. `docs`)
non-nullable.

### Why?
When dealing with code generated from a Payload GraphQL schema, it is
often necessary to use type guards and optional chaining.

For example:

```graphql
type Posts {
  docs: [Post]
  ...
}
```

This implies that the `docs` field itself is nullable and that the array
can contain nulls. In reality, neither of these is true. But because of
the types generated by tools like `graphql-code-generator`, the way to
access `posts` ends up something like this:

```ts
const posts = (query.data.docs ?? []).filter(doc => doc != null);
```

Instead, we would like the schema to be:

```graphql
type Posts {
  docs: [Post!]!
  ...
}
```


### How?
The proposed change involves adding `GraphQLNonNull` where appropriate.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
This commit is contained in:
reiv
2025-04-03 17:17:23 +02:00
committed by GitHub
parent 816fb28f55
commit 018bdad247
3 changed files with 2896 additions and 520 deletions

View File

@@ -1,21 +1,21 @@
import { GraphQLBoolean, GraphQLInt, GraphQLList, GraphQLObjectType } from 'graphql' import { GraphQLBoolean, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType } from 'graphql'
export const buildPaginatedListType = (name, docType) => export const buildPaginatedListType = (name, docType) =>
new GraphQLObjectType({ new GraphQLObjectType({
name, name,
fields: { fields: {
docs: { docs: {
type: new GraphQLList(docType), type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(docType))),
}, },
hasNextPage: { type: GraphQLBoolean }, hasNextPage: { type: new GraphQLNonNull(GraphQLBoolean) },
hasPrevPage: { type: GraphQLBoolean }, hasPrevPage: { type: new GraphQLNonNull(GraphQLBoolean) },
limit: { type: GraphQLInt }, limit: { type: new GraphQLNonNull(GraphQLInt) },
nextPage: { type: GraphQLInt }, nextPage: { type: new GraphQLNonNull(GraphQLInt) },
offset: { type: GraphQLInt }, offset: { type: GraphQLInt },
page: { type: GraphQLInt }, page: { type: new GraphQLNonNull(GraphQLInt) },
pagingCounter: { type: GraphQLInt }, pagingCounter: { type: new GraphQLNonNull(GraphQLInt) },
prevPage: { type: GraphQLInt }, prevPage: { type: new GraphQLNonNull(GraphQLInt) },
totalDocs: { type: GraphQLInt }, totalDocs: { type: new GraphQLNonNull(GraphQLInt) },
totalPages: { type: GraphQLInt }, totalPages: { type: new GraphQLNonNull(GraphQLInt) },
}, },
}) })

View File

@@ -348,11 +348,15 @@ export const fieldToSchemaMap: FieldToSchemaMap = {
name: joinName, name: joinName,
fields: { fields: {
docs: { docs: {
type: Array.isArray(field.collection) type: new GraphQLNonNull(
? GraphQLJSON Array.isArray(field.collection)
: new GraphQLList(graphqlResult.collections[field.collection].graphQL.type), ? GraphQLJSON
: new GraphQLList(
new GraphQLNonNull(graphqlResult.collections[field.collection].graphQL.type),
),
),
}, },
hasNextPage: { type: GraphQLBoolean }, hasNextPage: { type: new GraphQLNonNull(GraphQLBoolean) },
}, },
}), }),
args: { args: {
@@ -428,7 +432,7 @@ export const fieldToSchemaMap: FieldToSchemaMap = {
...objectTypeConfig, ...objectTypeConfig,
[formatName(field.name)]: formattedNameResolver({ [formatName(field.name)]: formattedNameResolver({
type: withNullableType({ type: withNullableType({
type: field?.hasMany === true ? new GraphQLList(type) : type, type: field?.hasMany === true ? new GraphQLList(new GraphQLNonNull(type)) : type,
field, field,
forceNullable, forceNullable,
parentIsLocalized, parentIsLocalized,
@@ -856,7 +860,10 @@ export const fieldToSchemaMap: FieldToSchemaMap = {
...objectTypeConfig, ...objectTypeConfig,
[formatName(field.name)]: formattedNameResolver({ [formatName(field.name)]: formattedNameResolver({
type: withNullableType({ type: withNullableType({
type: field.hasMany === true ? new GraphQLList(GraphQLString) : GraphQLString, type:
field.hasMany === true
? new GraphQLList(new GraphQLNonNull(GraphQLString))
: GraphQLString,
field, field,
forceNullable, forceNullable,
parentIsLocalized, parentIsLocalized,

File diff suppressed because it is too large Load Diff