Skip to content

Commit

Permalink
Update typescript, typescript-operations and typescript-resolvers plu…
Browse files Browse the repository at this point in the history
…gins 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
  • Loading branch information
eddeee888 committed May 15, 2023
1 parent 63b25cd commit ba84a3a
Show file tree
Hide file tree
Showing 77 changed files with 5,829 additions and 5,388 deletions.
139 changes: 139 additions & 0 deletions .changeset/gold-frogs-explain.md
@@ -0,0 +1,139 @@
---
'@graphql-codegen/visitor-plugin-common': minor
'@graphql-codegen/typescript-operations': minor
'@graphql-codegen/typescript': minor
'@graphql-codegen/typescript-resolvers': minor
---

Implement Scalars with input/output types

In GraphQL, Scalar types can be different for client and server. For example, given the native GraphQL ID:
- A client may send `string` or `number` in the input
- A client receives `string` in its selection set (i.e output)
- A server receives `string` in the resolver (GraphQL parses `string` or `number` received from the client to `string`)
- A server may return `string` or `number` (GraphQL serializes the value to `string` before sending it to the client )

Currently, we represent every Scalar with only one type. This is what codegen generates as base type:

```ts
export type Scalars = {
ID: string;
}
```
Then, this is used in both input and output type e.g.
```ts
export type Book = {
__typename?: "Book";
id: Scalars["ID"]; // Output's ID can be `string` 👍
};

export type QueryBookArgs = {
id: Scalars["ID"]; // Input's ID can be `string` or `number`. However, the type is only `string` here 👎
};
```

This PR extends each Scalar to have input and output:

```ts
export type Scalars = {
ID: {
input: string | number;
output: string;
}
}
```
Then, each input/output GraphQL type can correctly refer to the correct input/output scalar type:
```ts
export type Book = {
__typename?: "Book";
id: Scalars["ID"]['output']; // Output's ID can be `string` 👍
};

export type QueryBookArgs = {
id: Scalars["ID"]['input']; // Input's ID can be `string` or `number` 👍
};
```

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:

```ts
export type Scalars = {
ID: {
input: string;
output: string | number;
}
}

export type Book = {
__typename?: "Book";
id: Scalars["ID"]['output']; // Resolvers can return `string` or `number` in ID fields 👍
};

export type QueryBookArgs = {
id: Scalars["ID"]['input']; // Resolvers receive `string` in ID fields 👍
};

export type ResolversTypes = {
ID: ID: ResolverTypeWrapper<Scalars['ID']['output']>; // Resolvers can return `string` or `number` in ID fields 👍
}

export type ResolversParentTypes = {
ID: Scalars['ID']['output']; // Resolvers receive `string` or `number` from parents 👍
};
```

---

Config changes:

1. Scalars option can now take input/output types:

```ts
config: {
scalars: {
ID: {
input: 'string',
output: 'string | number'
}
}
}
```

2. If a string is given (instead of an object with input/output fields), it will be used as both input and output types:

```ts
config: {
scalars: {
ID: 'string' // This means `string` will be used for both ID's input and output types
}
}
```

3. (Potential) BREAKING CHANGE: External module Scalar types need to be an object with input/output fields

```ts
config: {
scalars: {
ID: './path/to/scalar-module'
}
}
```

If correctly, wired up, the following will be generated:

```ts
// Previously, imported `ID` type can be a primitive type, now it must be an object with input/output fields
import { ID } from "./path/to/scalar-module";

export type Scalars = {
ID: { input: ID['input']; output: ID['output']; }
};
```

---

(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.
88 changes: 44 additions & 44 deletions dev-test/githunt/typed-document-nodes.ts
Expand Up @@ -8,55 +8,55 @@ export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> =
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
ID: { input: string | number; output: string };
String: { input: string; output: string };
Boolean: { input: boolean; output: boolean };
Int: { input: number; output: number };
Float: { input: number; output: number };
};

/** A comment about an entry, submitted by a user */
export type Comment = {
__typename?: 'Comment';
/** The text of the comment */
content: Scalars['String'];
content: Scalars['String']['output'];
/** A timestamp of when the comment was posted */
createdAt: Scalars['Float'];
createdAt: Scalars['Float']['output'];
/** The SQL ID of this entry */
id: Scalars['Int'];
id: Scalars['Int']['output'];
/** The GitHub user who posted the comment */
postedBy: User;
/** The repository which this comment is about */
repoName: Scalars['String'];
repoName: Scalars['String']['output'];
};

/** Information about a GitHub repository submitted to GitHunt */
export type Entry = {
__typename?: 'Entry';
/** The number of comments posted about this repository */
commentCount: Scalars['Int'];
commentCount: Scalars['Int']['output'];
/** Comments posted about this repository */
comments: Array<Maybe<Comment>>;
/** A timestamp of when the entry was submitted */
createdAt: Scalars['Float'];
createdAt: Scalars['Float']['output'];
/** The hot score of this repository */
hotScore: Scalars['Float'];
hotScore: Scalars['Float']['output'];
/** The SQL ID of this entry */
id: Scalars['Int'];
id: Scalars['Int']['output'];
/** The GitHub user who submitted this entry */
postedBy: User;
/** Information about the repository from GitHub */
repository: Repository;
/** The score of this repository, upvotes - downvotes */
score: Scalars['Int'];
score: Scalars['Int']['output'];
/** XXX to be changed */
vote: Vote;
};

