import { GraphQLNonNull , GraphQLType , isScalarType , isEnumType , isListType } from 'graphql' ; import * as prettier from 'prettier' ; import { LegacyCompilerContext , LegacyOperation , LegacyInlineFragment , LegacyField } from '../compiler/legacyIR' ; import { CodeGenerator } from '../utilities/CodeGenerator' ; import { typeDeclarationForGraphQLType , interfaceDeclarationForFragment , propertiesFromFields , updateTypeNameField , pickedPropertyDeclarations , interfaceNameFromOperation , } from '../typescript/codeGeneration' ; import { typeNameFromGraphQLType } from '../typescript/types' ; import { Property , interfaceDeclaration } from '../typescript/language' ; import { isList } from '../utilities/graphql' ; import { propertyDeclarations } from '../flow/codeGeneration' ; export function generateSource ( context ) { const generator = new CodeGenerator < LegacyCompilerContext > ( context ) ; generator . printOnNewline ( '/* tslint:disable */' ) ; generator . printOnNewline ( '/* eslint-disable */' ) ; generator . printOnNewline ( '//  This file was automatically generated and should not be edited.' ) ; generator . printOnNewline ( `import { Injectable } from '@angular/core';` ) ; generator . printOnNewline ( `import API, { graphqlOperation, GraphQLResult } from '@aws-amplify/api-graphql';` ) ; generator . printOnNewline ( `import { Observable } from 'zen-observable-ts';` ) ; generator . printNewline ( ) ; generateTypes ( generator , context ) ; generator . printNewline ( ) ; generateAngularService ( generator , context ) ; return prettier . format ( generator . output , { parser : 'typescript' } ) ; } function generateTypes ( generator , context ) { if ( context . schema . getSubscriptionType ( ) ) { generateSubscriptionResponseWrapper ( generator ) ; generateSubscriptionOperationTypes ( generator , context ) ; } context . typesUsed . forEach ( type => typeDeclarationForGraphQLType ( generator , type ) ) ; Object . values ( context . operations ) . forEach ( operation => { interfaceDeclarationForOperation ( generator , operation ) ; } ) ; Object . values ( context . fragments ) . forEach ( operation => interfaceDeclarationForFragment ( generator , operation ) ) ; } function generateSubscriptionResponseWrapper ( generator ) { generator . printOnNewline ( `
  export interface SubscriptionResponse<T> {
    value: GraphQLResult<T>;
  }` ) ; generator . printNewline ( ) ; } function generateSubscriptionOperationTypes ( generator , context ) { const typeName = '__SubscriptionContainer' ; const properties = [ ] ; Object . values ( context . operations ) . filter ( operation => operation . operationType === 'subscription' ) . forEach ( operation => { const { operationName , operationType } = operation ; const typeName = interfaceNameFromOperation ( { operationName , operationType } ) ; if ( operation . fields . length ) { properties . push ( { fieldName : operation . fields [ 0 ] . responseName , typeName , } ) ; } } ) ; if ( properties . length ) { generator . printOnNewline ( `export type ${ typeName } = ` ) ; generator . pushScope ( { typeName } ) ; generator . withinBlock ( ( ) => propertyDeclarations ( generator , properties ) , '{' , '}' ) ; generator . popScope ( ) ; generator . print ( ';' ) ; generator . printNewline ( ) ; } } function interfaceDeclarationForOperation ( generator , { operationName , operationType , fields } ) { const interfaceName = interfaceNameFromOperation ( { operationName , operationType } ) ; fields = fields . map ( field => updateTypeNameField ( field ) ) ; if ( fields [ 0 ] . fields ) { const properties = propertiesFromFields ( generator . context , fields [ 0 ] . fields as LegacyField [ ] ) ; interfaceDeclaration ( generator , { interfaceName , } , ( ) => { pickedPropertyDeclarations ( generator , properties ) ; } ) ; } } function getOperationResultField ( operation ) { if ( operation . fields . length ) { return operation . fields [ 0 ] ; } } function getReturnTypeName ( generator , op ) { const { operationName , operationType } = op ; const { type } = op . fields [ 0 ] ; if ( isListType ( type ) ) { const { ofType } = type ; if ( isScalarType ( ofType ) || isEnumType ( ofType ) ) { return `Array<${ typeNameFromGraphQLType ( generator . context , ofType ) }>` ; } } if ( isScalarType ( type ) || isEnumType ( type ) ) { return typeNameFromGraphQLType ( generator . context , type ) ; } else { let returnType = interfaceNameFromOperation ( { operationName , operationType } ) ; if ( op . operationType === 'subscription' && op . fields . length ) { returnType = `Pick<__SubscriptionContainer, "${ op . fields [ 0 ] . responseName }">` ; } if ( isList ( type ) ) { returnType = `Array<${ returnType }>` ; } return returnType ; } } function generateAngularService ( generator , context ) { const operations = context . operations ; generator . printOnNewline ( `@Injectable({
    providedIn: 'root'
  })` ) ; generator . printOnNewline ( `export class APIService {` ) ; generator . withIndent ( ( ) => { Object . values ( operations ) . forEach ( ( op ) => { if ( op . operationType === 'subscription' ) { return generateSubscriptionOperation ( generator , op ) ; } if ( op . operationType === 'query' || op . operationType === 'mutation' ) { return generateQueryOrMutationOperation ( generator , op ) ; } } ) ; generator . printOnNewline ( '}' ) ; } ) ; } function generateSubscriptionOperation ( generator , op ) { const statement = formatTemplateString ( generator , op . source ) ; const { operationName } = op ; const vars = variablesFromField ( generator . context , op . variables ) ; const returnType = getReturnTypeName ( generator , op ) ; generator . printNewline ( ) ; const subscriptionName = `${ operationName }Listener` ; if ( ! vars . length ) { generator . print ( `${ subscriptionName }: Observable<SubscriptionResponse<${ returnType }>> = API.graphql(graphqlOperation(\n\`${ statement }\`)) as Observable<SubscriptionResponse<${ returnType }>>` ) ; } else { generator . print ( `${ subscriptionName }(` ) ; variableDeclaration ( generator , vars ) ; generator . print ( `) : Observable<SubscriptionResponse<${ returnType }>> {` ) ; generator . withIndent ( ( ) => { generator . printNewlineIfNeeded ( ) ; generator . print ( `const statement = \`${ statement }\`` ) ; const params = [ 'statement' ] ; variableAssignmentToInput ( generator , vars ) ; params . push ( 'gqlAPIServiceArguments' ) ; generator . printOnNewline ( `return API.graphql(graphqlOperation(${ params . join ( ', ' ) })) as Observable<SubscriptionResponse<${ returnType }>>;` ) ; generator . printOnNewline ( '}' ) ; } ) ; } generator . printNewline ( ) ; } function generateQueryOrMutationOperation ( generator , op ) { const statement = formatTemplateString ( generator , op . source ) ; const vars = variablesFromField ( generator . context , op . variables ) ; const returnType = getReturnTypeName ( generator , op ) ; const resultField = getOperationResultField ( op ) ; const resultProp = resultField ? `.${ resultField . responseName }` : '' ; generator . printNewline ( ) ; generator . print ( `async ${ op . operationName }(` ) ; variableDeclaration ( generator , vars ) ; generator . print ( `) : Promise<${ returnType }> {` ) ; generator . withIndent ( ( ) => { generator . printNewlineIfNeeded ( ) ; generator . print ( `const statement = \`${ statement }\`` ) ; const params = [ 'statement' ] ; if ( op . variables . length ) { variableAssignmentToInput ( generator , vars ) ; params . push ( 'gqlAPIServiceArguments' ) ; } generator . printOnNewline ( `const response = await API.graphql(graphqlOperation(${ params . join ( ', ' ) })) as any;` ) ; generator . printOnNewline ( `return (<${ returnType }>response.data${ resultProp })` ) ; } ) ; generator . printOnNewline ( '}' ) ; } export function variablesFromField ( context , fields ) { return fields . map ( field => propertyFromVar ( context , field ) ) ; } export function propertyFromVar ( context , field ) { let { name : fieldName , type : fieldType } = field ; fieldName = fieldName || field . responseName ; const propertyName = fieldName ; let property = { fieldName , fieldType , propertyName } ; let isNullable = true ; if ( fieldType instanceof GraphQLNonNull ) { isNullable = false ; } const typeName = typeNameFromGraphQLType ( context , fieldType , null , false ) ; return { ... property , typeName , isComposite : false , fieldType , isNullable } ; } function variableDeclaration ( generator , properties ) { properties . sort ( ( a , b ) => { if ( ! a . isNullable && b . isNullable ) { return - 1 ; } if ( ! b . isNullable && a . isNullable ) { return 1 ; } return 0 ; } ) . forEach ( property => { const { fieldName , typeName , isArray , isNullable } = property ; generator . print ( fieldName ) ; if ( isNullable ) { generator . print ( '?' ) ; } generator . print ( ':' ) ; if ( isArray ) { generator . print ( ' Array<' ) ; } generator . print ( `${ typeName }` ) ; if ( isArray ) { generator . print ( '>' ) ; } generator . print ( ', ' ) ; } ) ; } function variableAssignmentToInput ( generator , vars ) { if ( vars . length > 0 ) { generator . printOnNewline ( 'const gqlAPIServiceArguments : any = ' ) ; generator . withinBlock ( ( ) => { vars . filter ( v => ! v . isNullable ) . forEach ( v => { generator . printOnNewline ( `${ v . fieldName },` ) ; } ) ; } , '{' , '}' ) ; vars . filter ( v => v . isNullable ) . forEach ( v => { generator . printOnNewline ( `if (${ v . fieldName }) ` ) ; generator . withinBlock ( ( ) => { generator . printOnNewline ( `gqlAPIServiceArguments.${ v . fieldName } = ${ v . fieldName }` ) ; } , '{' , '}' ) ; } ) ; } } function formatTemplateString ( generator , str ) { const indentation = ' ' . repeat ( generator . currentFile . indentWidth * ( generator . currentFile . indentLevel + 2 ) ) ; return str . split ( '\n' ) . map ( ( line , idx ) => ( idx > 0 ? indentation + line : line ) ) . join ( '\n' ) ; }