import { indentMultiline } from '@graphql-codegen/visitor-plugin-common' ; import { TypeScriptDeclarationBlock } from '../languages/typescript-declaration-block' ; import { AppSyncModelVisitor , CodeGenEnum , CodeGenField , CodeGenModel , ParsedAppSyncModelConfig , RawAppSyncModelConfig , } from './appsync-visitor' ; export interface RawAppSyncModelTypeScriptConfig extends RawAppSyncModelConfig { } export interface ParsedAppSyncModelTypeScriptConfig extends ParsedAppSyncModelConfig { isDeclaration : boolean ; } export class AppSyncModelTypeScriptVisitor < TRawConfig extends RawAppSyncModelTypeScriptConfig = RawAppSyncModelTypeScriptConfig , TPluginConfig extends ParsedAppSyncModelTypeScriptConfig = ParsedAppSyncModelTypeScriptConfig > extends AppSyncModelVisitor < TRawConfig , TPluginConfig > { protected SCALAR_TYPE_MAP : { [ key ] : string ; } = { String : 'string' , Int : 'number' , Float : 'number' , Boolean : 'boolean' , ID : 'string' , } ; protected IMPORT_STATEMENTS = [ 'import { ModelInit, MutableModel, PersistentModelConstructor } from "@aws-amplify/datastore";' , 'import { initSchema } from "@aws-amplify/datastore";' , '' , 'import { schema } from "./schema";' , ] ; generate ( ) { const shouldUseModelNameFieldInHasManyAndBelongsTo = false ; this . processDirectives ( shouldUseModelNameFieldInHasManyAndBelongsTo ) ; const imports = this . generateImports ( ) ; const enumDeclarations = Object . values ( this . enumMap ) . map ( enumObj => this . generateEnumDeclarations ( enumObj ) ) . join ( '\n\n' ) ; const modelDeclarations = Object . values ( this . modelMap ) . map ( typeObj => this . generateModelDeclaration ( typeObj ) ) . join ( '\n\n' ) ; const nonModelDeclarations = Object . values ( this . nonModelMap ) . map ( typeObj => this . generateModelDeclaration ( typeObj ) ) . join ( '\n\n' ) ; const modelInitialization = this . generateModelInitialization ( [ ... Object . values ( this . modelMap ) , ... Object . values ( this . nonModelMap ) ] ) ; const modelExports = this . generateExports ( Object . values ( this . modelMap ) ) ; return [ imports , enumDeclarations , modelDeclarations , nonModelDeclarations , modelInitialization , modelExports ] . join ( '\n\n' ) ; } protected generateImports ( ) { return this . IMPORT_STATEMENTS . join ( '\n' ) ; } protected generateEnumDeclarations ( enumObj , exportEnum = false ) { const enumDeclarations = new TypeScriptDeclarationBlock ( ) . asKind ( 'enum' ) . withName ( this . getEnumName ( enumObj ) ) . withEnumValues ( enumObj . values ) . export ( exportEnum ) ; return enumDeclarations . string ; } protected generateModelMetaData ( modelObj ) { const modelName = this . generateModelTypeDeclarationName ( modelObj ) ; const modelDeclarations = new TypeScriptDeclarationBlock ( ) . asKind ( 'type' ) . withName ( `${ modelName }MetaData` ) . export ( false ) ; const isTimestampFeatureFlagEnabled = this . config . isTimestampFieldsAdded ; let readOnlyFieldNames = [ ] ; modelObj . fields . forEach ( ( field ) => { if ( isTimestampFeatureFlagEnabled && field . isReadOnly ) { readOnlyFieldNames . push ( `'${ field . name }'` ) ; } } ) ; modelDeclarations . addProperty ( 'readOnlyFields' , readOnlyFieldNames . join ( ' | ' ) ) ; return modelDeclarations . string ; } param modelObj param isDeclaration protected generateModelDeclaration ( modelObj , isDeclaration = true ) { const modelName = this . generateModelTypeDeclarationName ( modelObj ) ; const modelDeclarations = new TypeScriptDeclarationBlock ( ) . asKind ( 'class' ) . withFlag ( { isDeclaration } ) . withName ( modelName ) . export ( true ) ; const isTimestampFeatureFlagEnabled = this . config . isTimestampFieldsAdded ; let readOnlyFieldNames = [ ] ; let modelMetaDataFormatted ; let modelMetaDataDeclaration = '' ; modelObj . fields . forEach ( ( field ) => { modelDeclarations . addProperty ( this . getFieldName ( field ) , this . getNativeType ( field ) , undefined , 'DEFAULT' , { readonly : true , optional : field . isList ? field . isListNullable : field . isNullable , } ) ; if ( isTimestampFeatureFlagEnabled && field . isReadOnly ) { readOnlyFieldNames . push ( `'${ field . name }'` ) ; } } ) ; if ( isTimestampFeatureFlagEnabled ) { modelMetaDataFormatted = `, ${ modelName }MetaData` ; modelMetaDataDeclaration = readOnlyFieldNames . length > 0 ? modelMetaDataFormatted : '' ; } modelDeclarations . addClassMethod ( 'constructor' , null , null , [ { name : 'init' , type : `ModelInit<${ modelName }${ modelMetaDataDeclaration }>` , } , ] , 'DEFAULT' , { } ) ; if ( Object . values ( this . modelMap ) . includes ( modelObj ) ) { modelDeclarations . addClassMethod ( 'copyOf' , modelName , null , [ { name : 'source' , type : modelName , } , { name : 'mutator' , type : `(draft: MutableModel<${ modelName }${ modelMetaDataDeclaration }>) => MutableModel<${ modelName }${ modelMetaDataDeclaration }> | void` , } , ] , 'DEFAULT' , { static : true } ) ; } return modelDeclarations . string ; } param model protected generateModelInitialization ( models , includeTypeInfo = true ) { if ( models . length === 0 ) { return '' ; } const modelClasses = models . map ( model => [ this . generateModelImportName ( model ) , this . generateModelImportAlias ( model ) ] ) . map ( ( [ importName , aliasName ] ) => { return importName === aliasName ? importName : `${ importName }: ${ aliasName }` ; } ) ; const initializationResult = [ 'const' , '{' , modelClasses . join ( ', ' ) , '}' , '=' , 'initSchema(schema)' ] ; if ( includeTypeInfo ) { const typeInfo = models . map ( model => { return [ this . generateModelImportName ( model ) , this . generateModelTypeDeclarationName ( model ) ] ; } ) . map ( ( [ importName , modelDeclarationName ] ) => `${ importName }: PersistentModelConstructor<${ modelDeclarationName }>;` ) ; const typeInfoStr = [ '{' , indentMultiline ( typeInfo . join ( '\n' ) ) , '}' ] . join ( '\n' ) ; initializationResult . push ( 'as' , typeInfoStr ) ; } return `${ initializationResult . join ( ' ' ) };` ; } protected generateExports ( modelsOrEnum ) { const exportStr = modelsOrEnum . map ( model => { if ( model . type === 'model' ) { const modelClassName = this . generateModelImportAlias ( model ) ; const exportClassName = this . getModelName ( model ) ; return modelClassName !== exportClassName ? `${ modelClassName } as ${ exportClassName }` : modelClassName ; } return model . name ; } ) . join ( ',\n' ) ; return [ 'export {' , indentMultiline ( exportStr ) , '};' ] . join ( '\n' ) ; } param model protected generateModelTypeDeclarationName ( model ) { return `${ this . getModelName ( model ) }Model` ; } param model protected generateModelImportAlias ( model ) { return this . getModelName ( model ) ; } param model protected generateModelImportName ( model ) { return this . getModelName ( model ) ; } param model protected generateModelExportName ( model ) { return this . getModelName ( model ) ; } protected getListType ( typeStr , field ) { let type = typeStr ; if ( field . isNullable ) { type = `(${ type } | null)` ; } return `${ type }[]` ; } protected getNativeType ( field ) { const typeName = field . type ; if ( this . isModelType ( field ) ) { const modelType = this . modelMap [ typeName ] ; const typeNameStr = this . generateModelTypeDeclarationName ( modelType ) ; return field . isList ? this . getListType ( typeNameStr , field ) : typeNameStr ; } let nativeType = super . getNativeType ( field ) ; if ( this . isEnumType ( field ) ) { nativeType = `${ nativeType } | keyof typeof ${ this . getEnumName ( this . enumMap [ typeName ] ) }` ; } return nativeType ; } }