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) =>
new GraphQLObjectType({
name,
fields: {
docs: {
type: new GraphQLList(docType),
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(docType))),
},
hasNextPage: { type: GraphQLBoolean },
hasPrevPage: { type: GraphQLBoolean },
limit: { type: GraphQLInt },
nextPage: { type: GraphQLInt },
hasNextPage: { type: new GraphQLNonNull(GraphQLBoolean) },
hasPrevPage: { type: new GraphQLNonNull(GraphQLBoolean) },
limit: { type: new GraphQLNonNull(GraphQLInt) },
nextPage: { type: new GraphQLNonNull(GraphQLInt) },
offset: { type: GraphQLInt },
page: { type: GraphQLInt },
pagingCounter: { type: GraphQLInt },
prevPage: { type: GraphQLInt },
totalDocs: { type: GraphQLInt },
totalPages: { type: GraphQLInt },
page: { type: new GraphQLNonNull(GraphQLInt) },
pagingCounter: { type: new GraphQLNonNull(GraphQLInt) },
prevPage: { type: new GraphQLNonNull(GraphQLInt) },
totalDocs: { type: new GraphQLNonNull(GraphQLInt) },
totalPages: { type: new GraphQLNonNull(GraphQLInt) },
},
})

View File

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

File diff suppressed because it is too large Load Diff