Skip to content

Commit

Permalink
feat(amplify-graphql-types-generator): generate model types (#3324)
Browse files Browse the repository at this point in the history
Fixes #3323
- accumulate return type of operation
- accumulate object types, interface types and union types
- generate object types, interface types and union types (for TypeScript)

This PR mainly handles TypeScript. Other frontends may have similary issue.



Co-authored-by: Yathi <511386+yuth@users.noreply.github.com>
  • Loading branch information
hirochachacha and yuth committed Jan 20, 2021
1 parent df74e0f commit 80193f7
Show file tree
Hide file tree
Showing 12 changed files with 3,015 additions and 124 deletions.
4 changes: 2 additions & 2 deletions packages/amplify-graphql-types-generator/src/angular/index.ts
Expand Up @@ -8,7 +8,7 @@ import {
interfaceDeclarationForFragment,
propertiesFromFields,
updateTypeNameField,
propertyDeclarations,
pickedPropertyDeclarations,
interfaceNameFromOperation,
} from '../typescript/codeGeneration';
import { typeNameFromGraphQLType } from '../typescript/types';
Expand Down Expand Up @@ -83,7 +83,7 @@ function interfaceDeclarationForOperation(generator: CodeGenerator, { operationN
interfaceName,
},
() => {
propertyDeclarations(generator, properties);
pickedPropertyDeclarations(generator, properties);
},
);
}
Expand Down
50 changes: 36 additions & 14 deletions packages/amplify-graphql-types-generator/src/compiler/index.ts
Expand Up @@ -7,9 +7,6 @@ import {
isCompositeType,
GraphQLOutputType,
GraphQLInputType,
GraphQLScalarType,
GraphQLEnumType,
GraphQLInputObjectType,
GraphQLObjectType,
GraphQLError,
GraphQLSchema,
Expand All @@ -21,6 +18,12 @@ import {
SelectionSetNode,
SelectionNode,
isSpecifiedScalarType,
isEnumType,
isUnionType,
isScalarType,
isObjectType,
isInputObjectType,
isInterfaceType,
NonNullTypeNode,
GraphQLNonNull,
} from 'graphql';
Expand Down Expand Up @@ -166,7 +169,7 @@ export function compileToIR(schema: GraphQLSchema, document: DocumentNode, optio
const possibleTypes = fragment.selectionSet.possibleTypes.filter(type => fragmentSpread.selectionSet.possibleTypes.includes(type));

fragmentSpread.isConditional = fragment.selectionSet.possibleTypes.some(
type => !fragmentSpread.selectionSet.possibleTypes.includes(type)
type => !fragmentSpread.selectionSet.possibleTypes.includes(type),
);

fragmentSpread.selectionSet = {
Expand Down Expand Up @@ -198,13 +201,29 @@ class Compiler {
if (this.typesUsedSet.has(type)) return;

if (
type instanceof GraphQLEnumType ||
type instanceof GraphQLInputObjectType ||
(type instanceof GraphQLScalarType && !isSpecifiedScalarType(type))
isEnumType(type) ||
isUnionType(type) ||
isInputObjectType(type) ||
isInterfaceType(type) ||
isObjectType(type) ||
(isScalarType(type) && !isSpecifiedScalarType(type))
) {
this.typesUsedSet.add(type);
}
if (type instanceof GraphQLInputObjectType) {
if (isInterfaceType(type) || isUnionType(type)) {
for (const concreteType of this.schema.getPossibleTypes(type)) {
this.addTypeUsed(getNamedType(concreteType));
}
}
if (isInputObjectType(type)) {
for (const field of Object.values(type.getFields())) {
this.addTypeUsed(getNamedType(field.type));
}
}
if (isObjectType(type)) {
for (const fieldType of type.getInterfaces()) {
this.addTypeUsed(getNamedType(fieldType));
}
for (const field of Object.values(type.getFields())) {
this.addTypeUsed(getNamedType(field.type));
}
Expand Down Expand Up @@ -233,6 +252,9 @@ class Compiler {

const source = print(operationDefinition);
const rootType = getOperationRootType(this.schema, operationDefinition) as GraphQLObjectType;
const selectionSet = this.compileSelectionSet(operationDefinition.selectionSet, rootType);

this.addTypeUsed(getNamedType((selectionSet.selections[0] as Field).type)); // store result type

return {
filePath,
Expand All @@ -241,7 +263,7 @@ class Compiler {
variables,
source,
rootType,
selectionSet: this.compileSelectionSet(operationDefinition.selectionSet, rootType),
selectionSet,
};
}

Expand All @@ -266,7 +288,7 @@ class Compiler {
selectionSetNode: SelectionSetNode,
parentType: GraphQLCompositeType,
possibleTypes: GraphQLObjectType[] = this.possibleTypesForType(parentType),
visitedFragments: Set<string> = new Set()
visitedFragments: Set<string> = new Set(),
): SelectionSet {
return {
possibleTypes,
Expand All @@ -275,8 +297,8 @@ class Compiler {
wrapInBooleanConditionsIfNeeded(
this.compileSelection(selectionNode, parentType, possibleTypes, visitedFragments),
selectionNode,
possibleTypes
)
possibleTypes,
),
)
.filter(x => x) as Selection[],
};
Expand All @@ -286,7 +308,7 @@ class Compiler {
selectionNode: SelectionNode,
parentType: GraphQLCompositeType,
possibleTypes: GraphQLObjectType[],
visitedFragments: Set<string>
visitedFragments: Set<string>,
): Selection | null {
switch (selectionNode.kind) {
case Kind.FIELD: {
Expand Down Expand Up @@ -383,7 +405,7 @@ class Compiler {
function wrapInBooleanConditionsIfNeeded(
selection: Selection | null,
selectionNode: SelectionNode,
possibleTypes: GraphQLObjectType[]
possibleTypes: GraphQLObjectType[],
): Selection | null {
if (!selection) return null;

Expand Down
Expand Up @@ -308,7 +308,7 @@ export function propertyFromField(context, field) {
}
}

export function propertyDeclarations(generator, properties, isInput) {
export function propertyDeclarations(generator, properties, isOptional) {
if (!properties) return;
properties.forEach(property => {
if (isAbstractType(getNamedType(property.type || property.fieldType))) {
Expand Down Expand Up @@ -355,23 +355,12 @@ export function propertyDeclarations(generator, properties, isInput) {
(property.inlineFragments && property.inlineFragments.length > 0) ||
(property.fragmentSpreads && property.fragmentSpreads.length > 0)
) {
const fields = property.fields.map(field => {
if (field.fieldName === '__typename') {
return {
...field,
typeName: `"${property.typeName}"`,
type: { name: `"${property.typeName}"` },
};
} else {
return field;
}
});
propertyDeclaration(generator, property, () => {
const properties = propertiesFromFields(generator.context, fields);
propertyDeclarations(generator, properties, isInput);
const properties = propertiesFromFields(generator.context, property.fields);
propertyDeclarations(generator, properties, isOptional);
});
} else {
propertyDeclaration(generator, { ...property, isInput });
propertyDeclaration(generator, { ...property, isOptional });
}
}
});
Expand Down
10 changes: 5 additions & 5 deletions packages/amplify-graphql-types-generator/src/flow/language.js
Expand Up @@ -21,10 +21,10 @@ export function typeDeclaration(generator, { interfaceName, noBrackets }, closur

export function propertyDeclaration(
generator,
{ fieldName, type, propertyName, typeName, description, isArray, isNullable, isArrayElementNullable, fragmentSpreads, isInput },
{ fieldName, type, propertyName, typeName, description, isArray, isNullable, isArrayElementNullable, fragmentSpreads, isOptional },
closure,
open = ' {|',
close = '|}'
close = '|}',
) {
const name = fieldName || propertyName;

Expand All @@ -36,7 +36,7 @@ export function propertyDeclaration(

if (closure) {
generator.printOnNewline(name);
if (isInput && isNullable) {
if (isOptional && isNullable) {
generator.print('?');
}
generator.print(':');
Expand Down Expand Up @@ -64,7 +64,7 @@ export function propertyDeclaration(
}
} else {
generator.printOnNewline(name);
if (isInput && isNullable) {
if (isOptional && isNullable) {
generator.print('?');
}
generator.print(`: ${typeName || typeNameFromGraphQLType(generator.context, type)}`);
Expand Down Expand Up @@ -110,7 +110,7 @@ export function propertySetsDeclaration(generator, property, propertySets, stand
});
},
'(',
')'
')',
);

generator.popScope();
Expand Down
85 changes: 79 additions & 6 deletions packages/amplify-graphql-types-generator/src/serializeToJSON.ts
@@ -1,4 +1,19 @@
import { isType, GraphQLType, GraphQLScalarType, GraphQLEnumType, GraphQLInputObjectType } from 'graphql';
import {
isType,
isEnumType,
isUnionType,
isInputObjectType,
isObjectType,
isInterfaceType,
isScalarType,
GraphQLType,
GraphQLScalarType,
GraphQLUnionType,
GraphQLEnumType,
GraphQLInputObjectType,
GraphQLObjectType,
GraphQLInterfaceType,
} from 'graphql';

import { LegacyCompilerContext } from './compiler/legacyIR';

Expand All @@ -9,7 +24,7 @@ export default function serializeToJSON(context: LegacyCompilerContext) {
fragments: Object.values(context.fragments),
typesUsed: context.typesUsed.map(serializeType),
},
'\t'
'\t',
);
}

Expand All @@ -23,16 +38,22 @@ export function serializeAST(ast: any, space?: string) {
return value;
}
},
space
space,
);
}

function serializeType(type: GraphQLType) {
if (type instanceof GraphQLEnumType) {
if (isEnumType(type)) {
return serializeEnumType(type);
} else if (type instanceof GraphQLInputObjectType) {
} else if (isUnionType(type)) {
return serializeUnionType(type);
} else if (isInputObjectType(type)) {
return serializeInputObjectType(type);
} else if (type instanceof GraphQLScalarType) {
} else if (isObjectType(type)) {
return serializeObjectType(type);
} else if (isInterfaceType(type)) {
return serializeInterfaceType(type);
} else if (isScalarType(type)) {
return serializeScalarType(type);
} else {
throw new Error(`Unexpected GraphQL type: ${type}`);
Expand All @@ -56,6 +77,21 @@ function serializeEnumType(type: GraphQLEnumType) {
};
}

function serializeUnionType(type: GraphQLUnionType) {
const { name, description } = type;
const types = type.getTypes();

return {
kind: 'UnionType',
name,
description,
types: types.map(type => ({
name: type.name,
description: type.description,
})),
};
}

function serializeInputObjectType(type: GraphQLInputObjectType) {
const { name, description } = type;
const fields = Object.values(type.getFields());
Expand All @@ -73,6 +109,43 @@ function serializeInputObjectType(type: GraphQLInputObjectType) {
};
}

function serializeObjectType(type: GraphQLObjectType) {
const { name, description } = type;
const ifaces = Object.values(type.getInterfaces());
const fields = Object.values(type.getFields());

return {
kind: 'ObjectType',
name,
description,
ifaces: ifaces.map(iface => ({
name: iface.name,
description: iface.description,
})),
fields: fields.map(field => ({
name: field.name,
type: String(field.type),
description: field.description,
})),
};
}

function serializeInterfaceType(type: GraphQLInterfaceType) {
const { name, description } = type;
const fields = Object.values(type.getFields());

return {
kind: 'InterfaceType',
name,
description,
fields: fields.map(field => ({
name: field.name,
type: String(field.type),
description: field.description,
})),
};
}

function serializeScalarType(type: GraphQLScalarType) {
const { name, description } = type;

Expand Down

0 comments on commit 80193f7

Please sign in to comment.