Skip to content

Commit ba84a3a

Browse files
authored
Update typescript, typescript-operations and typescript-resolvers plugins Scalars input/output type (#9375)
* Implement Scalar input/output types - Use Scalars input/output in base typescript plugin - Use Scalars in typescript-operations plugin - Update typescript-resolvers to support input/output - Update related website docs * Make version bump minor * Fix changeset * Fix unit tests * Update tests
1 parent 63b25cd commit ba84a3a

File tree

77 files changed

+5829
-5388
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+5829
-5388
lines changed

.changeset/gold-frogs-explain.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
---
2+
'@graphql-codegen/visitor-plugin-common': minor
3+
'@graphql-codegen/typescript-operations': minor
4+
'@graphql-codegen/typescript': minor
5+
'@graphql-codegen/typescript-resolvers': minor
6+
---
7+
8+
Implement Scalars with input/output types
9+
10+
In GraphQL, Scalar types can be different for client and server. For example, given the native GraphQL ID:
11+
- A client may send `string` or `number` in the input
12+
- A client receives `string` in its selection set (i.e output)
13+
- A server receives `string` in the resolver (GraphQL parses `string` or `number` received from the client to `string`)
14+
- A server may return `string` or `number` (GraphQL serializes the value to `string` before sending it to the client )
15+
16+
Currently, we represent every Scalar with only one type. This is what codegen generates as base type:
17+
18+
```ts
19+
export type Scalars = {
20+
ID: string;
21+
}
22+
```
23+
24+
Then, this is used in both input and output type e.g.
25+
26+
```ts
27+
export type Book = {
28+
__typename?: "Book";
29+
id: Scalars["ID"]; // Output's ID can be `string` 👍
30+
};
31+
32+
export type QueryBookArgs = {
33+
id: Scalars["ID"]; // Input's ID can be `string` or `number`. However, the type is only `string` here 👎
34+
};
35+
```
36+
37+
This PR extends each Scalar to have input and output:
38+
39+
```ts
40+
export type Scalars = {
41+
ID: {
42+
input: string | number;
43+
output: string;
44+
}
45+
}
46+
```
47+
48+
Then, each input/output GraphQL type can correctly refer to the correct input/output scalar type:
49+
50+
```ts
51+
export type Book = {
52+
__typename?: "Book";
53+
id: Scalars["ID"]['output']; // Output's ID can be `string` 👍
54+
};
55+
56+
export type QueryBookArgs = {
57+
id: Scalars["ID"]['input']; // Input's ID can be `string` or `number` 👍
58+
};
59+
```
60+
61+
Note that for `typescript-resolvers`, the type of ID needs to be inverted. However, the referenced types in GraphQL input/output types should still work correctly:
62+
63+
```ts
64+
export type Scalars = {
65+
ID: {
66+
input: string;
67+
output: string | number;
68+
}
69+
}
70+
71+
export type Book = {
72+
__typename?: "Book";
73+
id: Scalars["ID"]['output']; // Resolvers can return `string` or `number` in ID fields 👍
74+
};
75+
76+
export type QueryBookArgs = {
77+
id: Scalars["ID"]['input']; // Resolvers receive `string` in ID fields 👍
78+
};
79+
80+
export type ResolversTypes = {
81+
ID: ID: ResolverTypeWrapper<Scalars['ID']['output']>; // Resolvers can return `string` or `number` in ID fields 👍
82+
}
83+
84+
export type ResolversParentTypes = {
85+
ID: Scalars['ID']['output']; // Resolvers receive `string` or `number` from parents 👍
86+
};
87+
```
88+
89+
---
90+
91+
Config changes:
92+
93+
1. Scalars option can now take input/output types:
94+
95+
```ts
96+
config: {
97+
scalars: {
98+
ID: {
99+
input: 'string',
100+
output: 'string | number'
101+
}
102+
}
103+
}
104+
```
105+
106+
2. If a string is given (instead of an object with input/output fields), it will be used as both input and output types:
107+
108+
```ts
109+
config: {
110+
scalars: {
111+
ID: 'string' // This means `string` will be used for both ID's input and output types
112+
}
113+
}
114+
```
115+
116+
3. (Potential) BREAKING CHANGE: External module Scalar types need to be an object with input/output fields
117+
118+
```ts
119+
config: {
120+
scalars: {
121+
ID: './path/to/scalar-module'
122+
}
123+
}
124+
```
125+
126+
If correctly, wired up, the following will be generated:
127+
128+
```ts
129+
// Previously, imported `ID` type can be a primitive type, now it must be an object with input/output fields
130+
import { ID } from "./path/to/scalar-module";
131+
132+
export type Scalars = {
133+
ID: { input: ID['input']; output: ID['output']; }
134+
};
135+
```
136+
137+
---
138+
139+
(Potential) BREAKING CHANGE: This changes Scalar types which could be referenced in other plugins. If you are a plugin maintainer and reference Scalar, please update your plugin to use the correct input/output types.

dev-test/githunt/typed-document-nodes.ts

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,55 @@ export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> =
88
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
99
/** All built-in and custom scalars, mapped to their actual values */
1010
export type Scalars = {
11-
ID: string;
12-
String: string;
13-
Boolean: boolean;
14-
Int: number;
15-
Float: number;
11+
ID: { input: string | number; output: string };
12+
String: { input: string; output: string };
13+
Boolean: { input: boolean; output: boolean };
14+
Int: { input: number; output: number };
15+
Float: { input: number; output: number };
1616
};
1717

1818
/** A comment about an entry, submitted by a user */
1919
export type Comment = {
2020
__typename?: 'Comment';
2121
/** The text of the comment */
22-
content: Scalars['String'];
22+
content: Scalars['String']['output'];
2323
/** A timestamp of when the comment was posted */
24-
createdAt: Scalars['Float'];
24+
createdAt: Scalars['Float']['output'];
2525
/** The SQL ID of this entry */
26-
id: Scalars['Int'];
26+
id: Scalars['Int']['output'];
2727
/** The GitHub user who posted the comment */
2828
postedBy: User;
2929
/** The repository which this comment is about */
30-
repoName: Scalars['String'];
30+
repoName: Scalars['String']['output'];
3131
};
3232

3333
/** Information about a GitHub repository submitted to GitHunt */
3434
export type Entry = {
3535
__typename?: 'Entry';
3636
/** The number of comments posted about this repository */
37-
commentCount: Scalars['Int'];
37+
commentCount: Scalars['Int']['output'];
3838
/** Comments posted about this repository */
3939
comments: Array<Maybe<Comment>>;
4040
/** A timestamp of when the entry was submitted */
41-
createdAt: Scalars['Float'];
41+
createdAt: Scalars['Float']['output'];
4242
/** The hot score of this repository */
43-
hotScore: Scalars['Float'];
43+
hotScore: Scalars['Float']['output'];
4444
/** The SQL ID of this entry */
45-
id: Scalars['Int'];
45+
id: Scalars['Int']['output'];
4646
/** The GitHub user who submitted this entry */
4747
postedBy: User;
4848
/** Information about the repository from GitHub */
4949
repository: Repository;
5050
/** The score of this repository, upvotes - downvotes */
51-
score: Scalars['Int'];
51+
score: Scalars['Int']['output'];
5252
/** XXX to be changed */
5353
vote: Vote;
5454
};
5555

5656
/** Information about a GitHub repository submitted to GitHunt */
5757
export type EntryCommentsArgs = {
58-
limit?: InputMaybe<Scalars['Int']>;
59-
offset?: InputMaybe<Scalars['Int']>;
58+
limit?: InputMaybe<Scalars['Int']['input']>;
59+
offset?: InputMaybe<Scalars['Int']['input']>;
6060
};
6161

6262
/** A list of options for the sort order of the feed */
@@ -80,16 +80,16 @@ export type Mutation = {
8080
};
8181

8282
export type MutationSubmitCommentArgs = {
83-
commentContent: Scalars['String'];
84-
repoFullName: Scalars['String'];
83+
commentContent: Scalars['String']['input'];
84+
repoFullName: Scalars['String']['input'];
8585
};
8686

8787
export type MutationSubmitRepositoryArgs = {
88-
repoFullName: Scalars['String'];
88+
repoFullName: Scalars['String']['input'];
8989
};
9090

9191
export type MutationVoteArgs = {
92-
repoFullName: Scalars['String'];
92+
repoFullName: Scalars['String']['input'];
9393
type: VoteType;
9494
};
9595

@@ -104,12 +104,12 @@ export type Query = {
104104
};
105105

106106
export type QueryEntryArgs = {
107-
repoFullName: Scalars['String'];
107+
repoFullName: Scalars['String']['input'];
108108
};
109109

110110
export type QueryFeedArgs = {
111-
limit?: InputMaybe<Scalars['Int']>;
112-
offset?: InputMaybe<Scalars['Int']>;
111+
limit?: InputMaybe<Scalars['Int']['input']>;
112+
offset?: InputMaybe<Scalars['Int']['input']>;
113113
type: FeedType;
114114
};
115115

@@ -120,19 +120,19 @@ export type QueryFeedArgs = {
120120
export type Repository = {
121121
__typename?: 'Repository';
122122
/** The description of the repository */
123-
description?: Maybe<Scalars['String']>;
123+
description?: Maybe<Scalars['String']['output']>;
124124
/** The full name of the repository with the username, e.g. apollostack/GitHunt-API */
125-
full_name: Scalars['String'];
125+
full_name: Scalars['String']['output'];
126126
/** The link to the repository on GitHub */
127-
html_url: Scalars['String'];
127+
html_url: Scalars['String']['output'];
128128
/** Just the name of the repository, e.g. GitHunt-API */
129-
name: Scalars['String'];
129+
name: Scalars['String']['output'];
130130
/** The number of open issues on this repository on GitHub */
131-
open_issues_count?: Maybe<Scalars['Int']>;
131+
open_issues_count?: Maybe<Scalars['Int']['output']>;
132132
/** The owner of this repository on GitHub, e.g. apollostack */
133133
owner?: Maybe<User>;
134134
/** The number of people who have starred this repository on GitHub */
135-
stargazers_count: Scalars['Int'];
135+
stargazers_count: Scalars['Int']['output'];
136136
};
137137

138138
export type Subscription = {
@@ -142,24 +142,24 @@ export type Subscription = {
142142
};
143143

144144
export type SubscriptionCommentAddedArgs = {
145-
repoFullName: Scalars['String'];
145+
repoFullName: Scalars['String']['input'];
146146
};
147147

148148
/** A user object from the GitHub API. This uses the exact field names returned from the GitHub API. */
149149
export type User = {
150150
__typename?: 'User';
151151
/** The URL to a directly embeddable image for this user's avatar */
152-
avatar_url: Scalars['String'];
152+
avatar_url: Scalars['String']['output'];
153153
/** The URL of this user's GitHub page */
154-
html_url: Scalars['String'];
154+
html_url: Scalars['String']['output'];
155155
/** The name of the user, e.g. apollostack */
156-
login: Scalars['String'];
156+
login: Scalars['String']['output'];
157157
};
158158

159159
/** XXX to be removed */
160160
export type Vote = {
161161
__typename?: 'Vote';
162-
vote_value: Scalars['Int'];
162+
vote_value: Scalars['Int']['output'];
163163
};
164164

165165
/** The type of vote to record, when submitting a vote */
@@ -170,7 +170,7 @@ export enum VoteType {
170170
}
171171

172172
export type OnCommentAddedSubscriptionVariables = Exact<{
173-
repoFullName: Scalars['String'];
173+
repoFullName: Scalars['String']['input'];
174174
}>;
175175

176176
export type OnCommentAddedSubscription = {
@@ -185,9 +185,9 @@ export type OnCommentAddedSubscription = {
185185
};
186186

187187
export type CommentQueryVariables = Exact<{
188-
repoFullName: Scalars['String'];
189-
limit?: InputMaybe<Scalars['Int']>;
190-
offset?: InputMaybe<Scalars['Int']>;
188+
repoFullName: Scalars['String']['input'];
189+
limit?: InputMaybe<Scalars['Int']['input']>;
190+
offset?: InputMaybe<Scalars['Int']['input']>;
191191
}>;
192192

193193
export type CommentQuery = {
@@ -253,8 +253,8 @@ export type FeedEntryFragment = {
253253

254254
export type FeedQueryVariables = Exact<{
255255
type: FeedType;
256-
offset?: InputMaybe<Scalars['Int']>;
257-
limit?: InputMaybe<Scalars['Int']>;
256+
offset?: InputMaybe<Scalars['Int']['input']>;
257+
limit?: InputMaybe<Scalars['Int']['input']>;
258258
}>;
259259

260260
export type FeedQuery = {
@@ -281,7 +281,7 @@ export type FeedQuery = {
281281
};
282282

283283
export type SubmitRepositoryMutationVariables = Exact<{
284-
repoFullName: Scalars['String'];
284+
repoFullName: Scalars['String']['input'];
285285
}>;
286286

287287
export type SubmitRepositoryMutation = {
@@ -302,8 +302,8 @@ export type RepoInfoFragment = {
302302
};
303303

304304
export type SubmitCommentMutationVariables = Exact<{
305-
repoFullName: Scalars['String'];
306-
commentContent: Scalars['String'];
305+
repoFullName: Scalars['String']['input'];
306+
commentContent: Scalars['String']['input'];
307307
}>;
308308

309309
export type SubmitCommentMutation = {
@@ -324,7 +324,7 @@ export type VoteButtonsFragment = {
324324
};
325325

326326
export type VoteMutationVariables = Exact<{
327-
repoFullName: Scalars['String'];
327+
repoFullName: Scalars['String']['input'];
328328
type: VoteType;
329329
}>;
330330

0 commit comments

Comments
 (0)