/** Information about a GitHub repository submitted to GitHunt */
export type EntryCommentsArgs = {
limit?: InputMaybe<Scalars['Int']>;
offset?: InputMaybe<Scalars['Int']>;
limit?: InputMaybe<Scalars['Int']['input']>;
offset?: InputMaybe<Scalars['Int']['input']>;
};

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

export type MutationSubmitCommentArgs = {
commentContent: Scalars['String'];
repoFullName: Scalars['String'];
commentContent: Scalars['String']['input'];
repoFullName: Scalars['String']['input'];
};

export type MutationSubmitRepositoryArgs = {
repoFullName: Scalars['String'];
repoFullName: Scalars['String']['input'];
};

export type MutationVoteArgs = {
repoFullName: Scalars['String'];
repoFullName: Scalars['String']['input'];
type: VoteType;
};

Expand All @@ -104,12 +104,12 @@ export type Query = {
};

export type QueryEntryArgs = {
repoFullName: Scalars['String'];
repoFullName: Scalars['String']['input'];
};

export type QueryFeedArgs = {
limit?: InputMaybe<Scalars['Int']>;
offset?: InputMaybe<Scalars['Int']>;
limit?: InputMaybe<Scalars['Int']['input']>;
offset?: InputMaybe<Scalars['Int']['input']>;
type: FeedType;
};

Expand All @@ -120,19 +120,19 @@ export type QueryFeedArgs = {
export type Repository = {
__typename?: 'Repository';
/** The description of the repository */
description?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']['output']>;
/** The full name of the repository with the username, e.g. apollostack/GitHunt-API */
full_name: Scalars['String'];
full_name: Scalars['String']['output'];
/** The link to the repository on GitHub */
html_url: Scalars['String'];
html_url: Scalars['String']['output'];
/** Just the name of the repository, e.g. GitHunt-API */
name: Scalars['String'];
name: Scalars['String']['output'];
/** The number of open issues on this repository on GitHub */
open_issues_count?: Maybe<Scalars['Int']>;
open_issues_count?: Maybe<Scalars['Int']['output']>;
/** The owner of this repository on GitHub, e.g. apollostack */
owner?: Maybe<User>;
/** The number of people who have starred this repository on GitHub */
stargazers_count: Scalars['Int'];
stargazers_count: Scalars['Int']['output'];
};

export type Subscription = {
Expand All @@ -142,24 +142,24 @@ export type Subscription = {
};

export type SubscriptionCommentAddedArgs = {
repoFullName: Scalars['String'];
repoFullName: Scalars['String']['input'];
};

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

/** XXX to be removed */
export type Vote = {
__typename?: 'Vote';
vote_value: Scalars['Int'];
vote_value: Scalars['Int']['output'];
};

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

export type OnCommentAddedSubscriptionVariables = Exact<{
repoFullName: Scalars['String'];
repoFullName: Scalars['String']['input'];
}>;

export type OnCommentAddedSubscription = {
Expand All @@ -185,9 +185,9 @@ export type OnCommentAddedSubscription = {
};

export type CommentQueryVariables = Exact<{
repoFullName: Scalars['String'];
limit?: InputMaybe<Scalars['Int']>;
offset?: InputMaybe<Scalars['Int']>;
repoFullName: Scalars['String']['input'];
limit?: InputMaybe<Scalars['Int']['input']>;
offset?: InputMaybe<Scalars['Int']['input']>;
}>;

export type CommentQuery = {
Expand Down Expand Up @@ -253,8 +253,8 @@ export type FeedEntryFragment = {

export type FeedQueryVariables = Exact<{
type: FeedType;
offset?: InputMaybe<Scalars['Int']>;
limit?: InputMaybe<Scalars['Int']>;
offset?: InputMaybe<Scalars['Int']['input']>;
limit?: InputMaybe<Scalars['Int']['input']>;
}>;

export type FeedQuery = {
Expand All @@ -281,7 +281,7 @@ export type FeedQuery = {
};

export type SubmitRepositoryMutationVariables = Exact<{
repoFullName: Scalars['String'];
repoFullName: Scalars['String']['input'];
}>;

export type SubmitRepositoryMutation = {
Expand All @@ -302,8 +302,8 @@ export type RepoInfoFragment = {
};

export type SubmitCommentMutationVariables = Exact<{
repoFullName: Scalars['String'];
commentContent: Scalars['String'];
repoFullName: Scalars['String']['input'];
commentContent: Scalars['String']['input'];
}>;

export type SubmitCommentMutation = {
Expand All @@ -324,7 +324,7 @@ export type VoteButtonsFragment = {
};

export type VoteMutationVariables = Exact<{
repoFullName: Scalars['String'];
repoFullName: Scalars['String']['input'];
type: VoteType;
}>;

Expand Down

0 comments on commit ba84a3a

Please sign in to comment.