From 37f4e2a88287fd9de20dbae1610b31b68034851d Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Thu, 20 Jun 2024 10:50:24 -0600 Subject: [PATCH 01/17] feat: add translation behavior to disable gen 1 patterns --- .../transformer-options-v2.ts | 1 + packages/amplify-graphql-api-construct/.jsii | 151 ++++++++++-------- packages/amplify-graphql-api-construct/API.md | 1 + .../src/internal/default-parameters.ts | 1 + .../src/types.ts | 7 + .../transform-parameters.ts | 1 + .../API.md | 1 + .../transform-parameters.ts | 1 + .../src/__e2e__/utils/index.ts | 1 + 9 files changed, 98 insertions(+), 67 deletions(-) diff --git a/packages/amplify-category-api/src/graphql-transformer/transformer-options-v2.ts b/packages/amplify-category-api/src/graphql-transformer/transformer-options-v2.ts index bfee67b16c..37367c8c19 100644 --- a/packages/amplify-category-api/src/graphql-transformer/transformer-options-v2.ts +++ b/packages/amplify-category-api/src/graphql-transformer/transformer-options-v2.ts @@ -290,6 +290,7 @@ const generateTransformParameters = ( enableTransformerCfnOutputs: true, allowDestructiveGraphqlSchemaUpdates: false, replaceTableUponGsiUpdate: false, + allowGen1Patterns: false, }; }; diff --git a/packages/amplify-graphql-api-construct/.jsii b/packages/amplify-graphql-api-construct/.jsii index 9db3a2e6aa..68eafccf6e 100644 --- a/packages/amplify-graphql-api-construct/.jsii +++ b/packages/amplify-graphql-api-construct/.jsii @@ -3537,7 +3537,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 858 + "line": 865 }, "name": "AddFunctionProps", "properties": [ @@ -3550,7 +3550,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 862 + "line": 869 }, "name": "dataSource", "type": { @@ -3566,7 +3566,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 867 + "line": 874 }, "name": "name", "type": { @@ -3583,7 +3583,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 902 + "line": 909 }, "name": "code", "optional": true, @@ -3601,7 +3601,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 874 + "line": 881 }, "name": "description", "optional": true, @@ -3619,7 +3619,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 881 + "line": 888 }, "name": "requestMappingTemplate", "optional": true, @@ -3637,7 +3637,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 888 + "line": 895 }, "name": "responseMappingTemplate", "optional": true, @@ -3655,7 +3655,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 895 + "line": 902 }, "name": "runtime", "optional": true, @@ -4585,7 +4585,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 761 + "line": 768 }, "name": "AmplifyGraphqlApiCfnResources", "properties": [ @@ -4598,7 +4598,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 815 + "line": 822 }, "name": "additionalCfnResources", "type": { @@ -4619,7 +4619,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 800 + "line": 807 }, "name": "amplifyDynamoDbTables", "type": { @@ -4640,7 +4640,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 790 + "line": 797 }, "name": "cfnDataSources", "type": { @@ -4661,7 +4661,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 785 + "line": 792 }, "name": "cfnFunctionConfigurations", "type": { @@ -4682,7 +4682,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 810 + "line": 817 }, "name": "cfnFunctions", "type": { @@ -4703,7 +4703,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 765 + "line": 772 }, "name": "cfnGraphqlApi", "type": { @@ -4719,7 +4719,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 770 + "line": 777 }, "name": "cfnGraphqlSchema", "type": { @@ -4735,7 +4735,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 780 + "line": 787 }, "name": "cfnResolvers", "type": { @@ -4756,7 +4756,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 805 + "line": 812 }, "name": "cfnRoles", "type": { @@ -4777,7 +4777,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 795 + "line": 802 }, "name": "cfnTables", "type": { @@ -4798,7 +4798,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 775 + "line": 782 }, "name": "cfnApiKey", "optional": true, @@ -4821,7 +4821,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 678 + "line": 685 }, "name": "AmplifyGraphqlApiProps", "properties": [ @@ -4835,7 +4835,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 695 + "line": 702 }, "name": "authorizationModes", "type": { @@ -4852,7 +4852,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 683 + "line": 690 }, "name": "definition", "type": { @@ -4869,7 +4869,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 689 + "line": 696 }, "name": "apiName", "optional": true, @@ -4888,7 +4888,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 710 + "line": 717 }, "name": "conflictResolution", "optional": true, @@ -4906,7 +4906,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 754 + "line": 761 }, "name": "dataStoreConfiguration", "optional": true, @@ -4926,7 +4926,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 703 + "line": 710 }, "name": "functionNameMap", "optional": true, @@ -4949,7 +4949,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 725 + "line": 732 }, "name": "functionSlots", "optional": true, @@ -4984,7 +4984,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 748 + "line": 755 }, "name": "outputStorageStrategy", "optional": true, @@ -5001,7 +5001,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 737 + "line": 744 }, "name": "predictionsBucket", "optional": true, @@ -5019,7 +5019,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 719 + "line": 726 }, "name": "stackMappings", "optional": true, @@ -5045,7 +5045,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 732 + "line": 739 }, "name": "transformerPlugins", "optional": true, @@ -5067,7 +5067,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 743 + "line": 750 }, "name": "translationBehavior", "optional": true, @@ -5090,7 +5090,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 822 + "line": 829 }, "name": "AmplifyGraphqlApiResources", "properties": [ @@ -5103,7 +5103,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 846 + "line": 853 }, "name": "cfnResources", "type": { @@ -5119,7 +5119,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 841 + "line": 848 }, "name": "functions", "type": { @@ -5140,7 +5140,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 826 + "line": 833 }, "name": "graphqlApi", "type": { @@ -5156,7 +5156,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 851 + "line": 858 }, "name": "nestedStacks", "type": { @@ -5177,7 +5177,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 836 + "line": 843 }, "name": "roles", "type": { @@ -5198,7 +5198,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 831 + "line": 838 }, "name": "tables", "type": { @@ -6237,7 +6237,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 612 + "line": 619 }, "name": "IAmplifyGraphqlDefinition", "properties": [ @@ -6252,7 +6252,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 637 + "line": 644 }, "name": "dataSourceStrategies", "type": { @@ -6286,7 +6286,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 623 + "line": 630 }, "name": "functionSlots", "type": { @@ -6320,7 +6320,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 617 + "line": 624 }, "name": "schema", "type": { @@ -6337,7 +6337,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 643 + "line": 650 }, "name": "customSqlDataSourceStrategies", "optional": true, @@ -6361,7 +6361,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 631 + "line": 638 }, "name": "referencedLambdaFunctions", "optional": true, @@ -6387,7 +6387,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 649 + "line": 656 }, "name": "IBackendOutputEntry", "properties": [ @@ -6400,7 +6400,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 658 + "line": 665 }, "name": "payload", "type": { @@ -6421,7 +6421,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 653 + "line": 660 }, "name": "version", "type": { @@ -6441,7 +6441,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 664 + "line": 671 }, "methods": [ { @@ -6452,7 +6452,7 @@ }, "locationInModule": { "filename": "src/types.ts", - "line": 671 + "line": 678 }, "name": "addBackendOutputEntry", "parameters": [ @@ -6802,7 +6802,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 504 + "line": 511 }, "name": "PartialTranslationBehavior", "properties": [ @@ -6817,7 +6817,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 596 + "line": 603 }, "name": "allowDestructiveGraphqlSchemaUpdates", "optional": true, @@ -6835,7 +6835,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 516 + "line": 523 }, "name": "disableResolverDeduping", "optional": true, @@ -6857,7 +6857,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 561 + "line": 568 }, "name": "enableAutoIndexQueryNames", "optional": true, @@ -6876,7 +6876,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 576 + "line": 583 }, "name": "enableSearchNodeToNodeEncryption", "optional": true, @@ -6894,7 +6894,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 582 + "line": 589 }, "name": "enableTransformerCfnOutputs", "optional": true, @@ -6912,7 +6912,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 541 + "line": 548 }, "name": "populateOwnerFieldForStaticGroupAuth", "optional": true, @@ -6931,7 +6931,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 606 + "line": 613 }, "name": "replaceTableUponGsiUpdate", "optional": true, @@ -6949,7 +6949,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 567 + "line": 574 }, "name": "respectPrimaryKeyAttributesOnConnectionField", "optional": true, @@ -6967,7 +6967,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 522 + "line": 529 }, "name": "sandboxModeEnabled", "optional": true, @@ -6988,7 +6988,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 554 + "line": 561 }, "name": "secondaryKeyAsGSI", "optional": true, @@ -7009,7 +7009,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 509 + "line": 516 }, "name": "shouldDeepMergeDirectiveConfigDefaults", "optional": true, @@ -7027,7 +7027,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 535 + "line": 542 }, "name": "subscriptionsInheritPrimaryAuth", "optional": true, @@ -7046,7 +7046,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 548 + "line": 555 }, "name": "suppressApiKeyGeneration", "optional": true, @@ -7064,7 +7064,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 529 + "line": 536 }, "name": "useSubUsernameForDefaultIdentityClaim", "optional": true, @@ -8105,6 +8105,23 @@ "primitive": "boolean" } }, + { + "abstract": true, + "docs": { + "default": "true", + "stability": "stable", + "summary": "When disabled usage of Gen 1 patterns will result in an error thrown." + }, + "immutable": true, + "locationInModule": { + "filename": "src/types.ts", + "line": 505 + }, + "name": "allowGen1Patterns", + "type": { + "primitive": "boolean" + } + }, { "abstract": true, "docs": { @@ -8452,5 +8469,5 @@ } }, "version": "1.11.1", - "fingerprint": "fLRs0exr9MTBPSkZ5TNAF8Jli5TTVfukC9TLuSg2Wyw=" + "fingerprint": "h8+kzb8bD9tOHjgEsTIXs6o77j51HTIey8QPgxvF3ZY=" } \ No newline at end of file diff --git a/packages/amplify-graphql-api-construct/API.md b/packages/amplify-graphql-api-construct/API.md index 65c2f02b70..c49230f400 100644 --- a/packages/amplify-graphql-api-construct/API.md +++ b/packages/amplify-graphql-api-construct/API.md @@ -438,6 +438,7 @@ export interface TimeToLiveSpecification { // @public export interface TranslationBehavior { readonly allowDestructiveGraphqlSchemaUpdates: boolean; + readonly allowGen1Patterns: boolean; readonly disableResolverDeduping: boolean; readonly enableAutoIndexQueryNames: boolean; // (undocumented) diff --git a/packages/amplify-graphql-api-construct/src/internal/default-parameters.ts b/packages/amplify-graphql-api-construct/src/internal/default-parameters.ts index 5b3b780219..c028524372 100644 --- a/packages/amplify-graphql-api-construct/src/internal/default-parameters.ts +++ b/packages/amplify-graphql-api-construct/src/internal/default-parameters.ts @@ -20,4 +20,5 @@ export const defaultTranslationBehavior: TranslationBehavior = { enableTransformerCfnOutputs: false, allowDestructiveGraphqlSchemaUpdates: false, replaceTableUponGsiUpdate: false, + allowGen1Patterns: true, }; diff --git a/packages/amplify-graphql-api-construct/src/types.ts b/packages/amplify-graphql-api-construct/src/types.ts index 966f1a4668..71b0bad999 100644 --- a/packages/amplify-graphql-api-construct/src/types.ts +++ b/packages/amplify-graphql-api-construct/src/types.ts @@ -496,6 +496,13 @@ export interface TranslationBehavior { * @experimental */ readonly replaceTableUponGsiUpdate: boolean; + + /** + * When disabled usage of Gen 1 patterns will result in an error thrown. + * + * @default true + */ + readonly allowGen1Patterns: boolean; } /** diff --git a/packages/amplify-graphql-transformer-core/src/transformer-context/transform-parameters.ts b/packages/amplify-graphql-transformer-core/src/transformer-context/transform-parameters.ts index a73e90539b..f40c7ee1fb 100644 --- a/packages/amplify-graphql-transformer-core/src/transformer-context/transform-parameters.ts +++ b/packages/amplify-graphql-transformer-core/src/transformer-context/transform-parameters.ts @@ -10,6 +10,7 @@ export const defaultTransformParameters: TransformParameters = { sandboxModeEnabled: false, allowDestructiveGraphqlSchemaUpdates: false, replaceTableUponGsiUpdate: false, + allowGen1Patterns: true, // Auth Params useSubUsernameForDefaultIdentityClaim: true, diff --git a/packages/amplify-graphql-transformer-interfaces/API.md b/packages/amplify-graphql-transformer-interfaces/API.md index e2c537d77f..97195a5c56 100644 --- a/packages/amplify-graphql-transformer-interfaces/API.md +++ b/packages/amplify-graphql-transformer-interfaces/API.md @@ -914,6 +914,7 @@ export type TransformParameters = { sandboxModeEnabled: boolean; allowDestructiveGraphqlSchemaUpdates: boolean; replaceTableUponGsiUpdate: boolean; + allowGen1Patterns: boolean; useSubUsernameForDefaultIdentityClaim: boolean; populateOwnerFieldForStaticGroupAuth: boolean; suppressApiKeyGeneration: boolean; diff --git a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transform-parameters.ts b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transform-parameters.ts index af1eaffcdf..6f82377918 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transform-parameters.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transform-parameters.ts @@ -14,6 +14,7 @@ export type TransformParameters = { sandboxModeEnabled: boolean; allowDestructiveGraphqlSchemaUpdates: boolean; replaceTableUponGsiUpdate: boolean; + allowGen1Patterns: boolean; // Auth Params useSubUsernameForDefaultIdentityClaim: boolean; diff --git a/packages/amplify-util-mock/src/__e2e__/utils/index.ts b/packages/amplify-util-mock/src/__e2e__/utils/index.ts index 85c1b167bb..a3f9a3d64e 100644 --- a/packages/amplify-util-mock/src/__e2e__/utils/index.ts +++ b/packages/amplify-util-mock/src/__e2e__/utils/index.ts @@ -68,6 +68,7 @@ export const defaultTransformParams: Pick Date: Thu, 27 Jun 2024 15:06:09 -0600 Subject: [PATCH 02/17] feat: disable required relational fields and all many-to-many fields --- .../src/graphql-belongs-to-transformer.ts | 9 +++++++++ .../src/graphql-has-many-transformer.ts | 9 +++++++++ .../src/graphql-has-one-transformer.ts | 9 +++++++++ packages/amplify-graphql-transformer/API.md | 2 +- .../src/graphql-transformer.ts | 9 ++++++--- 5 files changed, 34 insertions(+), 4 deletions(-) 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 c144c58f87..565290b1f0 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 @@ -24,6 +24,7 @@ import { InterfaceTypeDefinitionNode, NamedTypeNode, ObjectTypeDefinitionNode, + Kind, } from 'graphql'; import { getBaseType, isListType, isNonNullType, makeField, makeNamedType, makeNonNullType } from 'graphql-transformer-common'; import produce from 'immer'; @@ -158,6 +159,14 @@ export class BelongsToTransformer extends TransformerPluginBase { const validate = (config: BelongsToDirectiveConfiguration, ctx: TransformerContextProvider): void => { const { field, object } = config; + if (!ctx.transformParameters.allowGen1Patterns) { + if (field.type.kind === Kind.NON_NULL_TYPE) { + throw new InvalidDirectiveError(`@${BelongsToDirective.name} cannot be used on required fields.`); + } + if (config.fields) { + throw new InvalidDirectiveError(`fields argument on @${BelongsToDirective.name} is deprecated. Use references instead.`); + } + } let dbType: ModelDataSourceStrategyDbType; try { 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 bbf0ecc503..97af263363 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 @@ -25,6 +25,7 @@ import { NamedTypeNode, ObjectTypeDefinitionNode, ObjectTypeExtensionNode, + Kind, } from 'graphql'; import { getBaseType, isListType, isNonNullType, makeField, makeNamedType, makeNonNullType } from 'graphql-transformer-common'; import produce from 'immer'; @@ -158,6 +159,14 @@ export class HasManyTransformer extends TransformerPluginBase { const validate = (config: HasManyDirectiveConfiguration, ctx: TransformerContextProvider): void => { const { field } = config; + if (!ctx.transformParameters.allowGen1Patterns) { + if (field.type.kind === Kind.NON_NULL_TYPE) { + throw new InvalidDirectiveError(`@${HasManyDirective.name} cannot be used on required fields.`); + } + if (config.fields) { + throw new InvalidDirectiveError(`fields argument on @${HasManyDirective.name} is deprecated. Use references instead.`); + } + } if (!isListType(field.type)) { throw new InvalidDirectiveError(`@${HasManyDirective.name} must be used with a list. Use @hasOne for non-list types.`); 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 a778825d59..4b7a29d9dc 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 @@ -24,6 +24,7 @@ import { InterfaceTypeDefinitionNode, NamedTypeNode, ObjectTypeDefinitionNode, + Kind, } from 'graphql'; import { getBaseType, @@ -186,6 +187,14 @@ export class HasOneTransformer extends TransformerPluginBase { const validate = (config: HasOneDirectiveConfiguration, ctx: TransformerContextProvider): void => { const { field } = config; + if (!ctx.transformParameters.allowGen1Patterns) { + if (field.type.kind === Kind.NON_NULL_TYPE) { + throw new InvalidDirectiveError(`@${HasOneDirective.name} cannot be used on required fields.`); + } + if (config.fields) { + throw new InvalidDirectiveError(`fields argument on @${HasOneDirective.name} is deprecated. Use references instead.`); + } + } let dbType: ModelDataSourceStrategyDbType; try { diff --git a/packages/amplify-graphql-transformer/API.md b/packages/amplify-graphql-transformer/API.md index 01a514a4ef..4c6b40819c 100644 --- a/packages/amplify-graphql-transformer/API.md +++ b/packages/amplify-graphql-transformer/API.md @@ -25,7 +25,7 @@ import { UserDefinedSlot } from '@aws-amplify/graphql-transformer-core'; export const constructTransform: (config: TransformConfig) => GraphQLTransform; // @public (undocumented) -export const constructTransformerChain: (options?: TransformerFactoryArgs) => TransformerPluginProvider[]; +export const constructTransformerChain: (options?: TransformerFactoryArgs, allowGen1Patterns?: boolean) => TransformerPluginProvider[]; // @public (undocumented) export const executeTransform: (config: ExecuteTransformConfig) => void; diff --git a/packages/amplify-graphql-transformer/src/graphql-transformer.ts b/packages/amplify-graphql-transformer/src/graphql-transformer.ts index 563b91efdd..48e6badf77 100644 --- a/packages/amplify-graphql-transformer/src/graphql-transformer.ts +++ b/packages/amplify-graphql-transformer/src/graphql-transformer.ts @@ -56,7 +56,10 @@ export type TransformConfig = { transformParameters: TransformParameters; }; -export const constructTransformerChain = (options?: TransformerFactoryArgs): TransformerPluginProvider[] => { +export const constructTransformerChain = ( + options?: TransformerFactoryArgs, + allowGen1Patterns: boolean = true, +): TransformerPluginProvider[] => { const modelTransformer = new ModelTransformer(); const authTransformer = new AuthTransformer(); const indexTransformer = new IndexTransformer(); @@ -72,7 +75,7 @@ export const constructTransformerChain = (options?: TransformerFactoryArgs): Tra indexTransformer, new HasManyTransformer(), hasOneTransformer, - new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer), + ...(allowGen1Patterns ? [new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer)] : []), new BelongsToTransformer(), new DefaultValueTransformer(), authTransformer, @@ -92,7 +95,7 @@ export const constructTransformerChain = (options?: TransformerFactoryArgs): Tra export const constructTransform = (config: TransformConfig): GraphQLTransform => { const { transformersFactoryArgs, authConfig, resolverConfig, userDefinedSlots, stackMapping, transformParameters } = config; - const transformers = constructTransformerChain(transformersFactoryArgs); + const transformers = constructTransformerChain(transformersFactoryArgs, transformParameters.allowGen1Patterns); return new GraphQLTransform({ transformers, From 98dbf0e1cc53b14a54b66de43190de6ec57469f0 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Fri, 28 Jun 2024 08:57:41 -0600 Subject: [PATCH 03/17] test: add test suite for disallowed patterns --- .../disable-gen1-patterns.test.ts | 122 ++++++++++++++++++ .../src/types.ts | 7 + 2 files changed, 129 insertions(+) create mode 100644 packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts new file mode 100644 index 0000000000..a788d62eac --- /dev/null +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts @@ -0,0 +1,122 @@ +import * as cdk from 'aws-cdk-lib'; +import { Template } from 'aws-cdk-lib/assertions'; +import { AmplifyGraphqlApi } from '../../amplify-graphql-api'; +import { AmplifyGraphqlDefinition } from '../../amplify-graphql-definition'; + +/** + * Utility to test if schema is valid when gen 1 patterns are disabled + * @param schema schema to test + */ +const verifySchema = (schema: string): void => { + const stack = new cdk.Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(schema), + authorizationModes: { + apiKeyConfig: { expires: cdk.Duration.days(7) }, + }, + translationBehavior: { + allowGen1Patterns: false, + }, + }); + Template.fromStack(stack); +}; + +describe('Disallow Gen 1 Patterns', () => { + test('does not allow @manyToMany', () => { + expect(() => + verifySchema(` + type Post @model { + tags: [Tag] @manyToMany + } + + type Tag @model { + posts: [Post] @manyToMany + } + `), + ).toThrow('Unknown directive "@manyToMany".'); + }); + + test('does not allow fields on @belongsTo', () => { + expect(() => + verifySchema(` + type Post @model { + author: Author @belongsTo(fields: []) + } + + type Author @model { + posts: [Post] @hasMany + } + `), + ).toThrow('fields argument on @belongsTo is deprecated. Use references instead.'); + }); + + test('does not allow fields on @hasMany', () => { + expect(() => + verifySchema(` + type Post @model { + author: Author @belongsTo + } + + type Author @model { + posts: [Post] @hasMany(fields: []) + } + `), + ).toThrow('fields argument on @hasMany is deprecated. Use references instead.'); + }); + + test('does not allow fields on @hasOne', () => { + expect(() => + verifySchema(` + type Profile @model { + author: Author @belongsTo + } + + type Author @model { + profile: Profile @hasOne(fields: []) + } + `), + ).toThrow('fields argument on @hasOne is deprecated. Use references instead.'); + }); + + test('does not allow required @belongsTo fields', () => { + expect(() => + verifySchema(` + type Post @model { + author: Author! @belongsTo + } + + type Author @model { + posts: [Post] @hasMany + } + `), + ).toThrow('@belongsTo cannot be used on required fields.'); + }); + + test('does not allow required @hasMany fields', () => { + expect(() => + verifySchema(` + type Post @model { + author: Author @belongsTo + } + + type Author @model { + posts: [Post]! @hasMany + } + `), + ).toThrow('@hasMany cannot be used on required fields.'); + }); + + test('does not allow required @hasOne fields', () => { + expect(() => + verifySchema(` + type Profile @model { + author: Author @belongsTo + } + + type Author @model { + profile: Profile! @hasOne + } + `), + ).toThrow('@hasOne cannot be used on required fields.'); + }); +}); diff --git a/packages/amplify-graphql-api-construct/src/types.ts b/packages/amplify-graphql-api-construct/src/types.ts index 71b0bad999..60a8492395 100644 --- a/packages/amplify-graphql-api-construct/src/types.ts +++ b/packages/amplify-graphql-api-construct/src/types.ts @@ -611,6 +611,13 @@ export interface PartialTranslationBehavior { * @experimental */ readonly replaceTableUponGsiUpdate?: boolean; + + /** + * When disabled usage of Gen 1 patterns will result in an error thrown. + * + * @default true + */ + readonly allowGen1Patterns?: boolean; } /** From 65ca23ee6a01b48276b7311d5cb91d84ff1f9c52 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Fri, 28 Jun 2024 10:21:07 -0600 Subject: [PATCH 04/17] chore: extract api --- packages/amplify-graphql-api-construct/.jsii | 122 ++++++++++-------- packages/amplify-graphql-api-construct/API.md | 1 + 2 files changed, 71 insertions(+), 52 deletions(-) diff --git a/packages/amplify-graphql-api-construct/.jsii b/packages/amplify-graphql-api-construct/.jsii index 68eafccf6e..35c28fbff7 100644 --- a/packages/amplify-graphql-api-construct/.jsii +++ b/packages/amplify-graphql-api-construct/.jsii @@ -3537,7 +3537,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 865 + "line": 872 }, "name": "AddFunctionProps", "properties": [ @@ -3550,7 +3550,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 869 + "line": 876 }, "name": "dataSource", "type": { @@ -3566,7 +3566,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 874 + "line": 881 }, "name": "name", "type": { @@ -3583,7 +3583,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 909 + "line": 916 }, "name": "code", "optional": true, @@ -3601,7 +3601,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 881 + "line": 888 }, "name": "description", "optional": true, @@ -3619,7 +3619,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 888 + "line": 895 }, "name": "requestMappingTemplate", "optional": true, @@ -3637,7 +3637,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 895 + "line": 902 }, "name": "responseMappingTemplate", "optional": true, @@ -3655,7 +3655,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 902 + "line": 909 }, "name": "runtime", "optional": true, @@ -4585,7 +4585,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 768 + "line": 775 }, "name": "AmplifyGraphqlApiCfnResources", "properties": [ @@ -4598,7 +4598,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 822 + "line": 829 }, "name": "additionalCfnResources", "type": { @@ -4619,7 +4619,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 807 + "line": 814 }, "name": "amplifyDynamoDbTables", "type": { @@ -4640,7 +4640,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 797 + "line": 804 }, "name": "cfnDataSources", "type": { @@ -4661,7 +4661,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 792 + "line": 799 }, "name": "cfnFunctionConfigurations", "type": { @@ -4682,7 +4682,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 817 + "line": 824 }, "name": "cfnFunctions", "type": { @@ -4703,7 +4703,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 772 + "line": 779 }, "name": "cfnGraphqlApi", "type": { @@ -4719,7 +4719,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 777 + "line": 784 }, "name": "cfnGraphqlSchema", "type": { @@ -4735,7 +4735,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 787 + "line": 794 }, "name": "cfnResolvers", "type": { @@ -4756,7 +4756,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 812 + "line": 819 }, "name": "cfnRoles", "type": { @@ -4777,7 +4777,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 802 + "line": 809 }, "name": "cfnTables", "type": { @@ -4798,7 +4798,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 782 + "line": 789 }, "name": "cfnApiKey", "optional": true, @@ -4821,7 +4821,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 685 + "line": 692 }, "name": "AmplifyGraphqlApiProps", "properties": [ @@ -4835,7 +4835,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 702 + "line": 709 }, "name": "authorizationModes", "type": { @@ -4852,7 +4852,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 690 + "line": 697 }, "name": "definition", "type": { @@ -4869,7 +4869,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 696 + "line": 703 }, "name": "apiName", "optional": true, @@ -4888,7 +4888,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 717 + "line": 724 }, "name": "conflictResolution", "optional": true, @@ -4906,7 +4906,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 761 + "line": 768 }, "name": "dataStoreConfiguration", "optional": true, @@ -4926,7 +4926,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 710 + "line": 717 }, "name": "functionNameMap", "optional": true, @@ -4949,7 +4949,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 732 + "line": 739 }, "name": "functionSlots", "optional": true, @@ -4984,7 +4984,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 755 + "line": 762 }, "name": "outputStorageStrategy", "optional": true, @@ -5001,7 +5001,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 744 + "line": 751 }, "name": "predictionsBucket", "optional": true, @@ -5019,7 +5019,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 726 + "line": 733 }, "name": "stackMappings", "optional": true, @@ -5045,7 +5045,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 739 + "line": 746 }, "name": "transformerPlugins", "optional": true, @@ -5067,7 +5067,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 750 + "line": 757 }, "name": "translationBehavior", "optional": true, @@ -5090,7 +5090,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 829 + "line": 836 }, "name": "AmplifyGraphqlApiResources", "properties": [ @@ -5103,7 +5103,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 853 + "line": 860 }, "name": "cfnResources", "type": { @@ -5119,7 +5119,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 848 + "line": 855 }, "name": "functions", "type": { @@ -5140,7 +5140,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 833 + "line": 840 }, "name": "graphqlApi", "type": { @@ -5156,7 +5156,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 858 + "line": 865 }, "name": "nestedStacks", "type": { @@ -5177,7 +5177,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 843 + "line": 850 }, "name": "roles", "type": { @@ -5198,7 +5198,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 838 + "line": 845 }, "name": "tables", "type": { @@ -6237,7 +6237,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 619 + "line": 626 }, "name": "IAmplifyGraphqlDefinition", "properties": [ @@ -6252,7 +6252,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 644 + "line": 651 }, "name": "dataSourceStrategies", "type": { @@ -6286,7 +6286,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 630 + "line": 637 }, "name": "functionSlots", "type": { @@ -6320,7 +6320,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 624 + "line": 631 }, "name": "schema", "type": { @@ -6337,7 +6337,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 650 + "line": 657 }, "name": "customSqlDataSourceStrategies", "optional": true, @@ -6361,7 +6361,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 638 + "line": 645 }, "name": "referencedLambdaFunctions", "optional": true, @@ -6387,7 +6387,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 656 + "line": 663 }, "name": "IBackendOutputEntry", "properties": [ @@ -6400,7 +6400,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 665 + "line": 672 }, "name": "payload", "type": { @@ -6421,7 +6421,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 660 + "line": 667 }, "name": "version", "type": { @@ -6441,7 +6441,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 671 + "line": 678 }, "methods": [ { @@ -6452,7 +6452,7 @@ }, "locationInModule": { "filename": "src/types.ts", - "line": 678 + "line": 685 }, "name": "addBackendOutputEntry", "parameters": [ @@ -6825,6 +6825,24 @@ "primitive": "boolean" } }, + { + "abstract": true, + "docs": { + "default": "true", + "stability": "stable", + "summary": "When disabled usage of Gen 1 patterns will result in an error thrown." + }, + "immutable": true, + "locationInModule": { + "filename": "src/types.ts", + "line": 620 + }, + "name": "allowGen1Patterns", + "optional": true, + "type": { + "primitive": "boolean" + } + }, { "abstract": true, "docs": { @@ -8469,5 +8487,5 @@ } }, "version": "1.11.1", - "fingerprint": "h8+kzb8bD9tOHjgEsTIXs6o77j51HTIey8QPgxvF3ZY=" + "fingerprint": "2wmG0N5kBjtA+aK3rlV8/q7K/7DLfdP+golmlXXr04M=" } \ No newline at end of file diff --git a/packages/amplify-graphql-api-construct/API.md b/packages/amplify-graphql-api-construct/API.md index c49230f400..7e555df386 100644 --- a/packages/amplify-graphql-api-construct/API.md +++ b/packages/amplify-graphql-api-construct/API.md @@ -314,6 +314,7 @@ export interface OptimisticConflictResolutionStrategy extends ConflictResolution // @public export interface PartialTranslationBehavior { readonly allowDestructiveGraphqlSchemaUpdates?: boolean; + readonly allowGen1Patterns?: boolean; readonly disableResolverDeduping?: boolean; readonly enableAutoIndexQueryNames?: boolean; readonly enableSearchNodeToNodeEncryption?: boolean; From dfff09c54d75cdb9c43059105b07752d177280a4 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Fri, 28 Jun 2024 10:25:31 -0600 Subject: [PATCH 05/17] test: fix test type --- .../src/__tests__/graphql-transformer.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts b/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts index 1829d714b3..8c3a056921 100644 --- a/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts +++ b/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts @@ -56,6 +56,7 @@ const defaultTransformConfig: TransformConfig = { enableTransformerCfnOutputs: true, allowDestructiveGraphqlSchemaUpdates: false, replaceTableUponGsiUpdate: false, + allowGen1Patterns: true, }, }; From 76ab1299c9860e9fcb55b3774cf2a72d09a41a6b Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Mon, 1 Jul 2024 09:19:39 -0600 Subject: [PATCH 06/17] test: add more tests --- .../package.json | 2 +- .../disable-gen1-patterns.test.ts | 191 ++++++++++++++++-- 2 files changed, 170 insertions(+), 23 deletions(-) diff --git a/packages/amplify-graphql-api-construct/package.json b/packages/amplify-graphql-api-construct/package.json index d4ec2f083f..7020ba7d54 100644 --- a/packages/amplify-graphql-api-construct/package.json +++ b/packages/amplify-graphql-api-construct/package.json @@ -164,7 +164,7 @@ "global": { "branches": 90, "functions": 90, - "lines": 60 + "lines": 59 } }, "coverageReporters": [ diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts index a788d62eac..9e4da5f211 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts @@ -6,8 +6,9 @@ import { AmplifyGraphqlDefinition } from '../../amplify-graphql-definition'; /** * Utility to test if schema is valid when gen 1 patterns are disabled * @param schema schema to test + * @param allowGen1Patterns if gen 1 patterns are allowed. */ -const verifySchema = (schema: string): void => { +const verifySchema = (schema: string, allowGen1Patterns: boolean): void => { const stack = new cdk.Stack(); new AmplifyGraphqlApi(stack, 'TestApi', { definition: AmplifyGraphqlDefinition.fromString(schema), @@ -15,72 +16,159 @@ const verifySchema = (schema: string): void => { apiKeyConfig: { expires: cdk.Duration.days(7) }, }, translationBehavior: { - allowGen1Patterns: false, + allowGen1Patterns, }, }); Template.fromStack(stack); }; -describe('Disallow Gen 1 Patterns', () => { +describe('allowGen1Patterns', () => { test('does not allow @manyToMany', () => { expect(() => - verifySchema(` + verifySchema( + ` type Post @model { - tags: [Tag] @manyToMany + tags: [Tag] @manyToMany(relationName: "PostTags") } type Tag @model { - posts: [Post] @manyToMany + posts: [Post] @manyToMany(relationName: "PostTags") } - `), + `, + false, + ), ).toThrow('Unknown directive "@manyToMany".'); }); + test('allows @manyToMany', () => { + expect(() => + verifySchema( + ` + type Post @model { + tags: [Tag] @manyToMany(relationName: "PostTags") + } + + type Tag @model { + posts: [Post] @manyToMany(relationName: "PostTags") + } + `, + true, + ), + ).not.toThrow(); + }); + test('does not allow fields on @belongsTo', () => { expect(() => - verifySchema(` + verifySchema( + ` type Post @model { - author: Author @belongsTo(fields: []) + authorID: ID + author: Author @belongsTo(fields: ["authorID"]) } type Author @model { posts: [Post] @hasMany } - `), + `, + false, + ), ).toThrow('fields argument on @belongsTo is deprecated. Use references instead.'); }); + test('allows fields on @belongsTo', () => { + expect(() => + verifySchema( + ` + type Post @model { + authorID: ID + author: Author @belongsTo(fields: ["authorID"]) + } + + type Author @model { + posts: [Post] @hasMany + } + `, + true, + ), + ).not.toThrow(); + }); + test('does not allow fields on @hasMany', () => { expect(() => - verifySchema(` + verifySchema( + ` type Post @model { author: Author @belongsTo } type Author @model { - posts: [Post] @hasMany(fields: []) + postID: ID + posts: [Post] @hasMany(fields: ["postID"]) } - `), + `, + false, + ), ).toThrow('fields argument on @hasMany is deprecated. Use references instead.'); }); + test('allows fields on @hasMany', () => { + expect(() => + verifySchema( + ` + type Post @model { + author: Author @belongsTo + } + + type Author @model { + postID: ID + posts: [Post] @hasMany(fields: ["postID"]) + } + `, + true, + ), + ).not.toThrow(); + }); + test('does not allow fields on @hasOne', () => { expect(() => - verifySchema(` + verifySchema( + ` type Profile @model { author: Author @belongsTo } type Author @model { - profile: Profile @hasOne(fields: []) + profileID: ID + profile: Profile @hasOne(fields: ["profileID"]) } - `), + `, + false, + ), ).toThrow('fields argument on @hasOne is deprecated. Use references instead.'); }); + test('allows fields on @hasOne', () => { + expect(() => + verifySchema( + ` + type Profile @model { + author: Author @belongsTo + } + + type Author @model { + profileID: ID + profile: Profile @hasOne(fields: ["profileID"]) + } + `, + true, + ), + ).not.toThrow(); + }); + test('does not allow required @belongsTo fields', () => { expect(() => - verifySchema(` + verifySchema( + ` type Post @model { author: Author! @belongsTo } @@ -88,13 +176,33 @@ describe('Disallow Gen 1 Patterns', () => { type Author @model { posts: [Post] @hasMany } - `), + `, + false, + ), ).toThrow('@belongsTo cannot be used on required fields.'); }); + test('allows required @belongsTo fields', () => { + expect(() => + verifySchema( + ` + type Post @model { + author: Author! @belongsTo + } + + type Author @model { + posts: [Post] @hasMany + } + `, + true, + ), + ).not.toThrow(); + }); + test('does not allow required @hasMany fields', () => { expect(() => - verifySchema(` + verifySchema( + ` type Post @model { author: Author @belongsTo } @@ -102,13 +210,33 @@ describe('Disallow Gen 1 Patterns', () => { type Author @model { posts: [Post]! @hasMany } - `), + `, + false, + ), ).toThrow('@hasMany cannot be used on required fields.'); }); + test('allows required @hasMany fields', () => { + expect(() => + verifySchema( + ` + type Post @model { + author: Author @belongsTo + } + + type Author @model { + posts: [Post]! @hasMany + } + `, + true, + ), + ).not.toThrow(); + }); + test('does not allow required @hasOne fields', () => { expect(() => - verifySchema(` + verifySchema( + ` type Profile @model { author: Author @belongsTo } @@ -116,7 +244,26 @@ describe('Disallow Gen 1 Patterns', () => { type Author @model { profile: Profile! @hasOne } - `), + `, + false, + ), ).toThrow('@hasOne cannot be used on required fields.'); }); + + test('allows required @hasOne fields', () => { + expect(() => + verifySchema( + ` + type Profile @model { + author: Author @belongsTo + } + + type Author @model { + profile: Profile! @hasOne + } + `, + true, + ), + ).not.toThrow(); + }); }); From 4cfd51367b7eed1fc71a0a44860a46b931a28a73 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Mon, 1 Jul 2024 09:28:42 -0600 Subject: [PATCH 07/17] test: add test for default behavior --- .../disable-gen1-patterns.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts index 9e4da5f211..f32073aaac 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts @@ -23,6 +23,29 @@ const verifySchema = (schema: string, allowGen1Patterns: boolean): void => { }; describe('allowGen1Patterns', () => { + const schema = ` + type Post @model { + tags: [Tag] @manyToMany(relationName: "PostTags") + } + + type Tag @model { + posts: [Post] @manyToMany(relationName: "PostTags") + } + `; + + test('defaults to allow', () => { + const stack = new cdk.Stack(); + expect( + () => + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(schema), + authorizationModes: { + apiKeyConfig: { expires: cdk.Duration.days(7) }, + }, + }), + ).not.toThrow(); + }); + test('does not allow @manyToMany', () => { expect(() => verifySchema( From 9f0fb191a9c289fe017307834a62980a46b885ed Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Mon, 1 Jul 2024 10:30:21 -0600 Subject: [PATCH 08/17] fix: disallow predictions --- .../disable-gen1-patterns.test.ts | 26 +++++++++++++++++++ .../src/graphql-transformer.ts | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts index f32073aaac..321f535daf 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts @@ -80,6 +80,32 @@ describe('allowGen1Patterns', () => { ).not.toThrow(); }); + test('does not allow @searchable', () => { + expect(() => + verifySchema( + ` + type Post @model @searchable { + title: String + } + `, + false, + ), + ).toThrow('Unknown directive "@searchable".'); + }); + + test('allows @searchable', () => { + expect(() => + verifySchema( + ` + type Post @model @searchable { + title: String + } + `, + true, + ), + ).not.toThrow(); + }); + test('does not allow fields on @belongsTo', () => { expect(() => verifySchema( diff --git a/packages/amplify-graphql-transformer/src/graphql-transformer.ts b/packages/amplify-graphql-transformer/src/graphql-transformer.ts index 48e6badf77..4f2914f8e5 100644 --- a/packages/amplify-graphql-transformer/src/graphql-transformer.ts +++ b/packages/amplify-graphql-transformer/src/graphql-transformer.ts @@ -82,7 +82,7 @@ export const constructTransformerChain = ( new MapsToTransformer(), new SqlTransformer(), new RefersToTransformer(), - new SearchableModelTransformer(), + ...(allowGen1Patterns ? [new SearchableModelTransformer()] : []), ...(options?.customTransformers ?? []), ]; }; From 1d0e9b0f6dd6aa859df0263b3b70338caecf839a Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Mon, 1 Jul 2024 10:39:46 -0600 Subject: [PATCH 09/17] fix: disallow predictions --- .../disable-gen1-patterns.test.ts | 55 +++++++++++++++---- .../src/graphql-transformer.ts | 2 +- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts index 321f535daf..d90247b063 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts @@ -1,5 +1,6 @@ import * as cdk from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; +import { Bucket } from 'aws-cdk-lib/aws-s3'; import { AmplifyGraphqlApi } from '../../amplify-graphql-api'; import { AmplifyGraphqlDefinition } from '../../amplify-graphql-definition'; @@ -23,17 +24,16 @@ const verifySchema = (schema: string, allowGen1Patterns: boolean): void => { }; describe('allowGen1Patterns', () => { - const schema = ` - type Post @model { - tags: [Tag] @manyToMany(relationName: "PostTags") - } - - type Tag @model { - posts: [Post] @manyToMany(relationName: "PostTags") - } - `; - test('defaults to allow', () => { + const schema = ` + type Post @model { + tags: [Tag] @manyToMany(relationName: "PostTags") + } + + type Tag @model { + posts: [Post] @manyToMany(relationName: "PostTags") + } + `; const stack = new cdk.Stack(); expect( () => @@ -106,6 +106,41 @@ describe('allowGen1Patterns', () => { ).not.toThrow(); }); + test('does not allow @predictions', () => { + expect(() => + verifySchema( + ` + type Query { + recognizeLabelsFromImage: [String] @predictions(actions: [identifyLabels]) + } + `, + false, + ), + ).toThrow('Unknown directive "@predictions".'); + }); + + test('allows @predictions', () => { + const schema = ` + type Query { + recognizeLabelsFromImage: [String] @predictions(actions: [identifyLabels]) + } + `; + const stack = new cdk.Stack(); + expect( + () => + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(schema), + authorizationModes: { + apiKeyConfig: { expires: cdk.Duration.days(7) }, + }, + translationBehavior: { + allowGen1Patterns: true, + }, + predictionsBucket: new Bucket(stack, 'myfakebucket'), + }), + ).not.toThrow(); + }); + test('does not allow fields on @belongsTo', () => { expect(() => verifySchema( diff --git a/packages/amplify-graphql-transformer/src/graphql-transformer.ts b/packages/amplify-graphql-transformer/src/graphql-transformer.ts index 4f2914f8e5..a30372bb48 100644 --- a/packages/amplify-graphql-transformer/src/graphql-transformer.ts +++ b/packages/amplify-graphql-transformer/src/graphql-transformer.ts @@ -70,7 +70,7 @@ export const constructTransformerChain = ( modelTransformer, new FunctionTransformer(options?.functionNameMap), new HttpTransformer(), - new PredictionsTransformer(options?.storageConfig), + ...(allowGen1Patterns ? [new PredictionsTransformer(options?.storageConfig)] : []), new PrimaryKeyTransformer(), indexTransformer, new HasManyTransformer(), From c2b2885fdf4cacbfa6927214cbca6b3533b511f3 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Mon, 1 Jul 2024 14:08:07 -0600 Subject: [PATCH 10/17] test: fix mock e2e param --- packages/amplify-util-mock/src/__e2e__/utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amplify-util-mock/src/__e2e__/utils/index.ts b/packages/amplify-util-mock/src/__e2e__/utils/index.ts index a3f9a3d64e..6d9c3709d3 100644 --- a/packages/amplify-util-mock/src/__e2e__/utils/index.ts +++ b/packages/amplify-util-mock/src/__e2e__/utils/index.ts @@ -68,7 +68,7 @@ export const defaultTransformParams: Pick Date: Wed, 3 Jul 2024 14:18:21 -0600 Subject: [PATCH 11/17] fix: add model name and field name to error messages --- .../__functional__/disable-gen1-patterns.test.ts | 12 ++++++------ .../src/graphql-belongs-to-transformer.ts | 10 ++++++++-- .../src/graphql-has-many-transformer.ts | 12 +++++++++--- .../src/graphql-has-one-transformer.ts | 12 +++++++++--- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts index d90247b063..d58385ef0f 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts @@ -156,7 +156,7 @@ describe('allowGen1Patterns', () => { `, false, ), - ).toThrow('fields argument on @belongsTo is deprecated. Use references instead.'); + ).toThrow('fields argument on @belongsTo is deprecated. Modify Post.author to use references instead.'); }); test('allows fields on @belongsTo', () => { @@ -192,7 +192,7 @@ describe('allowGen1Patterns', () => { `, false, ), - ).toThrow('fields argument on @hasMany is deprecated. Use references instead.'); + ).toThrow('fields argument on @hasMany is deprecated. Modify Author.posts to use references instead.'); }); test('allows fields on @hasMany', () => { @@ -228,7 +228,7 @@ describe('allowGen1Patterns', () => { `, false, ), - ).toThrow('fields argument on @hasOne is deprecated. Use references instead.'); + ).toThrow('fields argument on @hasOne is deprecated. Modify Author.profile to use references instead.'); }); test('allows fields on @hasOne', () => { @@ -263,7 +263,7 @@ describe('allowGen1Patterns', () => { `, false, ), - ).toThrow('@belongsTo cannot be used on required fields.'); + ).toThrow('@belongsTo cannot be used on required fields. Modify Post.author to be optional.'); }); test('allows required @belongsTo fields', () => { @@ -297,7 +297,7 @@ describe('allowGen1Patterns', () => { `, false, ), - ).toThrow('@hasMany cannot be used on required fields.'); + ).toThrow('@hasMany cannot be used on required fields. Modify Author.posts to be optional.'); }); test('allows required @hasMany fields', () => { @@ -331,7 +331,7 @@ describe('allowGen1Patterns', () => { `, false, ), - ).toThrow('@hasOne cannot be used on required fields.'); + ).toThrow('@hasOne cannot be used on required fields. Modify Author.profile to be optional.'); }); test('allows required @hasOne fields', () => { 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 565290b1f0..e40033e355 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 @@ -160,11 +160,17 @@ export class BelongsToTransformer extends TransformerPluginBase { const validate = (config: BelongsToDirectiveConfiguration, ctx: TransformerContextProvider): void => { const { field, object } = config; if (!ctx.transformParameters.allowGen1Patterns) { + const modelName = object.name.value; + const fieldName = field.name.value; if (field.type.kind === Kind.NON_NULL_TYPE) { - throw new InvalidDirectiveError(`@${BelongsToDirective.name} cannot be used on required fields.`); + throw new InvalidDirectiveError( + `@${BelongsToDirective.name} cannot be used on required fields. Modify ${modelName}.${fieldName} to be optional.`, + ); } if (config.fields) { - throw new InvalidDirectiveError(`fields argument on @${BelongsToDirective.name} is deprecated. Use references instead.`); + throw new InvalidDirectiveError( + `fields argument on @${BelongsToDirective.name} is deprecated. Modify ${modelName}.${fieldName} to use references instead.`, + ); } } 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 97af263363..f869bdc822 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 @@ -158,13 +158,19 @@ export class HasManyTransformer extends TransformerPluginBase { } const validate = (config: HasManyDirectiveConfiguration, ctx: TransformerContextProvider): void => { - const { field } = config; + const { field, object } = config; if (!ctx.transformParameters.allowGen1Patterns) { + const modelName = object.name.value; + const fieldName = field.name.value; if (field.type.kind === Kind.NON_NULL_TYPE) { - throw new InvalidDirectiveError(`@${HasManyDirective.name} cannot be used on required fields.`); + throw new InvalidDirectiveError( + `@${HasManyDirective.name} cannot be used on required fields. Modify ${modelName}.${fieldName} to be optional.`, + ); } if (config.fields) { - throw new InvalidDirectiveError(`fields argument on @${HasManyDirective.name} is deprecated. Use references instead.`); + throw new InvalidDirectiveError( + `fields argument on @${HasManyDirective.name} is deprecated. Modify ${modelName}.${fieldName} to use references instead.`, + ); } } 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 4b7a29d9dc..2c14c50bac 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 @@ -186,13 +186,19 @@ export class HasOneTransformer extends TransformerPluginBase { } const validate = (config: HasOneDirectiveConfiguration, ctx: TransformerContextProvider): void => { - const { field } = config; + const { field, object } = config; if (!ctx.transformParameters.allowGen1Patterns) { + const modelName = object.name.value; + const fieldName = field.name.value; if (field.type.kind === Kind.NON_NULL_TYPE) { - throw new InvalidDirectiveError(`@${HasOneDirective.name} cannot be used on required fields.`); + throw new InvalidDirectiveError( + `@${HasOneDirective.name} cannot be used on required fields. Modify ${modelName}.${fieldName} to be optional.`, + ); } if (config.fields) { - throw new InvalidDirectiveError(`fields argument on @${HasOneDirective.name} is deprecated. Use references instead.`); + throw new InvalidDirectiveError( + `fields argument on @${HasOneDirective.name} is deprecated. Modify ${modelName}.${fieldName} to use references instead.`, + ); } } From f3be157a6204292a95a4b32e9e0e18916cfd1be8 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Wed, 3 Jul 2024 15:21:33 -0600 Subject: [PATCH 12/17] test: update test descriptions --- .../disable-gen1-patterns.test.ts | 600 +++++++++--------- 1 file changed, 302 insertions(+), 298 deletions(-) diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts index d58385ef0f..cfb2188c7d 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts @@ -46,308 +46,312 @@ describe('allowGen1Patterns', () => { ).not.toThrow(); }); - test('does not allow @manyToMany', () => { - expect(() => - verifySchema( - ` - type Post @model { - tags: [Tag] @manyToMany(relationName: "PostTags") - } - - type Tag @model { - posts: [Post] @manyToMany(relationName: "PostTags") - } - `, - false, - ), - ).toThrow('Unknown directive "@manyToMany".'); - }); - - test('allows @manyToMany', () => { - expect(() => - verifySchema( - ` - type Post @model { - tags: [Tag] @manyToMany(relationName: "PostTags") - } - - type Tag @model { - posts: [Post] @manyToMany(relationName: "PostTags") - } - `, - true, - ), - ).not.toThrow(); - }); - - test('does not allow @searchable', () => { - expect(() => - verifySchema( - ` - type Post @model @searchable { - title: String - } - `, - false, - ), - ).toThrow('Unknown directive "@searchable".'); - }); - - test('allows @searchable', () => { - expect(() => - verifySchema( - ` - type Post @model @searchable { - title: String - } - `, - true, - ), - ).not.toThrow(); - }); - - test('does not allow @predictions', () => { - expect(() => - verifySchema( - ` + describe('allowGen1Patterns: true', () => { + test('allows @manyToMany', () => { + expect(() => + verifySchema( + ` + type Post @model { + tags: [Tag] @manyToMany(relationName: "PostTags") + } + + type Tag @model { + posts: [Post] @manyToMany(relationName: "PostTags") + } + `, + true, + ), + ).not.toThrow(); + }); + + test('allows @searchable', () => { + expect(() => + verifySchema( + ` + type Post @model @searchable { + title: String + } + `, + true, + ), + ).not.toThrow(); + }); + + test('allows @predictions', () => { + const schema = ` type Query { recognizeLabelsFromImage: [String] @predictions(actions: [identifyLabels]) } - `, - false, - ), - ).toThrow('Unknown directive "@predictions".'); - }); - - test('allows @predictions', () => { - const schema = ` - type Query { - recognizeLabelsFromImage: [String] @predictions(actions: [identifyLabels]) - } - `; - const stack = new cdk.Stack(); - expect( - () => - new AmplifyGraphqlApi(stack, 'TestApi', { - definition: AmplifyGraphqlDefinition.fromString(schema), - authorizationModes: { - apiKeyConfig: { expires: cdk.Duration.days(7) }, - }, - translationBehavior: { - allowGen1Patterns: true, - }, - predictionsBucket: new Bucket(stack, 'myfakebucket'), - }), - ).not.toThrow(); - }); - - test('does not allow fields on @belongsTo', () => { - expect(() => - verifySchema( - ` - type Post @model { - authorID: ID - author: Author @belongsTo(fields: ["authorID"]) - } - - type Author @model { - posts: [Post] @hasMany - } - `, - false, - ), - ).toThrow('fields argument on @belongsTo is deprecated. Modify Post.author to use references instead.'); - }); - - test('allows fields on @belongsTo', () => { - expect(() => - verifySchema( - ` - type Post @model { - authorID: ID - author: Author @belongsTo(fields: ["authorID"]) - } - - type Author @model { - posts: [Post] @hasMany - } - `, - true, - ), - ).not.toThrow(); - }); - - test('does not allow fields on @hasMany', () => { - expect(() => - verifySchema( - ` - type Post @model { - author: Author @belongsTo - } - - type Author @model { - postID: ID - posts: [Post] @hasMany(fields: ["postID"]) - } - `, - false, - ), - ).toThrow('fields argument on @hasMany is deprecated. Modify Author.posts to use references instead.'); - }); - - test('allows fields on @hasMany', () => { - expect(() => - verifySchema( - ` - type Post @model { - author: Author @belongsTo - } - - type Author @model { - postID: ID - posts: [Post] @hasMany(fields: ["postID"]) - } - `, - true, - ), - ).not.toThrow(); + `; + const stack = new cdk.Stack(); + expect( + () => + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(schema), + authorizationModes: { + apiKeyConfig: { expires: cdk.Duration.days(7) }, + }, + translationBehavior: { + allowGen1Patterns: true, + }, + predictionsBucket: new Bucket(stack, 'myfakebucket'), + }), + ).not.toThrow(); + }); + + test('allows fields on @belongsTo', () => { + expect(() => + verifySchema( + ` + type Post @model { + authorID: ID + author: Author @belongsTo(fields: ["authorID"]) + } + + type Author @model { + posts: [Post] @hasMany + } + `, + true, + ), + ).not.toThrow(); + }); + + test('allows fields on @hasMany', () => { + expect(() => + verifySchema( + ` + type Post @model { + author: Author @belongsTo + } + + type Author @model { + postID: ID + posts: [Post] @hasMany(fields: ["postID"]) + } + `, + true, + ), + ).not.toThrow(); + }); + + test('allows fields on @hasOne', () => { + expect(() => + verifySchema( + ` + type Profile @model { + author: Author @belongsTo + } + + type Author @model { + profileID: ID + profile: Profile @hasOne(fields: ["profileID"]) + } + `, + true, + ), + ).not.toThrow(); + }); + + test('allows required @belongsTo fields', () => { + expect(() => + verifySchema( + ` + type Post @model { + author: Author! @belongsTo + } + + type Author @model { + posts: [Post] @hasMany + } + `, + true, + ), + ).not.toThrow(); + }); + + test('allows required @hasMany fields', () => { + expect(() => + verifySchema( + ` + type Post @model { + author: Author @belongsTo + } + + type Author @model { + posts: [Post]! @hasMany + } + `, + true, + ), + ).not.toThrow(); + }); + + test('allows required @hasOne fields', () => { + expect(() => + verifySchema( + ` + type Profile @model { + author: Author @belongsTo + } + + type Author @model { + profile: Profile! @hasOne + } + `, + true, + ), + ).not.toThrow(); + }); }); - test('does not allow fields on @hasOne', () => { - expect(() => - verifySchema( - ` - type Profile @model { - author: Author @belongsTo - } - - type Author @model { - profileID: ID - profile: Profile @hasOne(fields: ["profileID"]) - } - `, - false, - ), - ).toThrow('fields argument on @hasOne is deprecated. Modify Author.profile to use references instead.'); - }); - - test('allows fields on @hasOne', () => { - expect(() => - verifySchema( - ` - type Profile @model { - author: Author @belongsTo - } - - type Author @model { - profileID: ID - profile: Profile @hasOne(fields: ["profileID"]) - } - `, - true, - ), - ).not.toThrow(); - }); - - test('does not allow required @belongsTo fields', () => { - expect(() => - verifySchema( - ` - type Post @model { - author: Author! @belongsTo - } - - type Author @model { - posts: [Post] @hasMany - } - `, - false, - ), - ).toThrow('@belongsTo cannot be used on required fields. Modify Post.author to be optional.'); - }); - - test('allows required @belongsTo fields', () => { - expect(() => - verifySchema( - ` - type Post @model { - author: Author! @belongsTo - } - - type Author @model { - posts: [Post] @hasMany - } - `, - true, - ), - ).not.toThrow(); - }); - - test('does not allow required @hasMany fields', () => { - expect(() => - verifySchema( - ` - type Post @model { - author: Author @belongsTo - } - - type Author @model { - posts: [Post]! @hasMany - } - `, - false, - ), - ).toThrow('@hasMany cannot be used on required fields. Modify Author.posts to be optional.'); - }); - - test('allows required @hasMany fields', () => { - expect(() => - verifySchema( - ` - type Post @model { - author: Author @belongsTo - } - - type Author @model { - posts: [Post]! @hasMany - } - `, - true, - ), - ).not.toThrow(); - }); - - test('does not allow required @hasOne fields', () => { - expect(() => - verifySchema( - ` - type Profile @model { - author: Author @belongsTo - } - - type Author @model { - profile: Profile! @hasOne - } - `, - false, - ), - ).toThrow('@hasOne cannot be used on required fields. Modify Author.profile to be optional.'); - }); - - test('allows required @hasOne fields', () => { - expect(() => - verifySchema( - ` - type Profile @model { - author: Author @belongsTo - } - - type Author @model { - profile: Profile! @hasOne - } - `, - true, - ), - ).not.toThrow(); + describe('allowGen1Patterns: false', () => { + test('does not allow @manyToMany', () => { + expect(() => + verifySchema( + ` + type Post @model { + tags: [Tag] @manyToMany(relationName: "PostTags") + } + + type Tag @model { + posts: [Post] @manyToMany(relationName: "PostTags") + } + `, + false, + ), + ).toThrow('Unknown directive "@manyToMany".'); + }); + + test('does not allow @searchable', () => { + expect(() => + verifySchema( + ` + type Post @model @searchable { + title: String + } + `, + false, + ), + ).toThrow('Unknown directive "@searchable".'); + }); + + test('does not allow @predictions', () => { + expect(() => + verifySchema( + ` + type Query { + recognizeLabelsFromImage: [String] @predictions(actions: [identifyLabels]) + } + `, + false, + ), + ).toThrow('Unknown directive "@predictions".'); + }); + + test('does not allow fields on @belongsTo', () => { + expect(() => + verifySchema( + ` + type Post @model { + authorID: ID + author: Author @belongsTo(fields: ["authorID"]) + } + + type Author @model { + posts: [Post] @hasMany + } + `, + false, + ), + ).toThrow('fields argument on @belongsTo is deprecated. Modify Post.author to use references instead.'); + }); + + test('does not allow fields on @hasMany', () => { + expect(() => + verifySchema( + ` + type Post @model { + author: Author @belongsTo + } + + type Author @model { + postID: ID + posts: [Post] @hasMany(fields: ["postID"]) + } + `, + false, + ), + ).toThrow('fields argument on @hasMany is deprecated. Modify Author.posts to use references instead.'); + }); + + test('does not allow fields on @hasOne', () => { + expect(() => + verifySchema( + ` + type Profile @model { + author: Author @belongsTo + } + + type Author @model { + profileID: ID + profile: Profile @hasOne(fields: ["profileID"]) + } + `, + false, + ), + ).toThrow('fields argument on @hasOne is deprecated. Modify Author.profile to use references instead.'); + }); + + test('does not allow required @belongsTo fields', () => { + expect(() => + verifySchema( + ` + type Post @model { + author: Author! @belongsTo + } + + type Author @model { + posts: [Post] @hasMany + } + `, + false, + ), + ).toThrow('@belongsTo cannot be used on required fields. Modify Post.author to be optional.'); + }); + + test('does not allow required @hasMany fields', () => { + expect(() => + verifySchema( + ` + type Post @model { + author: Author @belongsTo + } + + type Author @model { + posts: [Post]! @hasMany + } + `, + false, + ), + ).toThrow('@hasMany cannot be used on required fields. Modify Author.posts to be optional.'); + }); + + test('does not allow required @hasOne fields', () => { + expect(() => + verifySchema( + ` + type Profile @model { + author: Author @belongsTo + } + + type Author @model { + profile: Profile! @hasOne + } + `, + false, + ), + ).toThrow('@hasOne cannot be used on required fields. Modify Author.profile to be optional.'); + }); }); }); From c07c917cf7ffce17a98fec97fb5f65d526524ad6 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Wed, 3 Jul 2024 15:24:24 -0600 Subject: [PATCH 13/17] fix: change deprecated -> disallowed --- .../__tests__/__functional__/disable-gen1-patterns.test.ts | 6 +++--- .../src/graphql-belongs-to-transformer.ts | 2 +- .../src/graphql-has-many-transformer.ts | 2 +- .../src/graphql-has-one-transformer.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts index cfb2188c7d..de3081ba84 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts @@ -264,7 +264,7 @@ describe('allowGen1Patterns', () => { `, false, ), - ).toThrow('fields argument on @belongsTo is deprecated. Modify Post.author to use references instead.'); + ).toThrow('fields argument on @belongsTo is disallowed. Modify Post.author to use references instead.'); }); test('does not allow fields on @hasMany', () => { @@ -282,7 +282,7 @@ describe('allowGen1Patterns', () => { `, false, ), - ).toThrow('fields argument on @hasMany is deprecated. Modify Author.posts to use references instead.'); + ).toThrow('fields argument on @hasMany is disallowed. Modify Author.posts to use references instead.'); }); test('does not allow fields on @hasOne', () => { @@ -300,7 +300,7 @@ describe('allowGen1Patterns', () => { `, false, ), - ).toThrow('fields argument on @hasOne is deprecated. Modify Author.profile to use references instead.'); + ).toThrow('fields argument on @hasOne is disallowed. Modify Author.profile to use references instead.'); }); test('does not allow required @belongsTo fields', () => { 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 e40033e355..23f212cd22 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 @@ -169,7 +169,7 @@ const validate = (config: BelongsToDirectiveConfiguration, ctx: TransformerConte } if (config.fields) { throw new InvalidDirectiveError( - `fields argument on @${BelongsToDirective.name} is deprecated. Modify ${modelName}.${fieldName} to use references instead.`, + `fields argument on @${BelongsToDirective.name} is disallowed. Modify ${modelName}.${fieldName} to use references instead.`, ); } } 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 f869bdc822..a659ac2376 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 @@ -169,7 +169,7 @@ const validate = (config: HasManyDirectiveConfiguration, ctx: TransformerContext } if (config.fields) { throw new InvalidDirectiveError( - `fields argument on @${HasManyDirective.name} is deprecated. Modify ${modelName}.${fieldName} to use references instead.`, + `fields argument on @${HasManyDirective.name} is disallowed. Modify ${modelName}.${fieldName} to use references instead.`, ); } } 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 2c14c50bac..c4b1bde544 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 @@ -197,7 +197,7 @@ const validate = (config: HasOneDirectiveConfiguration, ctx: TransformerContextP } if (config.fields) { throw new InvalidDirectiveError( - `fields argument on @${HasOneDirective.name} is deprecated. Modify ${modelName}.${fieldName} to use references instead.`, + `fields argument on @${HasOneDirective.name} is disallowed. Modify ${modelName}.${fieldName} to use references instead.`, ); } } From ce75ede57d4885a13e348ce640aebcd1030f3f1a Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Wed, 3 Jul 2024 15:34:01 -0600 Subject: [PATCH 14/17] refactor: add allowGen1Patterns to TransformerFactoryArgs --- packages/amplify-graphql-api-construct/.jsii | 22 +++++++++---------- .../src/amplify-graphql-api.ts | 10 +++++---- packages/amplify-graphql-transformer/API.md | 3 ++- .../src/graphql-transformer.ts | 16 +++++++------- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/amplify-graphql-api-construct/.jsii b/packages/amplify-graphql-api-construct/.jsii index 35c28fbff7..fb9edb7025 100644 --- a/packages/amplify-graphql-api-construct/.jsii +++ b/packages/amplify-graphql-api-construct/.jsii @@ -3998,7 +3998,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 283 + "line": 285 }, "name": "addDynamoDbDataSource", "parameters": [ @@ -4047,7 +4047,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 295 + "line": 297 }, "name": "addElasticsearchDataSource", "parameters": [ @@ -4094,7 +4094,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 305 + "line": 307 }, "name": "addEventBridgeDataSource", "parameters": [ @@ -4141,7 +4141,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 387 + "line": 389 }, "name": "addFunction", "parameters": [ @@ -4176,7 +4176,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 316 + "line": 318 }, "name": "addHttpDataSource", "parameters": [ @@ -4224,7 +4224,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 327 + "line": 329 }, "name": "addLambdaDataSource", "parameters": [ @@ -4272,7 +4272,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 338 + "line": 340 }, "name": "addNoneDataSource", "parameters": [ @@ -4311,7 +4311,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 349 + "line": 351 }, "name": "addOpenSearchDataSource", "parameters": [ @@ -4359,7 +4359,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 362 + "line": 364 }, "name": "addRdsDataSource", "parameters": [ @@ -4426,7 +4426,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 378 + "line": 380 }, "name": "addResolver", "parameters": [ @@ -8487,5 +8487,5 @@ } }, "version": "1.11.1", - "fingerprint": "2wmG0N5kBjtA+aK3rlV8/q7K/7DLfdP+golmlXXr04M=" + "fingerprint": "bie/pSK2lDysAxU25A0YKgH5obMspQsx/3xuPdQi6vg=" } \ No newline at end of file diff --git a/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts b/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts index a5cc95aa48..3cd40434f6 100644 --- a/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts +++ b/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts @@ -184,6 +184,10 @@ export class AmplifyGraphqlApi extends Construct { const assetProvider = new AssetProvider(this); + const transformParameters = { + ...defaultTranslationBehavior, + ...(translationBehavior ?? {}), + }; const executeTransformConfig: ExecuteTransformConfig = { scope: this, nestedStackProvider: { @@ -204,14 +208,12 @@ export class AmplifyGraphqlApi extends Construct { ...definition.referencedLambdaFunctions, ...functionNameMap, }, + allowGen1Patterns: transformParameters.allowGen1Patterns, }, authConfig, stackMapping: stackMappings ?? {}, resolverConfig: this.dataStoreConfiguration ? convertToResolverConfig(this.dataStoreConfiguration) : undefined, - transformParameters: { - ...defaultTranslationBehavior, - ...(translationBehavior ?? {}), - }, + transformParameters, // CDK construct uses a custom resource. We'll define this explicitly here to remind ourselves that this value is unused in the CDK // construct flow rdsLayerMapping: undefined, diff --git a/packages/amplify-graphql-transformer/API.md b/packages/amplify-graphql-transformer/API.md index 4c6b40819c..d8e628ddcf 100644 --- a/packages/amplify-graphql-transformer/API.md +++ b/packages/amplify-graphql-transformer/API.md @@ -25,7 +25,7 @@ import { UserDefinedSlot } from '@aws-amplify/graphql-transformer-core'; export const constructTransform: (config: TransformConfig) => GraphQLTransform; // @public (undocumented) -export const constructTransformerChain: (options?: TransformerFactoryArgs, allowGen1Patterns?: boolean) => TransformerPluginProvider[]; +export const constructTransformerChain: (options?: TransformerFactoryArgs) => TransformerPluginProvider[]; // @public (undocumented) export const executeTransform: (config: ExecuteTransformConfig) => void; @@ -56,6 +56,7 @@ export type TransformerFactoryArgs = { storageConfig?: any; customTransformers?: TransformerPluginProvider[]; functionNameMap?: Record; + allowGen1Patterns?: boolean; }; // (No @packageDocumentation comment for this package) diff --git a/packages/amplify-graphql-transformer/src/graphql-transformer.ts b/packages/amplify-graphql-transformer/src/graphql-transformer.ts index a30372bb48..f4d7f4e6b7 100644 --- a/packages/amplify-graphql-transformer/src/graphql-transformer.ts +++ b/packages/amplify-graphql-transformer/src/graphql-transformer.ts @@ -42,6 +42,7 @@ export type TransformerFactoryArgs = { storageConfig?: any; customTransformers?: TransformerPluginProvider[]; functionNameMap?: Record; + allowGen1Patterns?: boolean; }; /** @@ -56,10 +57,7 @@ export type TransformConfig = { transformParameters: TransformParameters; }; -export const constructTransformerChain = ( - options?: TransformerFactoryArgs, - allowGen1Patterns: boolean = true, -): TransformerPluginProvider[] => { +export const constructTransformerChain = (options?: TransformerFactoryArgs): TransformerPluginProvider[] => { const modelTransformer = new ModelTransformer(); const authTransformer = new AuthTransformer(); const indexTransformer = new IndexTransformer(); @@ -70,19 +68,21 @@ export const constructTransformerChain = ( modelTransformer, new FunctionTransformer(options?.functionNameMap), new HttpTransformer(), - ...(allowGen1Patterns ? [new PredictionsTransformer(options?.storageConfig)] : []), + ...(options?.allowGen1Patterns ? [new PredictionsTransformer(options?.storageConfig)] : []), new PrimaryKeyTransformer(), indexTransformer, new HasManyTransformer(), hasOneTransformer, - ...(allowGen1Patterns ? [new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer)] : []), + ...(options?.allowGen1Patterns + ? [new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer)] + : []), new BelongsToTransformer(), new DefaultValueTransformer(), authTransformer, new MapsToTransformer(), new SqlTransformer(), new RefersToTransformer(), - ...(allowGen1Patterns ? [new SearchableModelTransformer()] : []), + ...(options?.allowGen1Patterns ? [new SearchableModelTransformer()] : []), ...(options?.customTransformers ?? []), ]; }; @@ -95,7 +95,7 @@ export const constructTransformerChain = ( export const constructTransform = (config: TransformConfig): GraphQLTransform => { const { transformersFactoryArgs, authConfig, resolverConfig, userDefinedSlots, stackMapping, transformParameters } = config; - const transformers = constructTransformerChain(transformersFactoryArgs, transformParameters.allowGen1Patterns); + const transformers = constructTransformerChain(transformersFactoryArgs); return new GraphQLTransform({ transformers, From 43a7bb1023d718d2b2a4f77cbfe2e05a10fa0d2b Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Fri, 5 Jul 2024 09:02:49 -0600 Subject: [PATCH 15/17] fix: mark _allowGen1Patterns as internal --- packages/amplify-graphql-api-construct/.jsii | 205 ++++++++---------- packages/amplify-graphql-api-construct/API.md | 2 + .../disable-gen1-patterns.test.ts | 10 +- .../src/amplify-graphql-api.ts | 7 +- .../src/internal/default-parameters.ts | 2 +- .../src/types.ts | 24 +- 6 files changed, 121 insertions(+), 129 deletions(-) diff --git a/packages/amplify-graphql-api-construct/.jsii b/packages/amplify-graphql-api-construct/.jsii index fb9edb7025..b0b9617fbb 100644 --- a/packages/amplify-graphql-api-construct/.jsii +++ b/packages/amplify-graphql-api-construct/.jsii @@ -3537,7 +3537,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 872 + "line": 892 }, "name": "AddFunctionProps", "properties": [ @@ -3550,7 +3550,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 876 + "line": 896 }, "name": "dataSource", "type": { @@ -3566,7 +3566,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 881 + "line": 901 }, "name": "name", "type": { @@ -3583,7 +3583,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 916 + "line": 936 }, "name": "code", "optional": true, @@ -3601,7 +3601,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 888 + "line": 908 }, "name": "description", "optional": true, @@ -3619,7 +3619,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 895 + "line": 915 }, "name": "requestMappingTemplate", "optional": true, @@ -3637,7 +3637,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 902 + "line": 922 }, "name": "responseMappingTemplate", "optional": true, @@ -3655,7 +3655,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 909 + "line": 929 }, "name": "runtime", "optional": true, @@ -3951,7 +3951,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 138 + "line": 139 }, "parameters": [ { @@ -3986,7 +3986,7 @@ "kind": "class", "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 84 + "line": 85 }, "methods": [ { @@ -3998,7 +3998,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 285 + "line": 290 }, "name": "addDynamoDbDataSource", "parameters": [ @@ -4047,7 +4047,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 297 + "line": 302 }, "name": "addElasticsearchDataSource", "parameters": [ @@ -4094,7 +4094,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 307 + "line": 312 }, "name": "addEventBridgeDataSource", "parameters": [ @@ -4141,7 +4141,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 389 + "line": 394 }, "name": "addFunction", "parameters": [ @@ -4176,7 +4176,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 318 + "line": 323 }, "name": "addHttpDataSource", "parameters": [ @@ -4224,7 +4224,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 329 + "line": 334 }, "name": "addLambdaDataSource", "parameters": [ @@ -4272,7 +4272,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 340 + "line": 345 }, "name": "addNoneDataSource", "parameters": [ @@ -4311,7 +4311,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 351 + "line": 356 }, "name": "addOpenSearchDataSource", "parameters": [ @@ -4359,7 +4359,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 364 + "line": 369 }, "name": "addRdsDataSource", "parameters": [ @@ -4426,7 +4426,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 380 + "line": 385 }, "name": "addResolver", "parameters": [ @@ -4467,7 +4467,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 119 + "line": 120 }, "name": "apiId", "type": { @@ -4482,7 +4482,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 99 + "line": 100 }, "name": "generatedFunctionSlots", "type": { @@ -4515,7 +4515,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 104 + "line": 105 }, "name": "graphqlUrl", "type": { @@ -4531,7 +4531,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 109 + "line": 110 }, "name": "realtimeUrl", "type": { @@ -4546,7 +4546,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 88 + "line": 89 }, "name": "resources", "type": { @@ -4562,7 +4562,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 114 + "line": 115 }, "name": "apiKey", "optional": true, @@ -4585,7 +4585,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 775 + "line": 795 }, "name": "AmplifyGraphqlApiCfnResources", "properties": [ @@ -4598,7 +4598,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 829 + "line": 849 }, "name": "additionalCfnResources", "type": { @@ -4619,7 +4619,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 814 + "line": 834 }, "name": "amplifyDynamoDbTables", "type": { @@ -4640,7 +4640,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 804 + "line": 824 }, "name": "cfnDataSources", "type": { @@ -4661,7 +4661,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 799 + "line": 819 }, "name": "cfnFunctionConfigurations", "type": { @@ -4682,7 +4682,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 824 + "line": 844 }, "name": "cfnFunctions", "type": { @@ -4703,7 +4703,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 779 + "line": 799 }, "name": "cfnGraphqlApi", "type": { @@ -4719,7 +4719,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 784 + "line": 804 }, "name": "cfnGraphqlSchema", "type": { @@ -4735,7 +4735,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 794 + "line": 814 }, "name": "cfnResolvers", "type": { @@ -4756,7 +4756,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 819 + "line": 839 }, "name": "cfnRoles", "type": { @@ -4777,7 +4777,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 809 + "line": 829 }, "name": "cfnTables", "type": { @@ -4798,7 +4798,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 789 + "line": 809 }, "name": "cfnApiKey", "optional": true, @@ -4821,7 +4821,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 692 + "line": 712 }, "name": "AmplifyGraphqlApiProps", "properties": [ @@ -4835,7 +4835,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 709 + "line": 729 }, "name": "authorizationModes", "type": { @@ -4852,7 +4852,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 697 + "line": 717 }, "name": "definition", "type": { @@ -4869,7 +4869,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 703 + "line": 723 }, "name": "apiName", "optional": true, @@ -4888,7 +4888,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 724 + "line": 744 }, "name": "conflictResolution", "optional": true, @@ -4906,7 +4906,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 768 + "line": 788 }, "name": "dataStoreConfiguration", "optional": true, @@ -4926,7 +4926,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 717 + "line": 737 }, "name": "functionNameMap", "optional": true, @@ -4949,7 +4949,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 739 + "line": 759 }, "name": "functionSlots", "optional": true, @@ -4984,7 +4984,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 762 + "line": 782 }, "name": "outputStorageStrategy", "optional": true, @@ -5001,7 +5001,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 751 + "line": 771 }, "name": "predictionsBucket", "optional": true, @@ -5019,7 +5019,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 733 + "line": 753 }, "name": "stackMappings", "optional": true, @@ -5045,7 +5045,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 746 + "line": 766 }, "name": "transformerPlugins", "optional": true, @@ -5067,7 +5067,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 757 + "line": 777 }, "name": "translationBehavior", "optional": true, @@ -5090,7 +5090,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 836 + "line": 856 }, "name": "AmplifyGraphqlApiResources", "properties": [ @@ -5103,7 +5103,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 860 + "line": 880 }, "name": "cfnResources", "type": { @@ -5119,7 +5119,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 855 + "line": 875 }, "name": "functions", "type": { @@ -5140,7 +5140,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 840 + "line": 860 }, "name": "graphqlApi", "type": { @@ -5156,7 +5156,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 865 + "line": 885 }, "name": "nestedStacks", "type": { @@ -5177,7 +5177,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 850 + "line": 870 }, "name": "roles", "type": { @@ -5198,7 +5198,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 845 + "line": 865 }, "name": "tables", "type": { @@ -6237,7 +6237,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 626 + "line": 646 }, "name": "IAmplifyGraphqlDefinition", "properties": [ @@ -6252,7 +6252,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 651 + "line": 671 }, "name": "dataSourceStrategies", "type": { @@ -6286,7 +6286,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 637 + "line": 657 }, "name": "functionSlots", "type": { @@ -6320,7 +6320,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 631 + "line": 651 }, "name": "schema", "type": { @@ -6337,7 +6337,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 657 + "line": 677 }, "name": "customSqlDataSourceStrategies", "optional": true, @@ -6361,7 +6361,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 645 + "line": 665 }, "name": "referencedLambdaFunctions", "optional": true, @@ -6387,7 +6387,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 663 + "line": 683 }, "name": "IBackendOutputEntry", "properties": [ @@ -6400,7 +6400,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 672 + "line": 692 }, "name": "payload", "type": { @@ -6421,7 +6421,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 667 + "line": 687 }, "name": "version", "type": { @@ -6441,7 +6441,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 678 + "line": 698 }, "methods": [ { @@ -6452,7 +6452,7 @@ }, "locationInModule": { "filename": "src/types.ts", - "line": 685 + "line": 705 }, "name": "addBackendOutputEntry", "parameters": [ @@ -6802,7 +6802,7 @@ "kind": "interface", "locationInModule": { "filename": "src/types.ts", - "line": 511 + "line": 521 }, "name": "PartialTranslationBehavior", "properties": [ @@ -6817,7 +6817,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 603 + "line": 613 }, "name": "allowDestructiveGraphqlSchemaUpdates", "optional": true, @@ -6825,24 +6825,6 @@ "primitive": "boolean" } }, - { - "abstract": true, - "docs": { - "default": "true", - "stability": "stable", - "summary": "When disabled usage of Gen 1 patterns will result in an error thrown." - }, - "immutable": true, - "locationInModule": { - "filename": "src/types.ts", - "line": 620 - }, - "name": "allowGen1Patterns", - "optional": true, - "type": { - "primitive": "boolean" - } - }, { "abstract": true, "docs": { @@ -6853,7 +6835,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 523 + "line": 533 }, "name": "disableResolverDeduping", "optional": true, @@ -6875,7 +6857,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 568 + "line": 578 }, "name": "enableAutoIndexQueryNames", "optional": true, @@ -6894,7 +6876,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 583 + "line": 593 }, "name": "enableSearchNodeToNodeEncryption", "optional": true, @@ -6912,7 +6894,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 589 + "line": 599 }, "name": "enableTransformerCfnOutputs", "optional": true, @@ -6930,7 +6912,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 548 + "line": 558 }, "name": "populateOwnerFieldForStaticGroupAuth", "optional": true, @@ -6949,7 +6931,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 613 + "line": 623 }, "name": "replaceTableUponGsiUpdate", "optional": true, @@ -6967,7 +6949,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 574 + "line": 584 }, "name": "respectPrimaryKeyAttributesOnConnectionField", "optional": true, @@ -6985,7 +6967,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 529 + "line": 539 }, "name": "sandboxModeEnabled", "optional": true, @@ -7006,7 +6988,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 561 + "line": 571 }, "name": "secondaryKeyAsGSI", "optional": true, @@ -7027,7 +7009,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 516 + "line": 526 }, "name": "shouldDeepMergeDirectiveConfigDefaults", "optional": true, @@ -7045,7 +7027,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 542 + "line": 552 }, "name": "subscriptionsInheritPrimaryAuth", "optional": true, @@ -7064,7 +7046,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 555 + "line": 565 }, "name": "suppressApiKeyGeneration", "optional": true, @@ -7082,7 +7064,7 @@ "immutable": true, "locationInModule": { "filename": "src/types.ts", - "line": 536 + "line": 546 }, "name": "useSubUsernameForDefaultIdentityClaim", "optional": true, @@ -8123,23 +8105,6 @@ "primitive": "boolean" } }, - { - "abstract": true, - "docs": { - "default": "true", - "stability": "stable", - "summary": "When disabled usage of Gen 1 patterns will result in an error thrown." - }, - "immutable": true, - "locationInModule": { - "filename": "src/types.ts", - "line": 505 - }, - "name": "allowGen1Patterns", - "type": { - "primitive": "boolean" - } - }, { "abstract": true, "docs": { @@ -8487,5 +8452,5 @@ } }, "version": "1.11.1", - "fingerprint": "bie/pSK2lDysAxU25A0YKgH5obMspQsx/3xuPdQi6vg=" + "fingerprint": "BBgFaqcNy9k8O9fDxfMRQ9JlZsv0/CKnMUkmDxFn7W4=" } \ No newline at end of file diff --git a/packages/amplify-graphql-api-construct/API.md b/packages/amplify-graphql-api-construct/API.md index 7e555df386..d4b4117496 100644 --- a/packages/amplify-graphql-api-construct/API.md +++ b/packages/amplify-graphql-api-construct/API.md @@ -314,6 +314,7 @@ export interface OptimisticConflictResolutionStrategy extends ConflictResolution // @public export interface PartialTranslationBehavior { readonly allowDestructiveGraphqlSchemaUpdates?: boolean; + // @internal readonly allowGen1Patterns?: boolean; readonly disableResolverDeduping?: boolean; readonly enableAutoIndexQueryNames?: boolean; @@ -439,6 +440,7 @@ export interface TimeToLiveSpecification { // @public export interface TranslationBehavior { readonly allowDestructiveGraphqlSchemaUpdates: boolean; + // @internal readonly allowGen1Patterns: boolean; readonly disableResolverDeduping: boolean; readonly enableAutoIndexQueryNames: boolean; diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts index de3081ba84..56e167be5e 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/disable-gen1-patterns.test.ts @@ -17,13 +17,13 @@ const verifySchema = (schema: string, allowGen1Patterns: boolean): void => { apiKeyConfig: { expires: cdk.Duration.days(7) }, }, translationBehavior: { - allowGen1Patterns, + _allowGen1Patterns: allowGen1Patterns, }, }); Template.fromStack(stack); }; -describe('allowGen1Patterns', () => { +describe('_allowGen1Patterns', () => { test('defaults to allow', () => { const schema = ` type Post @model { @@ -46,7 +46,7 @@ describe('allowGen1Patterns', () => { ).not.toThrow(); }); - describe('allowGen1Patterns: true', () => { + describe('_allowGen1Patterns: true', () => { test('allows @manyToMany', () => { expect(() => verifySchema( @@ -92,7 +92,7 @@ describe('allowGen1Patterns', () => { apiKeyConfig: { expires: cdk.Duration.days(7) }, }, translationBehavior: { - allowGen1Patterns: true, + _allowGen1Patterns: true, }, predictionsBucket: new Bucket(stack, 'myfakebucket'), }), @@ -205,7 +205,7 @@ describe('allowGen1Patterns', () => { }); }); - describe('allowGen1Patterns: false', () => { + describe('_allowGen1Patterns: false', () => { test('does not allow @manyToMany', () => { expect(() => verifySchema( diff --git a/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts b/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts index 3cd40434f6..343b878fda 100644 --- a/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts +++ b/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts @@ -3,6 +3,7 @@ import { Construct } from 'constructs'; import { ExecuteTransformConfig, executeTransform } from '@aws-amplify/graphql-transformer'; import { NestedStack, Stack } from 'aws-cdk-lib'; import { AttributionMetadataStorage, StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage'; +import { TransformParameters } from '@aws-amplify/graphql-transformer-interfaces'; import { graphqlOutputKey } from '@aws-amplify/backend-output-schemas'; import type { GraphqlOutput, AwsAppsyncAuthenticationType } from '@aws-amplify/backend-output-schemas'; import { @@ -184,10 +185,14 @@ export class AmplifyGraphqlApi extends Construct { const assetProvider = new AssetProvider(this); - const transformParameters = { + const mergedTranslationBehavior = { ...defaultTranslationBehavior, ...(translationBehavior ?? {}), }; + const transformParameters: TransformParameters = { + ...mergedTranslationBehavior, + allowGen1Patterns: mergedTranslationBehavior._allowGen1Patterns, + }; const executeTransformConfig: ExecuteTransformConfig = { scope: this, nestedStackProvider: { diff --git a/packages/amplify-graphql-api-construct/src/internal/default-parameters.ts b/packages/amplify-graphql-api-construct/src/internal/default-parameters.ts index c028524372..fe2c7a3084 100644 --- a/packages/amplify-graphql-api-construct/src/internal/default-parameters.ts +++ b/packages/amplify-graphql-api-construct/src/internal/default-parameters.ts @@ -20,5 +20,5 @@ export const defaultTranslationBehavior: TranslationBehavior = { enableTransformerCfnOutputs: false, allowDestructiveGraphqlSchemaUpdates: false, replaceTableUponGsiUpdate: false, - allowGen1Patterns: true, + _allowGen1Patterns: true, }; diff --git a/packages/amplify-graphql-api-construct/src/types.ts b/packages/amplify-graphql-api-construct/src/types.ts index 60a8492395..a992f8ebae 100644 --- a/packages/amplify-graphql-api-construct/src/types.ts +++ b/packages/amplify-graphql-api-construct/src/types.ts @@ -500,9 +500,19 @@ export interface TranslationBehavior { /** * When disabled usage of Gen 1 patterns will result in an error thrown. * + * Gen 1 Patterns that will be disabled when set to false: + * - Use of @manyToMany + * - Use of @searchable + * - Use of @predictions + * - Use of fields argument on @hasOne, @hasMany, and @belongsTo. + * - Use of @hasOne, @hasMany, and @belongsTo on required fields. + * * @default true + * @internal + * Warning: Although this has `public` access, it is intended for internal use and should not be used directly. + * The behavior of this may change without warning. */ - readonly allowGen1Patterns: boolean; + readonly _allowGen1Patterns: boolean; } /** @@ -615,9 +625,19 @@ export interface PartialTranslationBehavior { /** * When disabled usage of Gen 1 patterns will result in an error thrown. * + * Gen 1 Patterns that will be disabled when set to false: + * - Use of @manyToMany + * - Use of @searchable + * - Use of @predictions + * - Use of fields argument on @hasOne, @hasMany, and @belongsTo. + * - Use of @hasOne, @hasMany, and @belongsTo on required fields. + * * @default true + * @internal + * Warning: Although this has `public` access, it is intended for internal use and should not be used directly. + * The behavior of this may change without warning. */ - readonly allowGen1Patterns?: boolean; + readonly _allowGen1Patterns?: boolean; } /** From 078d586c1cca1694bf7756b0bea98824dc5ded54 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Fri, 5 Jul 2024 09:05:06 -0600 Subject: [PATCH 16/17] chore: extract api --- packages/amplify-graphql-api-construct/API.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/amplify-graphql-api-construct/API.md b/packages/amplify-graphql-api-construct/API.md index d4b4117496..c9bf8c1709 100644 --- a/packages/amplify-graphql-api-construct/API.md +++ b/packages/amplify-graphql-api-construct/API.md @@ -315,7 +315,7 @@ export interface OptimisticConflictResolutionStrategy extends ConflictResolution export interface PartialTranslationBehavior { readonly allowDestructiveGraphqlSchemaUpdates?: boolean; // @internal - readonly allowGen1Patterns?: boolean; + readonly _allowGen1Patterns?: boolean; readonly disableResolverDeduping?: boolean; readonly enableAutoIndexQueryNames?: boolean; readonly enableSearchNodeToNodeEncryption?: boolean; @@ -441,7 +441,7 @@ export interface TimeToLiveSpecification { export interface TranslationBehavior { readonly allowDestructiveGraphqlSchemaUpdates: boolean; // @internal - readonly allowGen1Patterns: boolean; + readonly _allowGen1Patterns: boolean; readonly disableResolverDeduping: boolean; readonly enableAutoIndexQueryNames: boolean; // (undocumented) From 71be8489994fa521b21a407fa6cbc0e53e26a9b9 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Fri, 5 Jul 2024 09:54:12 -0600 Subject: [PATCH 17/17] fix: constructTransformerChain default behavior for allowGen1Patterns --- .../src/__tests__/graphql-transformer.test.ts | 8 ++++++++ .../src/graphql-transformer.ts | 10 +++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts b/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts index 8c3a056921..9cd5e3c2b6 100644 --- a/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts +++ b/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts @@ -37,6 +37,14 @@ describe('constructTransformerChain', () => { it('succeeds on admin roles', () => { expect(constructTransformerChain().length).toEqual(numOfTransformers); }); + + it('allows gen 1 patterns by default', () => { + expect(constructTransformerChain().length).toEqual(numOfTransformers); + }); + + it('removes transformers not supported in gen 2', () => { + expect(constructTransformerChain({ allowGen1Patterns: false }).length).toEqual(numOfTransformers - 3); + }); }); const defaultTransformConfig: TransformConfig = { diff --git a/packages/amplify-graphql-transformer/src/graphql-transformer.ts b/packages/amplify-graphql-transformer/src/graphql-transformer.ts index f4d7f4e6b7..930a01127c 100644 --- a/packages/amplify-graphql-transformer/src/graphql-transformer.ts +++ b/packages/amplify-graphql-transformer/src/graphql-transformer.ts @@ -63,26 +63,26 @@ export const constructTransformerChain = (options?: TransformerFactoryArgs): Tra const indexTransformer = new IndexTransformer(); const hasOneTransformer = new HasOneTransformer(); + const allowGen1Patterns = options?.allowGen1Patterns === undefined ? true : options?.allowGen1Patterns; + // The default list of transformers should match DefaultDirectives in packages/amplify-graphql-directives/src/index.ts return [ modelTransformer, new FunctionTransformer(options?.functionNameMap), new HttpTransformer(), - ...(options?.allowGen1Patterns ? [new PredictionsTransformer(options?.storageConfig)] : []), + ...(allowGen1Patterns ? [new PredictionsTransformer(options?.storageConfig)] : []), new PrimaryKeyTransformer(), indexTransformer, new HasManyTransformer(), hasOneTransformer, - ...(options?.allowGen1Patterns - ? [new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer)] - : []), + ...(allowGen1Patterns ? [new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer)] : []), new BelongsToTransformer(), new DefaultValueTransformer(), authTransformer, new MapsToTransformer(), new SqlTransformer(), new RefersToTransformer(), - ...(options?.allowGen1Patterns ? [new SearchableModelTransformer()] : []), + ...(allowGen1Patterns ? [new SearchableModelTransformer()] : []), ...(options?.customTransformers ?? []), ]; };