diff --git a/packages/amplify-graphql-relational-transformer/src/graphql-belongs-to-transformer.ts b/packages/amplify-graphql-relational-transformer/src/graphql-belongs-to-transformer.ts index 25c859e663..4f7ab9e708 100644 --- a/packages/amplify-graphql-relational-transformer/src/graphql-belongs-to-transformer.ts +++ b/packages/amplify-graphql-relational-transformer/src/graphql-belongs-to-transformer.ts @@ -67,44 +67,32 @@ export class BelongsToTransformer extends TransformerPluginBase { const resultDoc: DocumentNode = produce(context.inputDocument, draftDoc => { const objectTypeMap = new Map>(); // key: type name | value: object type node // First iteration builds a map of the object types to reference for relation types - draftDoc?.definitions?.forEach(def => { - if (def.kind === 'ObjectTypeExtension' || def.kind === 'ObjectTypeDefinition') { - objectTypeMap.set(def.name.value, def); - } - }); - - draftDoc?.definitions?.forEach(def => { - if (def.kind === 'ObjectTypeExtension' || def.kind === 'ObjectTypeDefinition') { - def?.fields?.forEach(field => { - field?.directives?.forEach(dir => { - if (dir.name.value === directiveName) { - const relatedType = objectTypeMap.get(getBaseType(field.type)); - if (relatedType) { // Validation is done in a different segment of the life cycle - const relationTypeField = relatedType?.fields?.find(relatedField => { - if (getBaseType(relatedField.type) === def.name.value && relatedField?.directives?.some( - relatedDir => relatedDir.name.value === 'hasOne' || relatedDir.name.value === 'hasMany', - )) { - return true; - } - return false; - }); - const relationTypeName = relationTypeField?.directives?.find(relationDir => relationDir.name.value === 'hasOne' || relationDir.name.value === 'hasMany')?.name?.value; - if (relationTypeName === 'hasOne') { - const connectionAttributeName = getConnectionAttributeName(def.name.value, field.name.value); - if (!def?.fields?.some(defField => defField.name.value === connectionAttributeName)) { - def?.fields?.push( - makeField( - connectionAttributeName, [], isNonNullType(field.type) ? - makeNonNullType(makeNamedType('ID')) : makeNamedType('ID'), [], - ) as WritableDraft, - ); - } - } - } - } - }); - }); - } + const filteredDefs = draftDoc?.definitions?.filter(def => def.kind === 'ObjectTypeExtension' || def.kind === 'ObjectTypeDefinition'); + const objectDefs = filteredDefs as Array>; + objectDefs?.forEach(def => objectTypeMap.set(def.name.value, def)); + + objectDefs?.forEach(def => { + const filteredFields = def?.fields?.filter(field => field?.directives?.some(dir => dir.name.value === directiveName && objectTypeMap.get(getBaseType(field.type)))); + filteredFields?.forEach(field => { + const relatedType = objectTypeMap.get(getBaseType(field.type)); + const relationTypeField = relatedType?.fields?.find(relatedField => + getBaseType(relatedField.type) === def.name.value && + relatedField?.directives?.some(relatedDir => relatedDir.name.value === 'hasOne' || relatedDir.name.value === 'hasMany') + ); + const relationTypeName = relationTypeField?.directives?.find(relationDir => relationDir.name.value === 'hasOne' || relationDir.name.value === 'hasMany')?.name?.value; + + if (relationTypeName === 'hasOne') { + const connectionAttributeName = getConnectionAttributeName(def.name.value, field.name.value); + if (!def?.fields?.some(defField => defField.name.value === connectionAttributeName)) { + def?.fields?.push( + makeField( + connectionAttributeName, [], isNonNullType(field.type) ? + makeNonNullType(makeNamedType('ID')) : makeNamedType('ID'), [], + ) as WritableDraft, + ); + } + } + }); }); }); return resultDoc; diff --git a/packages/amplify-graphql-relational-transformer/src/graphql-has-many-transformer.ts b/packages/amplify-graphql-relational-transformer/src/graphql-has-many-transformer.ts index 738c101ff4..e040172b42 100644 --- a/packages/amplify-graphql-relational-transformer/src/graphql-has-many-transformer.ts +++ b/packages/amplify-graphql-relational-transformer/src/graphql-has-many-transformer.ts @@ -12,7 +12,7 @@ import { FieldDefinitionNode, InterfaceTypeDefinitionNode, NamedTypeNode, - ObjectTypeDefinitionNode, + ObjectTypeDefinitionNode, ObjectTypeExtensionNode, } from 'graphql'; import { makeQueryConnectionWithKeyResolver, updateTableForConnection } from './resolvers'; import { ensureHasManyConnectionField, extendTypeWithConnection } from './schema'; @@ -69,28 +69,23 @@ export class HasManyTransformer extends TransformerPluginBase { mutateSchema = (context: TransformerPreProcessContextProvider): DocumentNode => { const resultDoc: DocumentNode = produce(context.inputDocument, draftDoc => { const connectingFieldsMap = new Map>(); // key: type name | value: connecting field + const filteredDefs = draftDoc?.definitions?.filter(def => def.kind === 'ObjectTypeDefinition' || def.kind === 'ObjectTypeExtension'); + const objectDefs = filteredDefs as Array>; // First iteration builds a map of the hasMany connecting fields that need to exist, second iteration ensures they exist - draftDoc?.definitions?.forEach(def => { - if (def.kind === 'ObjectTypeExtension' || def.kind === 'ObjectTypeDefinition') { - def?.fields?.forEach(field => { - field?.directives?.forEach(dir => { - if (dir.name.value === directiveName) { - const baseFieldType = getBaseType(field.type); - const connectionAttributeName = getConnectionAttributeName(def.name.value, field.name.value); - const newField = makeField(connectionAttributeName, [], isNonNullType(field.type) ? makeNonNullType(makeNamedType('ID')) : makeNamedType('ID'), []); - connectingFieldsMap.set(baseFieldType, newField as WritableDraft); - } - }); - }); - } + objectDefs?.forEach(def => { + const filteredFields = def?.fields?.filter(field => field?.directives?.some(dir => dir.name.value === directiveName)); + filteredFields?.forEach(field => { + const baseFieldType = getBaseType(field.type); + const connectionAttributeName = getConnectionAttributeName(def.name.value, field.name.value); + const newField = makeField(connectionAttributeName, [], isNonNullType(field.type) ? makeNonNullType(makeNamedType('ID')) : makeNamedType('ID'), []); + connectingFieldsMap.set(baseFieldType, newField as WritableDraft); + }); }); - draftDoc?.definitions?.forEach(def => { - if ((def.kind === 'ObjectTypeDefinition' || def.kind === 'ObjectTypeExtension') && connectingFieldsMap.has(def.name.value)) { - const fieldToAdd = connectingFieldsMap.get(def.name.value); - if (def.fields && fieldToAdd && !def.fields.some(field => field.name.value === fieldToAdd.name.value)) { - def.fields.push(fieldToAdd); - } + objectDefs?.filter(def => connectingFieldsMap.has(def.name.value))?.forEach(def => { + const fieldToAdd = connectingFieldsMap.get(def.name.value); + if (def.fields && fieldToAdd && !def.fields.some(field => field.name.value === fieldToAdd.name.value)) { + def.fields.push(fieldToAdd); } }); }); diff --git a/packages/amplify-graphql-relational-transformer/src/graphql-has-one-transformer.ts b/packages/amplify-graphql-relational-transformer/src/graphql-has-one-transformer.ts index 9b676d2a7a..02ee480be5 100644 --- a/packages/amplify-graphql-relational-transformer/src/graphql-has-one-transformer.ts +++ b/packages/amplify-graphql-relational-transformer/src/graphql-has-one-transformer.ts @@ -11,7 +11,7 @@ import { DocumentNode, FieldDefinitionNode, InterfaceTypeDefinitionNode, - ObjectTypeDefinitionNode, + ObjectTypeDefinitionNode, ObjectTypeExtensionNode, } from 'graphql'; import { isListType, @@ -74,40 +74,40 @@ export class HasOneTransformer extends TransformerPluginBase { */ mutateSchema = (context: TransformerPreProcessContextProvider): DocumentNode => { const document: DocumentNode = produce(context.inputDocument, draftDoc => { - draftDoc.definitions.forEach(def => { - if (def.kind === 'ObjectTypeDefinition' || def.kind === 'ObjectTypeExtension') { - def?.fields?.forEach(field => { - field?.directives?.forEach(dir => { - if (dir.name.value === directiveName) { - const connectionAttributeName = getConnectionAttributeName(def.name.value, field.name.value); - let hasFieldsDefined = false; - let removalIndex = -1; - dir?.arguments?.forEach((arg, idx) => { - if (arg.name.value === 'fields') { - if ((arg.value.kind === 'StringValue' && arg.value.value) || (arg.value.kind === 'ListValue' && arg.value.values && arg.value.values.length > 0)) { - hasFieldsDefined = true; - } else { - removalIndex = idx; - } - } - }); - if (removalIndex !== -1) { - dir?.arguments?.splice(removalIndex, 1); - } - if (!hasFieldsDefined) { - // eslint-disable-next-line no-param-reassign - dir.arguments = [makeArgument('fields', makeValueNode(connectionAttributeName)) as WritableDraft]; - def?.fields?.push( - makeField( - connectionAttributeName, [], isNonNullType(field.type) ? - makeNonNullType(makeNamedType('ID')) : makeNamedType('ID'), [], - ) as WritableDraft, - ); + const filteredDefs = draftDoc?.definitions?.filter(def => def.kind === 'ObjectTypeDefinition' || def.kind === 'ObjectTypeExtension'); + const objectDefs = filteredDefs as Array>; + + objectDefs?.forEach(def => { + const filteredFields = def?.fields?.filter(field => field?.directives?.some(dir => dir.name.value === directiveName)); + filteredFields?.forEach(field => { + field?.directives?.forEach(dir => { + const connectionAttributeName = getConnectionAttributeName(def.name.value, field.name.value); + let hasFieldsDefined = false; + let removalIndex = -1; + dir?.arguments?.forEach((arg, idx) => { + if (arg.name.value === 'fields') { + if ((arg.value.kind === 'StringValue' && arg.value.value) || (arg.value.kind === 'ListValue' && arg.value.values && arg.value.values.length > 0)) { + hasFieldsDefined = true; + } else { + removalIndex = idx; } } }); + if (removalIndex !== -1) { + dir?.arguments?.splice(removalIndex, 1); + } + if (!hasFieldsDefined) { + // eslint-disable-next-line no-param-reassign + dir.arguments = [makeArgument('fields', makeValueNode(connectionAttributeName)) as WritableDraft]; + def?.fields?.push( + makeField( + connectionAttributeName, [], isNonNullType(field.type) ? + makeNonNullType(makeNamedType('ID')) : makeNamedType('ID'), [], + ) as WritableDraft, + ); + } }); - } + }); }); }); return document; diff --git a/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts b/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts index a2b9dda1bf..2badb24fb2 100644 --- a/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts +++ b/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts @@ -12,7 +12,6 @@ import { DirectiveNode, DocumentNode, FieldDefinitionNode, - GraphQLError, InterfaceTypeDefinitionNode, Kind, ObjectTypeDefinitionNode, @@ -25,7 +24,7 @@ import { isListType, makeArgument, makeDirective, - makeField, makeListType, + makeField, makeNamedType, makeValueNode, toUpper, @@ -111,32 +110,30 @@ export class ManyToManyTransformer extends TransformerPluginBase { mutateSchema = (context: TransformerPreProcessContextProvider): DocumentNode => { const manyToManyMap = new Map(); // Relation name is the key, each array should be length 2 (two models being connected) const newDocument: DocumentNode = produce(context.inputDocument, draftDoc => { + const filteredDefs = draftDoc?.definitions?.filter(def => def.kind === 'ObjectTypeExtension' || def.kind === 'ObjectTypeDefinition'); + const objectDefs = filteredDefs as Array>; // First iteration builds the map - draftDoc?.definitions?.forEach(def => { - if (def.kind === 'ObjectTypeDefinition' || def.kind === 'ObjectTypeExtension') { - def?.fields?.forEach(field => { - field?.directives?.forEach(dir => { - if (dir.name.value === directiveName) { - const relationArg = dir?.arguments?.find(arg => arg.name.value === 'relationName'); - if (relationArg?.value?.kind === 'StringValue') { - const relationName = relationArg.value.value; - const manyToManyContext: ManyToManyPreProcessContext = { - model: def, - field: field, - directive: dir, - modelAuthDirectives: def?.directives?.filter(authDir => authDir.name.value === 'auth') ?? [], - fieldAuthDirectives: def?.directives?.filter(authDir => authDir.name.value === 'auth') ?? [], - relationName: relationName, - }; - if (!manyToManyMap.has(relationName)) { - manyToManyMap.set(relationName, []); - } - manyToManyMap.get(relationName)?.push(manyToManyContext); - } + objectDefs?.forEach(def => { + def?.fields?.forEach(field => { + field?.directives?.filter(dir => dir.name.value === directiveName)?.forEach(dir => { + const relationArg = dir?.arguments?.find(arg => arg.name.value === 'relationName'); + if (relationArg?.value?.kind === 'StringValue') { + const relationName = relationArg.value.value; + const manyToManyContext: ManyToManyPreProcessContext = { + model: def, + field: field, + directive: dir, + modelAuthDirectives: def?.directives?.filter(authDir => authDir.name.value === 'auth') ?? [], + fieldAuthDirectives: def?.directives?.filter(authDir => authDir.name.value === 'auth') ?? [], + relationName: relationName, + }; + if (!manyToManyMap.has(relationName)) { + manyToManyMap.set(relationName, []); } - }); + manyToManyMap.get(relationName)?.push(manyToManyContext); + } }); - } + }); }); // Run check for relations that are not binary and therefore invalid