graphql / graphql/graphql-spec

Hint that a field of an union or interface type will resolve only to types specified in the selection set

Open
#951 6 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
14.6k
Forks
1.2k
PR merge metrics
No merged PRs in 30d

Description

**Note: this is a very early thought-dump**

Given the following schema and operation:

```graphql
interface Searchable {
id: ID!
}

type User implements Searchable {
id: ID!
name: String!
}

type Post implements Searchable {
id: ID!
}

type Comment implements Searchable {
id: ID!
}

type Query {
search: [Searchable!]!
}
```

```graphql
query Search($needle: String!) {
search(needle: $needle) {
... on User {
id
name
}
}
}
```

The generated TypeScript type must be:

```typescript
type SearchQuery = {
search: { id?: never; name?: never } | { id: string; name: string };
};
```

The `Query.search` resolver implementation could process the selection set and only resolve to types that are actually requested via a fragment/inline-fragment.

```ts
import { GraphQLObjectType, GraphQLNonNull, GraphQLList } from "graphql";
import { parseResolveInfo } from "graphql-parse-resolve-info";
import { GraphQLSearchable } from "./searchable";

const items = [
{ type: "User", id: "User:1", name: "Laurin" },
{ type: "Post", id: "Post:1", title: "My first post." },
{ type: "Comment", id: "Comment:1", content: "Hi Laurin" }
];

const GraphQLQueryType = new GraphQLObjectType({
name: "Query",
fields: () => ({
search: {
type: new GraphQLNonNull(
new GraphQLList(new GraphQLNonNull(GraphQLSearchableType))
),
resolve(_source, args, _context, info) {
const requestedTypeNames = Object.keys(
parseResolveInfo(info)?.fieldsByTypeName ?? {}
);

return items.filter((item) => requestedTypeNames.includes(item.type));
}
}
})
});
```

Full runnable example over here: https://codesandbox.io/s/graphql-yoga-interface-field-only-resolve-selected-types-bzw7b6

---

It could be helpful if there was a way of hinting via the schema (and introspection) that a specific field of an interface or union type will only resolve to those types that are explicitly selected via fragment/inline-fragments on the selection set within the executed document. E.g. via a directive.

```graphql
type Query {
search: [Searchable!]! @onlyResolveToSelectedEntities
}
```

For the following operation:

```graphql
query Search($needle: String!) {
search(needle: $needle) {
... on User {
id
name
}
}
}
```

instead, we can now generate

```typescript
type SearchQuery = {
search: { id: string; name: string };
};
```

For the following operation:

```graphql
query Search($needle: String!) {
search(needle: $needle) {
... on User {
id
name
}
... on Comment {
id
content
}
}
}
```

instead, we can now generate

```typescript
type SearchQuery = {
search:
| { id: string; name: string; content?: never }
| { id: string; name?: never; content: string };
};
```

In addition, the GraphQL execution algorithm could take that instruction into account and raise an exception in case the resolved type would not match the selected types within the selection set.

So far I am unsure what should happen if you select an `interface` type on such a field. I guess tools should interpret this as "this can now resolve to all entities that implement the interface", which would be future-proof in case a new interface member was added.

```graphql
query Search($needle: String!) {
search(needle: $needle) {
... on Searchable {
id
}
}
}
```

```typescript
type SearchQuery = {
search: { id: string };
};
```

---

This approach makes sense for union types, where there are currently [no shared fields](https://github.com/graphql/graphql-spec/pull/939).

It could furthermore help people build more resilient and future-proof GraphQL schemas.

---

Related issues:

- https://github.com/dotansimha/graphql-code-generator/pull/7782
- https://github.com/graphql/graphql-spec/pull/950
- https://github.com/graphql/graphql-spec/pull/939

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.