dotansimha / dotansimha/graphql-code-generator

Support an incremental adoption of Fragment Masking

Open
#9,075 4 comments 19 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
11.3k
Forks
1.4k
Avg merge
1d 1h
Merged PRs (30d)
23

Description

### Is your feature request related to a problem? Please describe.

I recently migrated a project to `client-preset` (from `gql-tag-operations-preset`) and would like to turn on Fragment Masking for it. Unfortunately, because of existing fragments that are not using Fragment Masking, we already have a thousand errors to handle to be able to.

When you enable Fragment Masking, all fragments, and operations are automatically masked, even though not all fragments or operations use Fragment Masking.

I would like the ability to unmask fragments recursively to **support an incremental adoption of Fragment Masking**.

Also related to:

- https://github.com/dotansimha/graphql-code-generator/discussions/9308
- https://github.com/dotansimha/graphql-code-generator/discussions/9325

#### Nested Fragment: ``

```tsx
export const UserAvatarUserFragment = gql(`
fragment UserAvatarUser on User {
avatarUrl
}
`);

type UserAvatarProps = {
user: ResultOf;
};

function UserAvatar(props: UserAvatarProps) {
return ;
}
```

#### Fragment: ``

```tsx
export const UserCardUserFragment = gql(`
fragment UserCardUser on User {
id
name
...UserAvatarUserFragment
}
`);

type UserCardProps = {
user: ResultOf;
};

function UserCard(props: UserCardProps) {
return (
<>

{/* ^ Property 'avatarUrl' is missing in type */}
{/* ^ Type '"UserCardUserFragment"' is not assignable to type '"UserAvatarUserFragment"' */}

ID: {props.user.id}

Name: {props.user.name}


);
}
```

#### Query: ``

```tsx
export const ExampleComponentQuery = gql(`
query ExampleComponent($id: String!) {
user(id: $id) {
...UserCardUser
}
}
`);

type ExampleComponentProps = {
id: VariablesOf['id'];
};

export function ExampleComponent(props: ExampleComponentProps) {
const { loading, data } = useQuery(ExampleComponentQuery, { variables: { id: props.id } });

if (loading) {
return <>loading...;
}

if (!data?.user) {
return <>no user;
}

return ;
}
```

### Describe the solution you'd like

We could expose the following utility type: `UnmaskResultOf`.

This would check if whether we're inside an operation or directly within a fragment and then recursively flatten fragments by resolving ` $fragmentRefs` and merging them to the root fragment.

```diff
type UserCardProps = {
- user: ResultOf;
- // ^? (property) user: { __typename: "User"; id: string; name: string; ' $fragmentRefs': { ... }}
+ user: UnmaskResultOf;
+ // ^? (property) user: { __typename: "User"; id: string; name: string; avatarUrl: string }
};
```

```ts
const testUnmaskResultOf: UnmaskResultOf = {
__typename: 'Query',
user: {
__typename: 'User',
id: 'some-id',
name: 'some-name',
avatarUrl: 'some-avatar-url',
},
};
```

