Skip to content
This repository was archived by the owner on Dec 8, 2021. It is now read-only.

Commit eb2f3cf

Browse files
koenpuntJason Kuhrt
authored andcommitted
feat(gg): generate resolver types for GQL interfaces, unions (#149)
closes #347, #346
1 parent 8963510 commit eb2f3cf

File tree

16 files changed

+1168
-182
lines changed

16 files changed

+1168
-182
lines changed

packages/graphqlgen/benchmarks/micro/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as Benchmark from '../lib/benchmark'
33

44
const type = {
55
name: 'Z',
6+
implements: null,
67
type: {
78
name: 'Z',
89
isInput: false,
@@ -18,6 +19,7 @@ const type = {
1819
const typeMap: Core.InputTypesMap = {
1920
A: {
2021
name: 'A',
22+
implements: null,
2123
type: {
2224
name: 'A',
2325
isInput: true,
@@ -41,6 +43,7 @@ const typeMap: Core.InputTypesMap = {
4143
isUnion: false,
4244
isRequired: false,
4345
isArray: false,
46+
isArrayRequired: false,
4447
},
4548
},
4649
{
@@ -56,12 +59,14 @@ const typeMap: Core.InputTypesMap = {
5659
isUnion: false,
5760
isRequired: false,
5861
isArray: false,
62+
isArrayRequired: false,
5963
},
6064
},
6165
],
6266
},
6367
B: {
6468
name: 'B',
69+
implements: null,
6570
type: {
6671
name: 'B',
6772
isInput: true,
@@ -85,12 +90,14 @@ const typeMap: Core.InputTypesMap = {
8590
isUnion: false,
8691
isRequired: false,
8792
isArray: false,
93+
isArrayRequired: false,
8894
},
8995
},
9096
],
9197
},
9298
C: {
9399
name: 'C',
100+
implements: null,
94101
type: {
95102
name: 'C',
96103
isInput: true,
@@ -114,12 +121,14 @@ const typeMap: Core.InputTypesMap = {
114121
isUnion: false,
115122
isRequired: false,
116123
isArray: false,
124+
isArrayRequired: false,
117125
},
118126
},
119127
],
120128
},
121129
D: {
122130
name: 'D',
131+
implements: null,
123132
type: {
124133
name: 'D',
125134
isInput: true,
@@ -143,6 +152,7 @@ const typeMap: Core.InputTypesMap = {
143152
isUnion: false,
144153
isRequired: false,
145154
isArray: false,
155+
isArrayRequired: false,
146156
},
147157
},
148158
],

packages/graphqlgen/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"watch": "tsc -w",
2121
"lint": "tslint --project tsconfig.json {src,test}/**/*.ts",
2222
"test": "jest",
23+
"check:types": "yarn tsc --noEmit",
2324
"test:watch": "jest --watch",
2425
"test:ci": "npm run lint && jest --maxWorkers 4",
2526
"gen": "ts-node --files src/index.ts"

packages/graphqlgen/src/generators/common.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import * as Source from '../source-helper'
12
import * as Common from './common'
23

34
it('getDistinctInputTypes', () => {
4-
const Z = {
5+
const Z: Source.GraphQLTypeObject = {
56
name: 'Z',
67
type: {
78
name: 'Z',
@@ -13,11 +14,13 @@ it('getDistinctInputTypes', () => {
1314
isUnion: false,
1415
},
1516
fields: [],
17+
implements: null,
1618
}
1719

1820
const typeMap: Common.InputTypesMap = {
1921
A: {
2022
name: 'A',
23+
implements: null,
2124
type: {
2225
name: 'A',
2326
isInput: true,
@@ -64,6 +67,7 @@ it('getDistinctInputTypes', () => {
6467
},
6568
B: {
6669
name: 'B',
70+
implements: null,
6771
type: {
6872
name: 'B',
6973
isInput: true,
@@ -94,6 +98,7 @@ it('getDistinctInputTypes', () => {
9498
},
9599
C: {
96100
name: 'C',
101+
implements: null,
97102
type: {
98103
name: 'C',
99104
isInput: true,
@@ -124,6 +129,7 @@ it('getDistinctInputTypes', () => {
124129
},
125130
D: {
126131
name: 'D',
132+
implements: null,
127133
type: {
128134
name: 'D',
129135
isInput: true,

packages/graphqlgen/src/generators/common.ts

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import * as os from 'os'
22

33
import {
44
GraphQLTypeObject,
5-
GraphQLType,
5+
GraphQLTypeDefinition,
66
GraphQLTypeField,
77
getGraphQLEnumValues,
8+
GraphQLInterfaceObject,
9+
GraphQLUnionObject,
810
} from '../source-helper'
911
import { ModelMap, ContextDefinition, GenerateArgs, Model } from '../types'
1012
import {
@@ -28,6 +30,24 @@ export interface TypeToInputTypeAssociation {
2830
[objectTypeName: string]: string[]
2931
}
3032

33+
export type InterfacesMap = Record<string, GraphQLTypeDefinition[]>
34+
35+
export const createInterfacesMap = (
36+
interfaces: GraphQLInterfaceObject[],
37+
): InterfacesMap =>
38+
interfaces.reduce<InterfacesMap>((interfacesMap, inter) => {
39+
interfacesMap[inter.name] = inter.implementors
40+
return interfacesMap
41+
}, {})
42+
43+
export type UnionsMap = Record<string, GraphQLTypeDefinition[]>
44+
45+
export const createUnionsMap = (unions: GraphQLUnionObject[]): UnionsMap =>
46+
unions.reduce<UnionsMap>((unionsMap, union) => {
47+
unionsMap[union.name] = union.types
48+
return unionsMap
49+
}, {})
50+
3151
export function fieldsFromModelDefinition(
3252
modelDef: TypeDefinition,
3353
): FieldDefinition[] {
@@ -114,7 +134,7 @@ export function getContextName(context?: ContextDefinition) {
114134
}
115135

116136
export function getModelName(
117-
type: GraphQLType,
137+
type: GraphQLTypeDefinition,
118138
modelMap: ModelMap,
119139
emptyType: string = '{}',
120140
): string {
@@ -199,21 +219,63 @@ const kv = (
199219
return `${key}${isOptional ? '?' : ''}: ${value}`
200220
}
201221

202-
const array = (innerType: string, config: { innerUnion?: boolean } = {}) => {
222+
const array = (
223+
innerType: string,
224+
config: { innerUnion?: boolean } = {},
225+
): string => {
203226
return config.innerUnion ? `${innerType}[]` : `Array<${innerType}>`
204227
}
205228

229+
const union = (types: string[]): string => {
230+
return types.join(' | ')
231+
}
232+
206233
type FieldPrintOptions = {
207234
isReturn?: boolean
208235
}
209236

210237
export const printFieldLikeType = (
211238
field: GraphQLTypeField,
212239
modelMap: ModelMap,
240+
interfacesMap: InterfacesMap,
241+
unionsMap: UnionsMap,
213242
options: FieldPrintOptions = {
214243
isReturn: false,
215244
},
216245
): string => {
246+
if (field.type.isInterface || field.type.isUnion) {
247+
const typesMap = field.type.isInterface ? interfacesMap : unionsMap
248+
249+
const modelNames = typesMap[field.type.name].map(type =>
250+
getModelName(type, modelMap),
251+
)
252+
253+
let rendering = union(modelNames)
254+
255+
if (!field.type.isRequired) {
256+
rendering = nullable(rendering)
257+
}
258+
259+
if (field.type.isArray) {
260+
rendering = array(rendering, { innerUnion: false })
261+
}
262+
263+
if (!field.type.isArrayRequired) {
264+
rendering = nullable(rendering)
265+
}
266+
267+
// We do not have to handle defaults becuase graphql only
268+
// supports defaults on field params but conversely
269+
// interfaces and unions are only supported on output. Therefore
270+
// these two features will never cross.
271+
272+
// No check for isReturn option because unions and interfaces
273+
// cannot be used to type graphql field parameters which implies
274+
// this branch will always be for a return case.
275+
276+
return rendering
277+
}
278+
217279
const name = field.type.isScalar
218280
? getTypeFromGraphQLType(field.type.name)
219281
: field.type.isInput || field.type.isEnum
@@ -335,11 +397,13 @@ export function isParentType(name: string) {
335397

336398
export function groupModelsNameByImportPath(models: Model[]) {
337399
return models.reduce<{ [importPath: string]: string[] }>((acc, model) => {
338-
if (acc[model.importPathRelativeToOutput] === undefined) {
339-
acc[model.importPathRelativeToOutput] = []
400+
const fileModels = acc[model.importPathRelativeToOutput] || []
401+
402+
if (!fileModels.includes(model.definition.name)) {
403+
fileModels.push(model.definition.name)
340404
}
341405

342-
acc[model.importPathRelativeToOutput].push(model.definition.name)
406+
acc[model.importPathRelativeToOutput] = fileModels
343407

344408
return acc
345409
}, {})

0 commit comments

Comments
 (0)