Skip to content

Commit 3d09e18

Browse files
longilitykettanaito
authored andcommitted
feat(gql): support TypedDocumentNode
1 parent a1aa7db commit 3d09e18

7 files changed

Lines changed: 359 additions & 5 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
],
6565
"sideEffects": false,
6666
"dependencies": {
67+
"@graphql-typed-document-node/core": "^3.1.0",
6768
"@mswjs/cookies": "^0.1.5",
6869
"@mswjs/interceptors": "^0.10.0",
6970
"@open-draft/until": "^1.0.3",

src/graphql.test-data.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
2+
export type Maybe<T> = T | null
3+
export type Exact<T extends { [key: string]: unknown }> = {
4+
[K in keyof T]: T[K]
5+
}
6+
export type MakeOptional<T, K extends keyof T> = Omit<T, K> &
7+
{ [SubKey in K]?: Maybe<T[SubKey]> }
8+
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> &
9+
{ [SubKey in K]: Maybe<T[SubKey]> }
10+
/** All built-in and custom scalars, mapped to their actual values */
11+
export type Scalars = {
12+
ID: string
13+
String: string
14+
Boolean: boolean
15+
Int: number
16+
Float: number
17+
}
18+
19+
export type Mutation = {
20+
__typename?: 'Mutation'
21+
login: User
22+
}
23+
24+
export type MutationLoginArgs = {
25+
username: Scalars['String']
26+
}
27+
28+
export type Query = {
29+
__typename?: 'Query'
30+
user: User
31+
}
32+
33+
export type User = {
34+
__typename?: 'User'
35+
id: Scalars['ID']
36+
firstName: Scalars['String']
37+
age: Scalars['Int']
38+
}
39+
40+
export type GetUserDetailQueryVariables = Exact<{
41+
userId: Scalars['String']
42+
}>
43+
44+
export type GetUserDetailQuery = { __typename?: 'Query' } & {
45+
user: { __typename?: 'User' } & Pick<User, 'id' | 'firstName' | 'age'>
46+
}
47+
48+
export type LoginMutationVariables = Exact<{
49+
username: Scalars['String']
50+
}>
51+
52+
export type LoginMutation = { __typename?: 'Mutation' } & {
53+
login: { __typename?: 'User' } & Pick<User, 'id'>
54+
}
55+
56+
export const GetUserDetailDocument = ({
57+
kind: 'Document',
58+
definitions: [
59+
{
60+
kind: 'OperationDefinition',
61+
operation: 'query',
62+
name: { kind: 'Name', value: 'GetUserDetail' },
63+
variableDefinitions: [
64+
{
65+
kind: 'VariableDefinition',
66+
variable: {
67+
kind: 'Variable',
68+
name: { kind: 'Name', value: 'userId' },
69+
},
70+
type: {
71+
kind: 'NonNullType',
72+
type: {
73+
kind: 'NamedType',
74+
name: { kind: 'Name', value: 'String' },
75+
},
76+
},
77+
},
78+
],
79+
selectionSet: {
80+
kind: 'SelectionSet',
81+
selections: [
82+
{
83+
kind: 'Field',
84+
name: { kind: 'Name', value: 'user' },
85+
selectionSet: {
86+
kind: 'SelectionSet',
87+
selections: [
88+
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
89+
{ kind: 'Field', name: { kind: 'Name', value: 'firstName' } },
90+
{ kind: 'Field', name: { kind: 'Name', value: 'age' } },
91+
],
92+
},
93+
},
94+
],
95+
},
96+
},
97+
],
98+
} as unknown) as DocumentNode<GetUserDetailQuery, GetUserDetailQueryVariables>
99+
export const LoginDocument = ({
100+
kind: 'Document',
101+
definitions: [
102+
{
103+
kind: 'OperationDefinition',
104+
operation: 'mutation',
105+
name: { kind: 'Name', value: 'Login' },
106+
variableDefinitions: [
107+
{
108+
kind: 'VariableDefinition',
109+
variable: {
110+
kind: 'Variable',
111+
name: { kind: 'Name', value: 'username' },
112+
},
113+
type: {
114+
kind: 'NonNullType',
115+
type: {
116+
kind: 'NamedType',
117+
name: { kind: 'Name', value: 'String' },
118+
},
119+
},
120+
},
121+
],
122+
selectionSet: {
123+
kind: 'SelectionSet',
124+
selections: [
125+
{
126+
kind: 'Field',
127+
name: { kind: 'Name', value: 'login' },
128+
arguments: [
129+
{
130+
kind: 'Argument',
131+
name: { kind: 'Name', value: 'username' },
132+
value: {
133+
kind: 'Variable',
134+
name: { kind: 'Name', value: 'username' },
135+
},
136+
},
137+
],
138+
selectionSet: {
139+
kind: 'SelectionSet',
140+
selections: [
141+
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
142+
],
143+
},
144+
},
145+
],
146+
},
147+
},
148+
],
149+
} as unknown) as DocumentNode<LoginMutation, LoginMutationVariables>

src/graphql.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
import { graphql } from './graphql'
2+
import {
3+
GetUserDetailDocument,
4+
GetUserDetailQueryVariables,
5+
LoginDocument,
6+
LoginMutationVariables,
7+
} from './graphql.test-data'
28

39
test('exports supported GraphQL operation types', () => {
410
expect(graphql).toBeDefined()
@@ -9,3 +15,25 @@ test('exports supported GraphQL operation types', () => {
915
'link',
1016
])
1117
})
18+
19+
test('pass proper type to request variable and data payload parameter for query', () => {
20+
const op = graphql.query(GetUserDetailDocument, (req, res, ctx) => {
21+
// If type doesn't match, this should fail compilation
22+
const variables: GetUserDetailQueryVariables = req.variables
23+
24+
// MANUAL VERIFICATION: intellisense on `ctx.data` should be of type `DataContext<GetUserDetailQuery>`.
25+
// Not sure how to test this.
26+
ctx.data
27+
})
28+
})
29+
30+
test('pass proper type to request variable and data payload parameter for mutation', () => {
31+
const op = graphql.mutation(LoginDocument, (req, res, ctx) => {
32+
// If type doesn't match, this should fail compilation
33+
const variables: LoginMutationVariables = req.variables
34+
35+
// MANUAL VERIFICATION: intellisense on `ctx.data` should be of type `DataContext<LoginMutation>`.
36+
// Not sure how to test this.
37+
ctx.data
38+
})
39+
})

src/graphql.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Mask } from './setupWorker/glossary'
22
import { ResponseResolver } from './handlers/RequestHandler'
3+
import { TypedDocumentNode } from '@graphql-typed-document-node/core'
4+
import { DocumentNode } from 'graphql'
35
import {
46
GraphQLHandler,
57
GraphQLContext,
@@ -17,7 +19,10 @@ function createScopedGraphQLHandler(
1719
Query extends Record<string, any>,
1820
Variables extends GraphQLVariables = GraphQLVariables
1921
>(
20-
operationName: GraphQLHandlerNameSelector,
22+
operationName:
23+
| GraphQLHandlerNameSelector
24+
| TypedDocumentNode<Query, Variables>
25+
| DocumentNode,
2126
resolver: ResponseResolver<
2227
GraphQLRequest<Variables>,
2328
GraphQLContext<Query>

src/handlers/GraphQLHandler.test.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { DocumentNode } from 'graphql'
12
import { Headers } from 'headers-utils/lib'
23
import { context } from '..'
34
import { createMockedRequest } from '../../test/support/utils'
5+
import { GetUserDetailDocument, LoginDocument } from '../graphql.test-data'
46
import { response } from '../response'
57
import {
68
GraphQLContext,
@@ -79,6 +81,134 @@ describe('info', () => {
7981
expect(handler.info).toHaveProperty('operationType', 'mutation')
8082
expect(handler.info).toHaveProperty('operationName', 'Login')
8183
})
84+
85+
test('parses operation name out of DocumentNode for query', () => {
86+
const handler = new GraphQLHandler(
87+
'query',
88+
GetUserDetailDocument,
89+
'*',
90+
resolver,
91+
)
92+
93+
expect(handler.info).toHaveProperty(
94+
'header',
95+
'query GetUserDetail (origin: *)',
96+
)
97+
expect(handler.info).toHaveProperty('operationType', 'query')
98+
expect(handler.info).toHaveProperty('operationName', 'GetUserDetail')
99+
})
100+
101+
test('parses operation name out of DocumentNode for mutation', () => {
102+
const handler = new GraphQLHandler('mutation', LoginDocument, '*', resolver)
103+
104+
expect(handler.info).toHaveProperty('header', 'mutation Login (origin: *)')
105+
expect(handler.info).toHaveProperty('operationType', 'mutation')
106+
expect(handler.info).toHaveProperty('operationName', 'Login')
107+
})
108+
109+
test('throws exception for mismatch operation type on DocumentNode for query', () => {
110+
const getUserDetailDocument: DocumentNode = {
111+
kind: 'Document',
112+
definitions: [
113+
{
114+
kind: 'OperationDefinition',
115+
operation: 'mutation',
116+
name: { kind: 'Name', value: 'GetUserDetail' },
117+
variableDefinitions: [
118+
{
119+
kind: 'VariableDefinition',
120+
variable: {
121+
kind: 'Variable',
122+
name: { kind: 'Name', value: 'userId' },
123+
},
124+
type: {
125+
kind: 'NonNullType',
126+
type: {
127+
kind: 'NamedType',
128+
name: { kind: 'Name', value: 'String' },
129+
},
130+
},
131+
},
132+
],
133+
selectionSet: {
134+
kind: 'SelectionSet',
135+
selections: [
136+
{
137+
kind: 'Field',
138+
name: { kind: 'Name', value: 'user' },
139+
selectionSet: {
140+
kind: 'SelectionSet',
141+
selections: [
142+
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
143+
],
144+
},
145+
},
146+
],
147+
},
148+
},
149+
],
150+
}
151+
expect(
152+
() => new GraphQLHandler('query', getUserDetailDocument, '*', resolver),
153+
).toThrowError()
154+
})
155+
156+
test('throws exception for mismatch operation type on DocumentNode for mutation', () => {
157+
const loginDocument: DocumentNode = {
158+
kind: 'Document',
159+
definitions: [
160+
{
161+
kind: 'OperationDefinition',
162+
operation: 'query',
163+
name: { kind: 'Name', value: 'Login' },
164+
variableDefinitions: [
165+
{
166+
kind: 'VariableDefinition',
167+
variable: {
168+
kind: 'Variable',
169+
name: { kind: 'Name', value: 'username' },
170+
},
171+
type: {
172+
kind: 'NonNullType',
173+
type: {
174+
kind: 'NamedType',
175+
name: { kind: 'Name', value: 'String' },
176+
},
177+
},
178+
},
179+
],
180+
selectionSet: {
181+
kind: 'SelectionSet',
182+
selections: [
183+
{
184+
kind: 'Field',
185+
name: { kind: 'Name', value: 'login' },
186+
arguments: [
187+
{
188+
kind: 'Argument',
189+
name: { kind: 'Name', value: 'username' },
190+
value: {
191+
kind: 'Variable',
192+
name: { kind: 'Name', value: 'username' },
193+
},
194+
},
195+
],
196+
selectionSet: {
197+
kind: 'SelectionSet',
198+
selections: [
199+
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
200+
],
201+
},
202+
},
203+
],
204+
},
205+
},
206+
],
207+
}
208+
expect(
209+
() => new GraphQLHandler('mutation', loginDocument, '*', resolver),
210+
).toThrowError()
211+
})
82212
})
83213

84214
describe('parse', () => {

0 commit comments

Comments
 (0)