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

Commit

Permalink
feat(gg): generate resolver types for GQL interfaces, unions (#149)
Browse files Browse the repository at this point in the history
closes #347, #346
  • Loading branch information
koenpunt authored and Jason Kuhrt committed Jan 20, 2019
1 parent 8963510 commit eb2f3cf
Show file tree
Hide file tree
Showing 16 changed files with 1,168 additions and 182 deletions.
10 changes: 10 additions & 0 deletions packages/graphqlgen/benchmarks/micro/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as Benchmark from '../lib/benchmark'

const type = {
name: 'Z',
implements: null,
type: {
name: 'Z',
isInput: false,
Expand All @@ -18,6 +19,7 @@ const type = {
const typeMap: Core.InputTypesMap = {
A: {
name: 'A',
implements: null,
type: {
name: 'A',
isInput: true,
Expand All @@ -41,6 +43,7 @@ const typeMap: Core.InputTypesMap = {
isUnion: false,
isRequired: false,
isArray: false,
isArrayRequired: false,
},
},
{
Expand All @@ -56,12 +59,14 @@ const typeMap: Core.InputTypesMap = {
isUnion: false,
isRequired: false,
isArray: false,
isArrayRequired: false,
},
},
],
},
B: {
name: 'B',
implements: null,
type: {
name: 'B',
isInput: true,
Expand All @@ -85,12 +90,14 @@ const typeMap: Core.InputTypesMap = {
isUnion: false,
isRequired: false,
isArray: false,
isArrayRequired: false,
},
},
],
},
C: {
name: 'C',
implements: null,
type: {
name: 'C',
isInput: true,
Expand All @@ -114,12 +121,14 @@ const typeMap: Core.InputTypesMap = {
isUnion: false,
isRequired: false,
isArray: false,
isArrayRequired: false,
},
},
],
},
D: {
name: 'D',
implements: null,
type: {
name: 'D',
isInput: true,
Expand All @@ -143,6 +152,7 @@ const typeMap: Core.InputTypesMap = {
isUnion: false,
isRequired: false,
isArray: false,
isArrayRequired: false,
},
},
],
Expand Down
1 change: 1 addition & 0 deletions packages/graphqlgen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"watch": "tsc -w",
"lint": "tslint --project tsconfig.json {src,test}/**/*.ts",
"test": "jest",
"check:types": "yarn tsc --noEmit",
"test:watch": "jest --watch",
"test:ci": "npm run lint && jest --maxWorkers 4",
"gen": "ts-node --files src/index.ts"
Expand Down
8 changes: 7 additions & 1 deletion packages/graphqlgen/src/generators/common.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as Source from '../source-helper'
import * as Common from './common'

it('getDistinctInputTypes', () => {
const Z = {
const Z: Source.GraphQLTypeObject = {
name: 'Z',
type: {
name: 'Z',
Expand All @@ -13,11 +14,13 @@ it('getDistinctInputTypes', () => {
isUnion: false,
},
fields: [],
implements: null,
}

const typeMap: Common.InputTypesMap = {
A: {
name: 'A',
implements: null,
type: {
name: 'A',
isInput: true,
Expand Down Expand Up @@ -64,6 +67,7 @@ it('getDistinctInputTypes', () => {
},
B: {
name: 'B',
implements: null,
type: {
name: 'B',
isInput: true,
Expand Down Expand Up @@ -94,6 +98,7 @@ it('getDistinctInputTypes', () => {
},
C: {
name: 'C',
implements: null,
type: {
name: 'C',
isInput: true,
Expand Down Expand Up @@ -124,6 +129,7 @@ it('getDistinctInputTypes', () => {
},
D: {
name: 'D',
implements: null,
type: {
name: 'D',
isInput: true,
Expand Down
76 changes: 70 additions & 6 deletions packages/graphqlgen/src/generators/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import * as os from 'os'

import {
GraphQLTypeObject,
GraphQLType,
GraphQLTypeDefinition,
GraphQLTypeField,
getGraphQLEnumValues,
GraphQLInterfaceObject,
GraphQLUnionObject,
} from '../source-helper'
import { ModelMap, ContextDefinition, GenerateArgs, Model } from '../types'
import {
Expand All @@ -28,6 +30,24 @@ export interface TypeToInputTypeAssociation {
[objectTypeName: string]: string[]
}

export type InterfacesMap = Record<string, GraphQLTypeDefinition[]>

export const createInterfacesMap = (
interfaces: GraphQLInterfaceObject[],
): InterfacesMap =>
interfaces.reduce<InterfacesMap>((interfacesMap, inter) => {
interfacesMap[inter.name] = inter.implementors
return interfacesMap
}, {})

export type UnionsMap = Record<string, GraphQLTypeDefinition[]>

export const createUnionsMap = (unions: GraphQLUnionObject[]): UnionsMap =>
unions.reduce<UnionsMap>((unionsMap, union) => {
unionsMap[union.name] = union.types
return unionsMap
}, {})

export function fieldsFromModelDefinition(
modelDef: TypeDefinition,
): FieldDefinition[] {
Expand Down Expand Up @@ -114,7 +134,7 @@ export function getContextName(context?: ContextDefinition) {
}

export function getModelName(
type: GraphQLType,
type: GraphQLTypeDefinition,
modelMap: ModelMap,
emptyType: string = '{}',
): string {
Expand Down Expand Up @@ -199,21 +219,63 @@ const kv = (
return `${key}${isOptional ? '?' : ''}: ${value}`
}

const array = (innerType: string, config: { innerUnion?: boolean } = {}) => {
const array = (
innerType: string,
config: { innerUnion?: boolean } = {},
): string => {
return config.innerUnion ? `${innerType}[]` : `Array<${innerType}>`
}

const union = (types: string[]): string => {
return types.join(' | ')
}

type FieldPrintOptions = {
isReturn?: boolean
}

export const printFieldLikeType = (
field: GraphQLTypeField,
modelMap: ModelMap,
interfacesMap: InterfacesMap,
unionsMap: UnionsMap,
options: FieldPrintOptions = {
isReturn: false,
},
): string => {
if (field.type.isInterface || field.type.isUnion) {
const typesMap = field.type.isInterface ? interfacesMap : unionsMap

const modelNames = typesMap[field.type.name].map(type =>
getModelName(type, modelMap),
)

let rendering = union(modelNames)

if (!field.type.isRequired) {
rendering = nullable(rendering)
}

if (field.type.isArray) {
rendering = array(rendering, { innerUnion: false })
}

if (!field.type.isArrayRequired) {
rendering = nullable(rendering)
}

// We do not have to handle defaults becuase graphql only
// supports defaults on field params but conversely
// interfaces and unions are only supported on output. Therefore
// these two features will never cross.

// No check for isReturn option because unions and interfaces
// cannot be used to type graphql field parameters which implies
// this branch will always be for a return case.

return rendering
}

const name = field.type.isScalar
? getTypeFromGraphQLType(field.type.name)
: field.type.isInput || field.type.isEnum
Expand Down Expand Up @@ -335,11 +397,13 @@ export function isParentType(name: string) {

export function groupModelsNameByImportPath(models: Model[]) {
return models.reduce<{ [importPath: string]: string[] }>((acc, model) => {
if (acc[model.importPathRelativeToOutput] === undefined) {
acc[model.importPathRelativeToOutput] = []
const fileModels = acc[model.importPathRelativeToOutput] || []

if (!fileModels.includes(model.definition.name)) {
fileModels.push(model.definition.name)
}

acc[model.importPathRelativeToOutput].push(model.definition.name)
acc[model.importPathRelativeToOutput] = fileModels

return acc
}, {})
Expand Down
Loading

0 comments on commit eb2f3cf

Please sign in to comment.