See the implementation on TypeScript Playground: [Link](https://www.typescriptlang.org/play?#code/C4TwDgpgBAqgzhATgQQG4ENjsfJAxRdAcwFsIA7YKAXigG8oB9R0Sc9MgLigCJdEeAbijoMWHIgA23OMEQBLckSgBfKADJ6AKCi8oAEgBmhUhWAA5DhB4B+bnwQox2fgWJlKQrSsFatraH4AYWwAE1cTDypaAAo6HSYWcAore34vXXlQmTlFIl9ddi4oWQUlXzVNeN0eA2N3MwAlCEM4W24GfjRMF0c3U0puLuccPsizVQqASg16PSNxyksydt5gsIiGz0m-AKgAUQAPDjBJCCCAexIwC-IzAEUAVyQQGm1dZgCiiHsnl4yoI9HHYEroAD5QOKJL6pNaOWqVd66ZE1OqLYDNVqrTqOEKIcJjLbAIa4jaEgZUHyg5EqKbUiHkR6SSQVXxaAD07KgAFpeXz+QLBULhSLRWLubtklAAAqICDAYDyQwgAA8ABUAHxvapQADaAGkoIooABrCAgC6GKBqgC63DVBptFVmdCp-ilADV0JJnnAAPKGdVa2gOs0Wq22tl7GDkeS3NUXACSlCQCAAxorbiqYMHITAoBBDsAKKE4CJyK8bJCTUMZtQtagLlkoNw7qgkDNC8XyKXIQka0byIYkFBE1o6w2m6EElWx7pWxB24go1K8JJMN3+lE1ck4Cq8Lm8AWiyWywxagsiZi2nZB8PEFA8NfVDPH+uFRQt2Yd5A99TdLK8qKsqKr-sifokPIwD7gANPM9QUtetQQheCFRMs1hauoYEohBUGgSihHIjGcbkAmybFog6aZuQKpej6ED+oG5i3OYTLrgARmc+7XhqfEwThhGoeiGE8IJugamBkkotJLaPr4exrhuFB-IgIBfpQP6MSq9y5vcx7dr2DAGoOprmpaUD3Ha5avCor7GYaxphhZVncCx5Bscy6BcRA+7vpu6JaXu9yOnxL7zlAbZIL4nJQAABjGJDoHAJrNHATLAAGcVGnA5AAORUMAAAW0BgIgFyQA+8jXGcUSYKRUAAO7QI1FxMqEUBFaI0DGnFaaSPIZjcmVjHynFcHNXlzJQANZqSK8haQBmIjxVpoQACIXGmjzoRcoQQONUAcY8VDNSIcqAnAeQrVdNVKoNHVLldtydUg0DJU10ChLcBWdd1UDFRcCBGjVEB1TRZYlXKAB07qQLA5BJSlaUZQGQZvGqBmnnMwlXi0N7cOgFbhVAVZKR+5AacAQVBgk3Dk92qnqYFu5BmysXipzXPc5zkrwwzn7onAaqMcAACMbwC5TLO-tmpL4psFIajFXK6AAejYfhprcsgA6LUtU8Loti-T-mC0SNPrAr5JRLmOqfMk3xpPCAmZNkvBwFcEDclkPCu5FsI8J7ZDct8fsJKIPQSNIHte9ykfiNyjxSOHbqxQAsvIcBXUo8UJy4UjZYYg2SB1MTGFckWixAHVoWYdLa+QuvFrIBtCyLsgAEym8p0sW6zVsEvg6J2wksXImrMrlZVoBQHl+fR3lOVQJB2fXcaATQ1vCQO2wgfpP7WT2MH3u+-7TuxyHYcCW6ilm+QTMd+Lkv30zVM00cJxnJc1y3A8zxqWVhyVWUANZ80CIjZKqVGKo0MFpCWtBEpQJRpITKgZP6gx-jcO4lAmZAPHqAzWWhG7N1Fkg5GMDUEBhNgjJG0D0pUPQccTBVxsH-xeKPD4SQ97FB4EzcOuggRIA6NSXeKReEH2pEfS+p9QgCORBfIOcdr7UgXjAKQx844LyTinf2Kgb7sy5JnNeudvSSHikIxARcS6lmITrQqZDIEUIYWg7utDkGULQSqDBpxzisL-rggBIBOHQkdoHfh-tLEiJRLFSeWk54MDEYo9IwgVBLyzivLOOdlDFWgIYC4zILiNWumVCqSBFSMSgBXEgAMpR5USdw8RPw4RICEEad2pQ8jCAvp08oIgRjqJjr0-Iqg8rcCyHBb4cE1FSFEY05JLsEj6O8L4IAA).

> **Warning**: Pull request with the full implementation will follow.

### Describe alternatives you've considered

_No response_

### Is your feature request related to a problem? Please describe.

_No response_

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.