From cc341035f25e2d247c3bb7b274944b5fc5a5d88f Mon Sep 17 00:00:00 2001 From: amplify-data-ci Date: Tue, 1 Oct 2024 21:46:53 +0000 Subject: [PATCH 01/15] chore: update .jsii assembly --- packages/amplify-graphql-api-construct/.jsii | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/amplify-graphql-api-construct/.jsii b/packages/amplify-graphql-api-construct/.jsii index fced3d35d0..736fae8895 100644 --- a/packages/amplify-graphql-api-construct/.jsii +++ b/packages/amplify-graphql-api-construct/.jsii @@ -9,23 +9,23 @@ "@aws-amplify/ai-constructs": "^0.1.4", "@aws-amplify/backend-output-schemas": "^1.0.0", "@aws-amplify/backend-output-storage": "^1.0.0", - "@aws-amplify/graphql-auth-transformer": "4.1.1", - "@aws-amplify/graphql-conversation-transformer": "0.2.1", - "@aws-amplify/graphql-default-value-transformer": "3.0.3", + "@aws-amplify/graphql-auth-transformer": "4.1.2", + "@aws-amplify/graphql-conversation-transformer": "0.2.2", + "@aws-amplify/graphql-default-value-transformer": "3.0.4", "@aws-amplify/graphql-directives": "2.2.0", - "@aws-amplify/graphql-function-transformer": "3.1.0", - "@aws-amplify/graphql-generation-transformer": "0.2.1", - "@aws-amplify/graphql-http-transformer": "3.0.3", - "@aws-amplify/graphql-index-transformer": "3.0.3", - "@aws-amplify/graphql-maps-to-transformer": "4.0.3", - "@aws-amplify/graphql-model-transformer": "3.0.3", - "@aws-amplify/graphql-predictions-transformer": "3.0.3", - "@aws-amplify/graphql-relational-transformer": "3.0.3", - "@aws-amplify/graphql-searchable-transformer": "3.0.3", - "@aws-amplify/graphql-sql-transformer": "0.4.3", - "@aws-amplify/graphql-transformer": "2.1.1", - "@aws-amplify/graphql-transformer-core": "3.1.1", - "@aws-amplify/graphql-transformer-interfaces": "4.1.0", + "@aws-amplify/graphql-function-transformer": "3.1.1", + "@aws-amplify/graphql-generation-transformer": "0.2.2", + "@aws-amplify/graphql-http-transformer": "3.0.4", + "@aws-amplify/graphql-index-transformer": "3.0.4", + "@aws-amplify/graphql-maps-to-transformer": "4.0.4", + "@aws-amplify/graphql-model-transformer": "3.0.4", + "@aws-amplify/graphql-predictions-transformer": "3.0.4", + "@aws-amplify/graphql-relational-transformer": "3.0.4", + "@aws-amplify/graphql-searchable-transformer": "3.0.4", + "@aws-amplify/graphql-sql-transformer": "0.4.4", + "@aws-amplify/graphql-transformer": "2.1.2", + "@aws-amplify/graphql-transformer-core": "3.1.2", + "@aws-amplify/graphql-transformer-interfaces": "4.1.1", "@aws-amplify/platform-core": "^1.0.0", "@aws-amplify/plugin-types": "^1.0.0", "@aws-crypto/crc32": "5.2.0", @@ -8901,6 +8901,6 @@ "symbolId": "src/model-datasource-strategy-types:VpcConfig" } }, - "version": "1.13.0", - "fingerprint": "vpuBiEwYGxPp4Ed7JJUbpcLRtderB+ArtF4LhEUgnQg=" + "version": "1.14.0", + "fingerprint": "izzdFUzhDEGlYfYipZndJkvmkwoDkGn6UZivsangA2g=" } \ No newline at end of file From ad5bb4ed958ae04fd74cf5b0d2cf7976ba4809c6 Mon Sep 17 00:00:00 2001 From: amplify-data-ci Date: Tue, 1 Oct 2024 21:47:09 +0000 Subject: [PATCH 02/15] chore: update .jsii assembly --- packages/amplify-data-construct/.jsii | 38 +++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/amplify-data-construct/.jsii b/packages/amplify-data-construct/.jsii index a8f5136727..d3008a4540 100644 --- a/packages/amplify-data-construct/.jsii +++ b/packages/amplify-data-construct/.jsii @@ -9,23 +9,23 @@ "@aws-amplify/ai-constructs": "^0.1.4", "@aws-amplify/backend-output-schemas": "^1.0.0", "@aws-amplify/backend-output-storage": "^1.0.0", - "@aws-amplify/graphql-auth-transformer": "4.1.1", - "@aws-amplify/graphql-conversation-transformer": "0.2.1", - "@aws-amplify/graphql-default-value-transformer": "3.0.3", + "@aws-amplify/graphql-auth-transformer": "4.1.2", + "@aws-amplify/graphql-conversation-transformer": "0.2.2", + "@aws-amplify/graphql-default-value-transformer": "3.0.4", "@aws-amplify/graphql-directives": "2.2.0", - "@aws-amplify/graphql-function-transformer": "3.1.0", - "@aws-amplify/graphql-generation-transformer": "0.2.1", - "@aws-amplify/graphql-http-transformer": "3.0.3", - "@aws-amplify/graphql-index-transformer": "3.0.3", - "@aws-amplify/graphql-maps-to-transformer": "4.0.3", - "@aws-amplify/graphql-model-transformer": "3.0.3", - "@aws-amplify/graphql-predictions-transformer": "3.0.3", - "@aws-amplify/graphql-relational-transformer": "3.0.3", - "@aws-amplify/graphql-searchable-transformer": "3.0.3", - "@aws-amplify/graphql-sql-transformer": "0.4.3", - "@aws-amplify/graphql-transformer": "2.1.1", - "@aws-amplify/graphql-transformer-core": "3.1.1", - "@aws-amplify/graphql-transformer-interfaces": "4.1.0", + "@aws-amplify/graphql-function-transformer": "3.1.1", + "@aws-amplify/graphql-generation-transformer": "0.2.2", + "@aws-amplify/graphql-http-transformer": "3.0.4", + "@aws-amplify/graphql-index-transformer": "3.0.4", + "@aws-amplify/graphql-maps-to-transformer": "4.0.4", + "@aws-amplify/graphql-model-transformer": "3.0.4", + "@aws-amplify/graphql-predictions-transformer": "3.0.4", + "@aws-amplify/graphql-relational-transformer": "3.0.4", + "@aws-amplify/graphql-searchable-transformer": "3.0.4", + "@aws-amplify/graphql-sql-transformer": "0.4.4", + "@aws-amplify/graphql-transformer": "2.1.2", + "@aws-amplify/graphql-transformer-core": "3.1.2", + "@aws-amplify/graphql-transformer-interfaces": "4.1.1", "@aws-amplify/platform-core": "^1.0.0", "@aws-amplify/plugin-types": "^1.0.0", "@aws-crypto/crc32": "5.2.0", @@ -130,7 +130,7 @@ "zod": "^3.22.2" }, "dependencies": { - "@aws-amplify/graphql-api-construct": "1.13.0", + "@aws-amplify/graphql-api-construct": "1.14.0", "aws-cdk-lib": "^2.152.0", "constructs": "^10.3.0" }, @@ -3969,6 +3969,6 @@ } }, "types": {}, - "version": "1.10.1", - "fingerprint": "18+pcRZF+HEurnEkAAQsQVHj1oGYG+i0hcJuPIeDP7U=" + "version": "1.10.2", + "fingerprint": "Ur2Iw+iIJolzSkulUyEDgbJ9aLqUExVDrAiHRMhbnL4=" } \ No newline at end of file From b6dacd7f201ff8902f51817b694036d41433a9c4 Mon Sep 17 00:00:00 2001 From: Kevin Shan Date: Wed, 2 Oct 2024 09:04:01 -0700 Subject: [PATCH 03/15] chore: migrate pg array objects e2e test in gen2 cdk (#2906) * chore: graphql prep for test migration * refactor: generic graphql field selection string with fieldmap * feat: add postgres array objects e2e test * test: remove bootstrap in test code * chore: schema cleanup * chore: final cleanup * chore: add explanation on FieldMap ans examples * chore: remove dup test --------- Signed-off-by: Kevin Shan Co-authored-by: Tim Schmelter --- codebuild_specs/e2e_workflow.yml | 134 ++++----- .../__tests__/sql-pg-array-objects.test.ts | 55 ++++ .../schemas/sql-array-objects/field-map.ts | 14 + .../schemas/sql-array-objects/schema.graphql | 14 + .../schemas/sql-models/field-map.ts | 13 + .../schemas/sql-models/schema.graphql | 10 + .../src/sql-tests-common/sql-array-objects.ts | 255 ++++++++++++++++++ .../src/sql-tests-common/sql-models.ts | 54 ++-- .../src/utils/sql-crudl-helper.ts | 87 ++++-- 9 files changed, 527 insertions(+), 109 deletions(-) create mode 100644 packages/amplify-graphql-api-construct-tests/src/__tests__/sql-pg-array-objects.test.ts create mode 100644 packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-array-objects/field-map.ts create mode 100644 packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-array-objects/schema.graphql create mode 100644 packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/field-map.ts create mode 100644 packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql create mode 100644 packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-array-objects.ts diff --git a/codebuild_specs/e2e_workflow.yml b/codebuild_specs/e2e_workflow.yml index 7b1c947886..1f15a19006 100644 --- a/codebuild_specs/e2e_workflow.yml +++ b/codebuild_specs/e2e_workflow.yml @@ -763,7 +763,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=14848' TEST_SUITE: >- src/__tests__/utils.test.ts|src/__tests__/ddb-iam-access.test.ts|src/__tests__/data-construct.test.ts|src/__tests__/custom-logic.test.ts|src/__tests__/amplify-table-5.test.ts - CLI_REGION: eu-north-1 + CLI_REGION: eu-south-1 depend-on: - publish_to_local_registry - identifier: >- @@ -775,7 +775,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=14848' TEST_SUITE: >- src/__tests__/add-resources.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/single-gsi-single-record.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/single-gsi-empty-table.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/single-gsi-1k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/single-gsi-10k-records.test.ts - CLI_REGION: ap-northeast-1 + CLI_REGION: ap-northeast-2 depend-on: - publish_to_local_registry - identifier: >- @@ -787,7 +787,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=14848' TEST_SUITE: >- src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-update-attr-single-record.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-update-attr-empty-table.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-update-attr-1k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-update-attr-10k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-single-record.test.ts - CLI_REGION: ap-northeast-2 + CLI_REGION: ap-south-1 depend-on: - publish_to_local_registry - identifier: >- @@ -799,7 +799,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=14848' TEST_SUITE: >- src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-empty-table.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-1k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-10k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/3-gsis-single-record.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/3-gsis-empty-table.test.ts - CLI_REGION: ap-south-1 + CLI_REGION: ap-southeast-1 depend-on: - publish_to_local_registry - identifier: 3_gsis_1k_records_3_gsis_10k_records @@ -810,7 +810,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=14848' TEST_SUITE: >- src/__tests__/deploy-velocity-temporarily-disabled/3-gsis-1k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/3-gsis-10k-records.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 depend-on: - publish_to_local_registry - identifier: sql_pg_models @@ -834,6 +834,16 @@ batch: USE_PARENT_ACCOUNT: 1 depend-on: - publish_to_local_registry + - identifier: sql_pg_array_objects + buildspec: codebuild_specs/run_cdk_tests.yml + env: + compute-type: BUILD_GENERAL1_MEDIUM + variables: + NODE_OPTIONS: '--max-old-space-size=6656' + TEST_SUITE: src/__tests__/sql-pg-array-objects.test.ts + CLI_REGION: ap-southeast-2 + depend-on: + - publish_to_local_registry - identifier: sql_mysql_canary buildspec: codebuild_specs/run_cdk_tests.yml env: @@ -841,7 +851,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/sql-mysql-canary.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: sql_models_2 @@ -851,7 +861,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/sql-models-2.test.ts - CLI_REGION: ca-central-1 + CLI_REGION: eu-central-1 depend-on: - publish_to_local_registry - identifier: sql_models_1 @@ -861,7 +871,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/sql-models-1.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: eu-north-1 depend-on: - publish_to_local_registry - identifier: sql_iam_access @@ -871,7 +881,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/sql-iam-access.test.ts - CLI_REGION: eu-north-1 + CLI_REGION: eu-west-1 depend-on: - publish_to_local_registry - identifier: default_ddb_canary @@ -881,7 +891,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/default-ddb-canary.test.ts - CLI_REGION: eu-north-1 + CLI_REGION: eu-south-1 depend-on: - publish_to_local_registry - identifier: base_cdk_ap_east_1 @@ -1081,7 +1091,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/amplify-table-4.test.ts - CLI_REGION: eu-west-1 + CLI_REGION: eu-west-2 depend-on: - publish_to_local_registry - identifier: amplify_table_3 @@ -1091,7 +1101,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/amplify-table-3.test.ts - CLI_REGION: eu-west-2 + CLI_REGION: eu-west-3 depend-on: - publish_to_local_registry - identifier: amplify_table_2 @@ -1101,7 +1111,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/amplify-table-2.test.ts - CLI_REGION: eu-west-3 + CLI_REGION: me-south-1 depend-on: - publish_to_local_registry - identifier: amplify_table_1 @@ -1111,7 +1121,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/amplify-table-1.test.ts - CLI_REGION: me-south-1 + CLI_REGION: sa-east-1 depend-on: - publish_to_local_registry - identifier: amplify_ddb_canary @@ -1121,7 +1131,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/amplify-ddb-canary.test.ts - CLI_REGION: sa-east-1 + CLI_REGION: us-east-1 depend-on: - publish_to_local_registry - identifier: all_auth_modes @@ -1131,7 +1141,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/all-auth-modes.test.ts - CLI_REGION: us-east-1 + CLI_REGION: us-east-2 depend-on: - publish_to_local_registry - identifier: admin_role @@ -1141,7 +1151,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/admin-role.test.ts - CLI_REGION: us-east-2 + CLI_REGION: us-west-1 depend-on: - publish_to_local_registry - identifier: sql_custom_ssl @@ -1151,7 +1161,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/sql-custom-ssl/sql-custom-ssl.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: restricted_field_auth_gen2 @@ -1162,7 +1172,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/restricted-field-auth/restricted-field-auth-gen2.test.ts - CLI_REGION: ap-northeast-2 + CLI_REGION: ap-south-1 depend-on: - publish_to_local_registry - identifier: restricted_field_auth_gen1 @@ -1173,7 +1183,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/restricted-field-auth/restricted-field-auth-gen1.test.ts - CLI_REGION: ap-south-1 + CLI_REGION: ap-southeast-1 depend-on: - publish_to_local_registry - identifier: restricted_field_auth_gen2_subscriptions_off @@ -1184,7 +1194,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/restricted-field-auth/subscriptions-off/restricted-field-auth-gen2-subscriptions-off.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 depend-on: - publish_to_local_registry - identifier: restricted_field_auth_gen1_subscriptions_off @@ -1195,7 +1205,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/restricted-field-auth/subscriptions-off/restricted-field-auth-gen1-subscriptions-off.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: references_sqlprimary_sqlrelated @@ -1206,7 +1216,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/references/references-sqlprimary-sqlrelated.test.ts - CLI_REGION: ap-south-1 + CLI_REGION: ap-southeast-1 depend-on: - publish_to_local_registry - identifier: references_sqlprimary_ddbrelated @@ -1217,7 +1227,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/references/references-sqlprimary-ddbrelated.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 depend-on: - publish_to_local_registry - identifier: references_ddbprimary_sqlrelated @@ -1228,7 +1238,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/references/references-ddbprimary-sqlrelated.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: references_ddbprimary_ddbrelated @@ -1239,7 +1249,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/references/references-ddbprimary-ddbrelated.test.ts - CLI_REGION: ca-central-1 + CLI_REGION: eu-central-1 depend-on: - publish_to_local_registry - identifier: recursive_relationships_sql @@ -1250,7 +1260,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/recursive/recursive-relationships-sql.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: eu-north-1 depend-on: - publish_to_local_registry - identifier: recursive_relationships_ddb @@ -1261,7 +1271,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/recursive/recursive-relationships-ddb.test.ts - CLI_REGION: eu-north-1 + CLI_REGION: eu-south-1 depend-on: - publish_to_local_registry - identifier: uuid_pk_sqlprimary_sqlrelated @@ -1272,7 +1282,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/postgres-uuid-pk/uuid-pk-sqlprimary-sqlrelated.test.ts - CLI_REGION: eu-south-1 + CLI_REGION: eu-west-1 depend-on: - publish_to_local_registry - identifier: uuid_pk_sqlprimary_ddbrelated @@ -1283,7 +1293,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/postgres-uuid-pk/uuid-pk-sqlprimary-ddbrelated.test.ts - CLI_REGION: eu-west-1 + CLI_REGION: eu-west-2 depend-on: - publish_to_local_registry - identifier: uuid_pk_ddbprimary_sqlrelated @@ -1294,7 +1304,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/postgres-uuid-pk/uuid-pk-ddbprimary-sqlrelated.test.ts - CLI_REGION: eu-west-2 + CLI_REGION: eu-west-3 depend-on: - publish_to_local_registry - identifier: multi_relationship_sqlprimary_sqlrelated @@ -1305,7 +1315,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/multi-relationship/multi-relationship-sqlprimary-sqlrelated.test.ts - CLI_REGION: eu-west-3 + CLI_REGION: me-south-1 depend-on: - publish_to_local_registry - identifier: multi_relationship_sqlprimary_ddbrelated @@ -1316,7 +1326,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/multi-relationship/multi-relationship-sqlprimary-ddbrelated.test.ts - CLI_REGION: me-south-1 + CLI_REGION: sa-east-1 depend-on: - publish_to_local_registry - identifier: multi_relationship_ddbprimary_sqlrelated @@ -1327,7 +1337,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/multi-relationship/multi-relationship-ddbprimary-sqlrelated.test.ts - CLI_REGION: sa-east-1 + CLI_REGION: us-east-1 depend-on: - publish_to_local_registry - identifier: multi_relationship_ddbprimary_ddbrelated @@ -1338,7 +1348,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/multi-relationship/multi-relationship-ddbprimary-ddbrelated.test.ts - CLI_REGION: us-east-1 + CLI_REGION: us-east-2 depend-on: - publish_to_local_registry - identifier: relationships_gen1 @@ -1348,7 +1358,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/relationships/gen1/relationships-gen1.test.ts - CLI_REGION: us-east-2 + CLI_REGION: us-west-1 depend-on: - publish_to_local_registry - identifier: assoc_field_subscriptions_off @@ -1359,7 +1369,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/owner-auth/subscriptions-off/assoc-field-subscriptions-off.test.ts - CLI_REGION: ap-northeast-2 + CLI_REGION: ap-south-1 depend-on: - publish_to_local_registry - identifier: bind_sql_ids @@ -1369,7 +1379,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/owner-auth/bind-sql-ids/bind-sql-ids.test.ts - CLI_REGION: ap-south-1 + CLI_REGION: ap-southeast-1 depend-on: - publish_to_local_registry - identifier: assoc_field_sqlprimary_sqlrelated @@ -1380,7 +1390,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/owner-auth/assoc-field/assoc-field-sqlprimary-sqlrelated.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 depend-on: - publish_to_local_registry - identifier: assoc_field_sqlprimary_ddbrelated @@ -1391,7 +1401,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/owner-auth/assoc-field/assoc-field-sqlprimary-ddbrelated.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: assoc_field_ddbprimary_sqlrelated @@ -1402,7 +1412,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/owner-auth/assoc-field/assoc-field-ddbprimary-sqlrelated.test.ts - CLI_REGION: ca-central-1 + CLI_REGION: eu-central-1 depend-on: - publish_to_local_registry - identifier: assoc_field_ddbprimary_ddbrelated @@ -1413,7 +1423,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/owner-auth/assoc-field/assoc-field-ddbprimary-ddbrelated.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: eu-north-1 depend-on: - publish_to_local_registry - identifier: static_group_auth_sqlprimary_sqlrelated_subscriptions_off @@ -1424,7 +1434,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/static-group-auth/static-group-auth-sqlprimary-sqlrelated-subscriptions-off.test.ts - CLI_REGION: eu-north-1 + CLI_REGION: eu-south-1 depend-on: - publish_to_local_registry - identifier: static_group_auth_sqlprimary_ddbrelated_subscriptions_off @@ -1435,7 +1445,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/static-group-auth/static-group-auth-sqlprimary-ddbrelated-subscriptions-off.test.ts - CLI_REGION: eu-south-1 + CLI_REGION: eu-west-1 depend-on: - publish_to_local_registry - identifier: static_group_auth_ddbprimary_sqlrelated_subscriptions_off @@ -1446,7 +1456,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/static-group-auth/static-group-auth-ddbprimary-sqlrelated-subscriptions-off.test.ts - CLI_REGION: eu-west-1 + CLI_REGION: eu-west-2 depend-on: - publish_to_local_registry - identifier: static_group_auth_ddbprimary_ddbrelated_subscriptions_off @@ -1457,7 +1467,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/static-group-auth/static-group-auth-ddbprimary-ddbrelated-subscriptions-off.test.ts - CLI_REGION: eu-west-2 + CLI_REGION: eu-west-3 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_sqlprimary_sqlrelated_subscriptions_off @@ -1468,7 +1478,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/dynamic-group-auth/dynamic-group-auth-sqlprimary-sqlrelated-subscriptions-off.test.ts - CLI_REGION: eu-west-3 + CLI_REGION: me-south-1 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_sqlprimary_ddbrelated_subscriptions_off @@ -1479,7 +1489,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/dynamic-group-auth/dynamic-group-auth-sqlprimary-ddbrelated-subscriptions-off.test.ts - CLI_REGION: me-south-1 + CLI_REGION: sa-east-1 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_ddbprimary_sqlrelated_subscriptions_off @@ -1490,7 +1500,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/dynamic-group-auth/dynamic-group-auth-ddbprimary-sqlrelated-subscriptions-off.test.ts - CLI_REGION: sa-east-1 + CLI_REGION: us-east-1 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_ddbprimary_ddbrelated_subscriptions_off @@ -1501,7 +1511,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/dynamic-group-auth/dynamic-group-auth-ddbprimary-ddbrelated-subscriptions-off.test.ts - CLI_REGION: us-east-1 + CLI_REGION: us-east-2 depend-on: - publish_to_local_registry - identifier: static_group_auth_sqlprimary_sqlrelated @@ -1512,7 +1522,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/static-group-auth/static-group-auth-sqlprimary-sqlrelated.test.ts - CLI_REGION: us-east-2 + CLI_REGION: us-west-1 depend-on: - publish_to_local_registry - identifier: static_group_auth_sqlprimary_ddbrelated @@ -1523,7 +1533,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/static-group-auth/static-group-auth-sqlprimary-ddbrelated.test.ts - CLI_REGION: us-west-1 + CLI_REGION: us-west-2 depend-on: - publish_to_local_registry - identifier: static_group_auth_ddbprimary_sqlrelated @@ -1534,7 +1544,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/static-group-auth/static-group-auth-ddbprimary-sqlrelated.test.ts - CLI_REGION: us-west-2 + CLI_REGION: ap-northeast-1 depend-on: - publish_to_local_registry - identifier: static_group_auth_ddbprimary_ddbrelated @@ -1545,7 +1555,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/static-group-auth/static-group-auth-ddbprimary-ddbrelated.test.ts - CLI_REGION: ap-northeast-1 + CLI_REGION: ap-northeast-2 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_sqlprimary_sqlrelated @@ -1556,7 +1566,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/dynamic-group-auth/dynamic-group-auth-sqlprimary-sqlrelated.test.ts - CLI_REGION: ap-northeast-2 + CLI_REGION: ap-south-1 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_sqlprimary_ddbrelated @@ -1567,7 +1577,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/dynamic-group-auth/dynamic-group-auth-sqlprimary-ddbrelated.test.ts - CLI_REGION: ap-south-1 + CLI_REGION: ap-southeast-1 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_ddbprimary_sqlrelated @@ -1578,7 +1588,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/dynamic-group-auth/dynamic-group-auth-ddbprimary-sqlrelated.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_ddbprimary_ddbrelated @@ -1589,7 +1599,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/dynamic-group-auth/dynamic-group-auth-ddbprimary-ddbrelated.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: generation @@ -1610,7 +1620,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/deploy-velocity/single-gsi-100k-records.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: replace_2_gsis_update_attr_100k_records @@ -1621,7 +1631,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/deploy-velocity/replace-2-gsis-update-attr-100k-records.test.ts - CLI_REGION: ca-central-1 + CLI_REGION: eu-central-1 depend-on: - publish_to_local_registry - identifier: replace_2_gsis_100k_records @@ -1631,7 +1641,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/deploy-velocity/replace-2-gsis-100k-records.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: eu-north-1 depend-on: - publish_to_local_registry - identifier: 3_gsis_100k_records @@ -1641,7 +1651,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/deploy-velocity/3-gsis-100k-records.test.ts - CLI_REGION: eu-north-1 + CLI_REGION: eu-south-1 depend-on: - publish_to_local_registry - identifier: conversation diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/sql-pg-array-objects.test.ts b/packages/amplify-graphql-api-construct-tests/src/__tests__/sql-pg-array-objects.test.ts new file mode 100644 index 0000000000..887dc26330 --- /dev/null +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/sql-pg-array-objects.test.ts @@ -0,0 +1,55 @@ +import generator from 'generate-password'; +import { getResourceNamesForStrategyName, ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; +import { getRDSTableNamePrefix } from 'amplify-category-api-e2e-core'; +import { SqlDatatabaseController } from '../sql-datatabase-controller'; +import { DURATION_1_HOUR } from '../utils/duration-constants'; +import { testGraphQLAPIArrayAndObjects } from '../sql-tests-common/sql-array-objects'; + +jest.setTimeout(DURATION_1_HOUR); + +describe('CDK GraphQL Transformer deployments with Postgres SQL datasources', () => { + const projFolderName = 'pgmodels'; + + // sufficient password length that meets the requirements for RDS cluster/instance + const [username, password, identifier] = generator.generateMultiple(3, { length: 11 }); + const region = process.env.CLI_REGION ?? 'us-west-2'; + const engine = 'postgres'; + + const databaseController: SqlDatatabaseController = new SqlDatatabaseController( + [ + `CREATE TABLE "${getRDSTableNamePrefix()}contact" ("id" INT PRIMARY KEY, "firstname" VARCHAR(20), "lastname" VARCHAR(50), "tags" VARCHAR[], "address" JSON)`, + ], + { + identifier, + engine, + username, + password, + region, + }, + ); + + const strategyName = `${engine}DBStrategy`; + const resourceNames = getResourceNamesForStrategyName(strategyName); + + beforeAll(async () => { + await databaseController.setupDatabase(); + }); + + afterAll(async () => { + await databaseController.cleanupDatabase(); + }); + + const constructTestOptions = (connectionConfigName: string) => ({ + projFolderName, + region, + connectionConfigName, + dbController: databaseController, + resourceNames, + }); + + testGraphQLAPIArrayAndObjects( + constructTestOptions('connectionUri'), + 'RDS Postgres Model Directive using Connection String SSM parameter', + ImportedRDSType.POSTGRESQL, + ); +}); diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-array-objects/field-map.ts b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-array-objects/field-map.ts new file mode 100644 index 0000000000..c5b6bd9210 --- /dev/null +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-array-objects/field-map.ts @@ -0,0 +1,14 @@ +import { FieldMap } from '../../../utils/sql-crudl-helper'; + +export const contactFieldMap: FieldMap = { + id: true, + firstname: true, + lastname: true, + tags: true, + address: { + city: true, + state: true, + street: true, + zip: true, + }, +}; diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-array-objects/schema.graphql b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-array-objects/schema.graphql new file mode 100644 index 0000000000..9cd38f48fd --- /dev/null +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-array-objects/schema.graphql @@ -0,0 +1,14 @@ +type Contact @refersTo(name: "e2e_test_contact") @model { + id: Int! @primaryKey + firstname: String + lastname: String + tags: [String] + address: ContactAddress +} + +type ContactAddress { + city: String! + state: String! + street: String! + zip: String! +} diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/field-map.ts b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/field-map.ts new file mode 100644 index 0000000000..c338fad0bd --- /dev/null +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/field-map.ts @@ -0,0 +1,13 @@ +import { FieldMap } from '../../../utils/sql-crudl-helper'; + +export const toDoFieldMap: FieldMap = { + id: true, + description: true, +}; + +export const studentFieldMap: FieldMap = { + studentId: true, + classId: true, + firstName: true, + lastName: true, +}; diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql new file mode 100644 index 0000000000..b99fe7eeb8 --- /dev/null +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql @@ -0,0 +1,10 @@ +type Todo @model @refersTo(name: "e2e_test_todos") { + id: ID! @primaryKey + description: String! +} +type Student @model @refersTo(name: "e2e_test_students") { + studentId: Int! @primaryKey(sortKeyFields: ["classId"]) + classId: String! + firstName: String + lastName: String +} diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-array-objects.ts b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-array-objects.ts new file mode 100644 index 0000000000..ba698455f3 --- /dev/null +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-array-objects.ts @@ -0,0 +1,255 @@ +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { LambdaClient, GetProvisionedConcurrencyConfigCommand } from '@aws-sdk/client-lambda'; +import { ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; +import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync'; +import { createNewProjectDir, deleteProjectDir } from 'amplify-category-api-e2e-core'; +import { initCDKProject, cdkDeploy, cdkDestroy } from '../commands'; +import { SqlDatatabaseController } from '../sql-datatabase-controller'; +import { CRUDLHelper } from '../utils/sql-crudl-helper'; +import { contactFieldMap } from './schemas/sql-array-objects/field-map'; +import { ONE_MINUTE } from '../utils/duration-constants'; + +export const testGraphQLAPIArrayAndObjects = ( + options: { + projFolderName: string; + region: string; + connectionConfigName: string; + dbController: SqlDatatabaseController; + resourceNames: { sqlLambdaAliasName: string }; + }, + testBlockDescription: string, + engine: ImportedRDSType, +): void => { + describe(`${testBlockDescription} - ${engine}`, () => { + let projRoot; + let region, lambdaFunctionName, lambdaAliasName; + + let dbController: SqlDatatabaseController; + let contactTableCRUDLHelper: CRUDLHelper; + + beforeAll(async () => { + ({ + region, + dbController, + resourceNames: { sqlLambdaAliasName: lambdaAliasName }, + } = options); + const { projFolderName, connectionConfigName } = options; + + const templatePath = path.resolve(path.join(__dirname, '..', '__tests__', 'backends', 'sql-models')); + const schemaPath = path.resolve(path.join(__dirname, '..', 'sql-tests-common', 'schemas', 'sql-array-objects', 'schema.graphql')); + const schemaConfigString = fs.readFileSync(schemaPath).toString(); + + projRoot = await createNewProjectDir(projFolderName); + const name = await initCDKProject(projRoot, templatePath); + dbController.writeDbDetails(projRoot, connectionConfigName, schemaConfigString); + const outputs = await cdkDeploy(projRoot, '--all', { postDeployWaitMs: ONE_MINUTE }); + const { awsAppsyncApiEndpoint: apiEndpoint, awsAppsyncApiKey: apiKey } = outputs[name]; + lambdaFunctionName = outputs[name].SQLFunctionName; + + const appSyncClient = new AWSAppSyncClient({ + url: apiEndpoint, + region, + disableOffline: true, + auth: { + type: AUTH_TYPE.API_KEY, + apiKey, + }, + }); + + contactTableCRUDLHelper = new CRUDLHelper(appSyncClient, 'Contact', 'Contacts', contactFieldMap); + }); + + afterAll(async () => { + try { + await cdkDestroy(projRoot, '--all'); + await dbController.clearDatabase(); + } catch (err) { + console.log(`Error invoking 'cdk destroy': ${err}`); + } + + deleteProjectDir(projRoot); + }); + + test(`check CRUDL on contact table with array and objects - ${engine}`, async () => { + // Create Contact Mutation + const contact1 = await contactTableCRUDLHelper.create({ + id: 1, + firstname: 'David', + lastname: 'Smith', + tags: ['tag1', 'tag2'], + address: { + city: 'Seattle', + state: 'WA', + street: '123 Main St', + zip: '98115', + }, + }); + const contact2 = await contactTableCRUDLHelper.create({ + id: 2, + firstname: 'Chris', + lastname: 'Sundersingh', + tags: ['tag3', 'tag4'], + address: { + city: 'Seattle', + state: 'WA', + street: '456 Another St', + zip: '98119', + }, + }); + + expect(contact1).toBeDefined(); + expect(contact1.id).toEqual(1); + expect(contact1.firstname).toEqual('David'); + expect(contact1.lastname).toEqual('Smith'); + expect(contact1.tags).toEqual(expect.arrayContaining(['tag1', 'tag2'])); + expect(contact1.address).toEqual( + expect.objectContaining({ + city: 'Seattle', + state: 'WA', + street: '123 Main St', + zip: '98115', + }), + ); + + // Get Contact Query + const getContact1 = await contactTableCRUDLHelper.get({ id: contact1.id }); + + expect(getContact1.id).toEqual(contact1.id); + expect(getContact1.firstname).toEqual('David'); + expect(getContact1.lastname).toEqual('Smith'); + expect(getContact1.tags).toEqual(expect.arrayContaining(['tag1', 'tag2'])); + expect(getContact1.address).toEqual( + expect.objectContaining({ + city: 'Seattle', + state: 'WA', + street: '123 Main St', + zip: '98115', + }), + ); + + // Update Contact Query + const updateContact1 = await contactTableCRUDLHelper.update({ + id: contact1.id, + firstname: 'David', + lastname: 'Jones', + tags: ['tag1', 'tag2', 'tag3'], + address: { + city: 'Seattle', + state: 'WA', + street: '12345 Main St', + zip: '98110', + }, + }); + + expect(updateContact1.id).toEqual(contact1.id); + expect(updateContact1.firstname).toEqual('David'); + expect(updateContact1.lastname).toEqual('Jones'); + expect(updateContact1.tags).toEqual(expect.arrayContaining(['tag1', 'tag2', 'tag3'])); + expect(updateContact1.address).toEqual( + expect.objectContaining({ + city: 'Seattle', + state: 'WA', + street: '12345 Main St', + zip: '98110', + }), + ); + + // Get Contact Query after update + const getUpdatedContact1 = await contactTableCRUDLHelper.get({ id: contact1.id }); + + expect(getUpdatedContact1.id).toEqual(contact1.id); + expect(getUpdatedContact1.firstname).toEqual('David'); + expect(getUpdatedContact1.lastname).toEqual('Jones'); + expect(getUpdatedContact1.tags).toEqual(expect.arrayContaining(['tag1', 'tag2', 'tag3'])); + expect(getUpdatedContact1.address).toEqual( + expect.objectContaining({ + city: 'Seattle', + state: 'WA', + street: '12345 Main St', + zip: '98110', + }), + ); + + // List Contact Query + const listContact = await contactTableCRUDLHelper.list(); + + expect(listContact.items.length).toEqual(2); + expect(listContact.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: contact1.id, + firstname: 'David', + lastname: 'Jones', + tags: expect.arrayContaining(['tag1', 'tag2', 'tag3']), + address: expect.objectContaining({ + city: 'Seattle', + state: 'WA', + street: '12345 Main St', + zip: '98110', + }), + }), + expect.objectContaining({ + id: contact2.id, + firstname: 'Chris', + lastname: 'Sundersingh', + tags: expect.arrayContaining(['tag3', 'tag4']), + address: expect.objectContaining({ + city: 'Seattle', + state: 'WA', + street: '456 Another St', + zip: '98119', + }), + }), + ]), + ); + + // Delete Contact Mutation + const deleteContact1 = await contactTableCRUDLHelper.delete({ id: contact1.id }); + + expect(deleteContact1.id).toEqual(contact1.id); + expect(deleteContact1.firstname).toEqual('David'); + expect(deleteContact1.lastname).toEqual('Jones'); + expect(deleteContact1.tags).toEqual(expect.arrayContaining(['tag1', 'tag2', 'tag3'])); + expect(deleteContact1.address).toEqual( + expect.objectContaining({ + city: 'Seattle', + state: 'WA', + street: '12345 Main St', + zip: '98110', + }), + ); + + // List Contact Query after delete + const listContactAfterDelete = await contactTableCRUDLHelper.list(); + + expect(listContactAfterDelete.items.length).toEqual(1); + expect(listContactAfterDelete.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: contact2.id, + firstname: 'Chris', + lastname: 'Sundersingh', + tags: expect.arrayContaining(['tag3', 'tag4']), + address: expect.objectContaining({ + city: 'Seattle', + state: 'WA', + street: '456 Another St', + zip: '98119', + }), + }), + ]), + ); + }); + + test(`check SQL Lambda provisioned concurrency - ${engine}`, async () => { + const client = new LambdaClient({ region }); + const command = new GetProvisionedConcurrencyConfigCommand({ + FunctionName: lambdaFunctionName, + Qualifier: lambdaAliasName, + }); + const response = await client.send(command); + expect(response.RequestedProvisionedConcurrentExecutions).toEqual(2); + }); + }); +}; diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts index 612ca423e9..0411dd2290 100644 --- a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts @@ -1,11 +1,13 @@ import * as path from 'path'; +import * as fs from 'fs-extra'; import { LambdaClient, GetProvisionedConcurrencyConfigCommand } from '@aws-sdk/client-lambda'; import { ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync'; -import { createNewProjectDir, deleteProjectDir, getRDSTableNamePrefix } from 'amplify-category-api-e2e-core'; +import { createNewProjectDir, deleteProjectDir } from 'amplify-category-api-e2e-core'; import { initCDKProject, cdkDeploy, cdkDestroy } from '../commands'; import { SqlDatatabaseController } from '../sql-datatabase-controller'; import { CRUDLHelper } from '../utils/sql-crudl-helper'; +import { toDoFieldMap, studentFieldMap } from './schemas/sql-models/field-map'; import { ONE_MINUTE } from '../utils/duration-constants'; export const testGraphQLAPI = ( @@ -20,32 +22,31 @@ export const testGraphQLAPI = ( engine: ImportedRDSType, ): void => { describe(`${testBlockDescription} - ${engine}`, () => { - const amplifyGraphqlSchema = ` - type Todo @model @refersTo(name: "${getRDSTableNamePrefix()}todos") { - id: ID! @primaryKey - description: String! - } - type Student @model @refersTo(name: "${getRDSTableNamePrefix()}students") { - studentId: Int! @primaryKey(sortKeyFields: ["classId"]) - classId: String! - firstName: String - lastName: String - } - `; - const { projFolderName, region, connectionConfigName, dbController, resourceNames } = options; - const templatePath = path.resolve(path.join(__dirname, '..', '__tests__', 'backends', 'sql-models')); - let projRoot; - let name; - let outputs; - let toDoTableCRUDLHelper, studentTableCRUDLHelper; + let region, lambdaFunctionName, lambdaAliasName; + + let dbController: SqlDatatabaseController; + let toDoTableCRUDLHelper: CRUDLHelper; + let studentTableCRUDLHelper: CRUDLHelper; beforeAll(async () => { + ({ + region, + dbController, + resourceNames: { sqlLambdaAliasName: lambdaAliasName }, + } = options); + const { projFolderName, connectionConfigName } = options; + + const templatePath = path.resolve(path.join(__dirname, '..', '__tests__', 'backends', 'sql-models')); + const schemaPath = path.resolve(path.join(__dirname, '..', 'sql-tests-common', 'schemas', 'sql-models', 'schema.graphql')); + const schemaConfigString = fs.readFileSync(schemaPath).toString(); + projRoot = await createNewProjectDir(projFolderName); - name = await initCDKProject(projRoot, templatePath); - dbController.writeDbDetails(projRoot, connectionConfigName, amplifyGraphqlSchema); - outputs = await cdkDeploy(projRoot, '--all', { postDeployWaitMs: ONE_MINUTE }); + const name = await initCDKProject(projRoot, templatePath); + dbController.writeDbDetails(projRoot, connectionConfigName, schemaConfigString); + const outputs = await cdkDeploy(projRoot, '--all', { postDeployWaitMs: ONE_MINUTE }); const { awsAppsyncApiEndpoint: apiEndpoint, awsAppsyncApiKey: apiKey } = outputs[name]; + lambdaFunctionName = outputs[name].SQLFunctionName; const appSyncClient = new AWSAppSyncClient({ url: apiEndpoint, @@ -57,8 +58,8 @@ export const testGraphQLAPI = ( }, }); - toDoTableCRUDLHelper = new CRUDLHelper(appSyncClient, 'Todo', 'Todos', ['id', 'description']); - studentTableCRUDLHelper = new CRUDLHelper(appSyncClient, 'Student', 'Students', ['studentId', 'classId', 'firstName', 'lastName']); + toDoTableCRUDLHelper = new CRUDLHelper(appSyncClient, 'Todo', 'Todos', toDoFieldMap); + studentTableCRUDLHelper = new CRUDLHelper(appSyncClient, 'Student', 'Students', studentFieldMap); }); afterAll(async () => { @@ -380,10 +381,9 @@ export const testGraphQLAPI = ( test(`check SQL Lambda provisioned concurrency - ${engine}`, async () => { const client = new LambdaClient({ region }); - const functionName = outputs[name].SQLFunctionName; const command = new GetProvisionedConcurrencyConfigCommand({ - FunctionName: functionName, - Qualifier: resourceNames.sqlLambdaAliasName, + FunctionName: lambdaFunctionName, + Qualifier: lambdaAliasName, }); const response = await client.send(command); expect(response.RequestedProvisionedConcurrentExecutions).toEqual(2); diff --git a/packages/amplify-graphql-api-construct-tests/src/utils/sql-crudl-helper.ts b/packages/amplify-graphql-api-construct-tests/src/utils/sql-crudl-helper.ts index 76c7aecbeb..992ee4ed7a 100644 --- a/packages/amplify-graphql-api-construct-tests/src/utils/sql-crudl-helper.ts +++ b/packages/amplify-graphql-api-construct-tests/src/utils/sql-crudl-helper.ts @@ -1,6 +1,46 @@ import AWSAppSyncClient from 'aws-appsync'; import gql from 'graphql-tag'; +/** + * Type that represents the GraphQL seletion set structure, used for conversion of FieldMap object to selection set string. + * + * Selecting scalar array: + * - Scalar array in GraphQL custom type is same in syntax in selection set as a single scalar field. + * + * Selecting object/array of objects: + * - Syntax for selecting fields is the same regardless of whether a field returns a single object or an array of objects. + * + * Example: + * - selection set in object: + * ```typescript + * { id: number, name: string, tags: string[], address: { street: string, city: string } } + * ``` + * - in FieldMap: + * ```typescript + * { id: true, name: true, tags: true, address: { street: true, city: true } } + * ``` + * - in selection set syntax string: + * ```typescript + * `id + * name + * tags + * address { + * street + * city + * }` + * ``` + * + * Note: + * - If field value is an object or array of objects, define as nested object. + * - Otherwise, use a `<[key]: true>` pair + * + * Future: + * - Handle edge cases e.g. nested arrays and optional fields. + */ +export type FieldMap = { + [key: string]: true | FieldMap; +}; + /** * A class that attempts to handle basic/generic CRUDL operations using AppSyncClient API */ @@ -9,14 +49,14 @@ export class CRUDLHelper { private readonly appSyncClient: AWSAppSyncClient, private readonly modelName: string, private readonly modelListName: string, - private readonly fields: Array, + private readonly fieldMap: FieldMap, ) {} public create = async (args: Record): Promise> => { const mutation = ` mutation Create${this.modelName}($input: Create${this.modelName}Input!, $condition: Model${this.modelName}ConditionInput) { create${this.modelName}(input: $input, condition: $condition) { - ${this.getOutputFields()} + ${this.getOutputFields(this.fieldMap)} } } `; @@ -34,7 +74,7 @@ export class CRUDLHelper { const query = ` query Get${this.modelName}(${this.getQueryInputTypes(args)}) { get${this.modelName}(${this.getQueryInputs(args)}) { - ${this.getOutputFields()} + ${this.getOutputFields(this.fieldMap)} } } `; @@ -53,7 +93,7 @@ export class CRUDLHelper { const query = ` query { get${this.modelName}(id: "${id}") { - ${this.getOutputFields()} + ${this.getOutputFields(this.fieldMap)} } } `; @@ -70,7 +110,7 @@ export class CRUDLHelper { const mutation = ` mutation Update${this.modelName}($input: Update${this.modelName}Input!, $condition: Model${this.modelName}ConditionInput) { update${this.modelName}(input: $input, condition: $condition) { - ${this.getOutputFields()} + ${this.getOutputFields(this.fieldMap)} } } `; @@ -88,7 +128,7 @@ export class CRUDLHelper { const mutation = ` mutation Delete${this.modelName}($input: Delete${this.modelName}Input!, $condition: Model${this.modelName}ConditionInput) { delete${this.modelName}(input: $input, condition: $condition) { - ${this.getOutputFields()} + ${this.getOutputFields(this.fieldMap)} } } `; @@ -107,7 +147,7 @@ export class CRUDLHelper { query List${this.modelListName}($limit: Int, $nextToken: String, $filter: Model${this.modelName}FilterInput) { list${this.modelListName}(limit: $limit, nextToken: $nextToken, filter: $filter) { items { - ${this.getOutputFields()} + ${this.getOutputFields(this.fieldMap)} } nextToken } @@ -132,20 +172,21 @@ export class CRUDLHelper { expect(errorMessage).toEqual('GraphQL error: Error processing the request. Check the logs for more details.'); }; - private getMutationInputs = (args: Record): string => { - return Object.entries(args) - .map(([key, value]) => `${key}: "${value}"`) - .join(', '); - }; - - private getOutputFields = (): string => { - return `${this.fields.join('\n')}\n`; - }; + private getOutputFields = (fieldMap: FieldMap, indentLevel = 1): string => { + let output = ''; + const indent = ' '.repeat(indentLevel); + + for (const [key, value] of Object.entries(fieldMap)) { + if (value === true) { + output += `${indent}${key}\n`; + } else if (typeof value === 'object') { + output += `${indent}${key} {\n`; + output += this.getOutputFields(value, indentLevel + 1); + output += `${indent}}\n`; + } + } - private getQueryInputTypes = (args: Record): string => { - return Object.entries(args) - .map(([key, value]) => `$${key}: ${this.getGraphQLType(value)}!`) - .join(', '); + return output; }; private getQueryInputs = (args: Record): string => { @@ -164,4 +205,10 @@ export class CRUDLHelper { return 'String'; } }; + + private getQueryInputTypes = (args: Record): string => { + return Object.entries(args) + .map(([key, value]) => `$${key}: ${this.getGraphQLType(value)}!`) + .join(', '); + }; } From 942df720c9c3c467112808f49b5c039d3399e50b Mon Sep 17 00:00:00 2001 From: Ian Saultz <52051793+atierian@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:43:51 -0400 Subject: [PATCH 04/15] fix(model-transformer) IndexName -> index in query list resolver (#2912) --- ...mplify-graphql-index-transformer.test.ts.snap | 16 ++++++++-------- ...-graphql-primary-key-transformer.test.ts.snap | 10 +++++----- .../__snapshots__/model-transformer.test.ts.snap | 6 +++--- .../src/resolvers/dynamodb/query.ts | 2 +- ...ify-graphql-has-many-transformer.test.ts.snap | 16 ++++++++-------- ...l-has-one-references-transformer.test.ts.snap | 4 ++-- ...graphql-many-to-many-transformer.test.ts.snap | 12 ++++++------ ...y-graphql-searchable-transformer.test.ts.snap | 2 +- 8 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-index-transformer.test.ts.snap b/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-index-transformer.test.ts.snap index 078251d55b..3d392dd6a2 100644 --- a/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-index-transformer.test.ts.snap +++ b/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-index-transformer.test.ts.snap @@ -431,7 +431,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -1251,7 +1251,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -1750,7 +1750,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -3577,7 +3577,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -4558,7 +4558,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -5424,7 +5424,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -6219,7 +6219,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -7385,7 +7385,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", diff --git a/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap b/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap index e50069bbae..03e1bd397a 100644 --- a/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap +++ b/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap @@ -2060,7 +2060,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -2654,7 +2654,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -3201,7 +3201,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -3795,7 +3795,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -4860,7 +4860,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", diff --git a/packages/amplify-graphql-model-transformer/src/__tests__/__snapshots__/model-transformer.test.ts.snap b/packages/amplify-graphql-model-transformer/src/__tests__/__snapshots__/model-transformer.test.ts.snap index c676b2fcd3..0115ca1500 100644 --- a/packages/amplify-graphql-model-transformer/src/__tests__/__snapshots__/model-transformer.test.ts.snap +++ b/packages/amplify-graphql-model-transformer/src/__tests__/__snapshots__/model-transformer.test.ts.snap @@ -2579,7 +2579,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -3166,7 +3166,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -3753,7 +3753,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", diff --git a/packages/amplify-graphql-model-transformer/src/resolvers/dynamodb/query.ts b/packages/amplify-graphql-model-transformer/src/resolvers/dynamodb/query.ts index 981281c39e..fc153e6894 100644 --- a/packages/amplify-graphql-model-transformer/src/resolvers/dynamodb/query.ts +++ b/packages/amplify-graphql-model-transformer/src/resolvers/dynamodb/query.ts @@ -160,7 +160,7 @@ export const generateListRequestTemplate = (): string => { ]), qref(methodCall(ref(`${requestVariable}.put`), str('operation'), str('Scan'))), ), - iff(not(methodCall(ref('util.isNull'), ref(indexNameVariable))), set(ref(`${requestVariable}.IndexName`), ref(indexNameVariable))), + iff(not(methodCall(ref('util.isNull'), ref(indexNameVariable))), set(ref(`${requestVariable}.index`), ref(indexNameVariable))), toJson(ref(requestVariable)), ]); return printBlock('List Request')(expression); diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap index e2f7b25b72..3bd49fe694 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap @@ -16925,7 +16925,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -16988,7 +16988,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -17051,7 +17051,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -17114,7 +17114,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -17177,7 +17177,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -17240,7 +17240,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -17365,7 +17365,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -17536,7 +17536,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-one-references-transformer.test.ts.snap b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-one-references-transformer.test.ts.snap index 199aee7fa6..8bba467cab 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-one-references-transformer.test.ts.snap +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-one-references-transformer.test.ts.snap @@ -1576,7 +1576,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -1747,7 +1747,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-many-to-many-transformer.test.ts.snap b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-many-to-many-transformer.test.ts.snap index 119e17c7ab..84794c73f9 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-many-to-many-transformer.test.ts.snap +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-many-to-many-transformer.test.ts.snap @@ -1897,7 +1897,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -2022,7 +2022,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -2147,7 +2147,7 @@ $util.qr($ctx.stash.put(\\"modelQueryExpression\\", $modelQueryExpression)) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -5884,7 +5884,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -5947,7 +5947,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", @@ -6010,7 +6010,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", diff --git a/packages/amplify-graphql-searchable-transformer/src/__tests__/__snapshots__/amplify-graphql-searchable-transformer.test.ts.snap b/packages/amplify-graphql-searchable-transformer/src/__tests__/__snapshots__/amplify-graphql-searchable-transformer.test.ts.snap index 6613f944a2..21a0086af6 100644 --- a/packages/amplify-graphql-searchable-transformer/src/__tests__/__snapshots__/amplify-graphql-searchable-transformer.test.ts.snap +++ b/packages/amplify-graphql-searchable-transformer/src/__tests__/__snapshots__/amplify-graphql-searchable-transformer.test.ts.snap @@ -1174,7 +1174,7 @@ $util.toJson({}) $util.qr($ListRequest.put(\\"operation\\", \\"Scan\\")) #end #if( !$util.isNull($ctx.stash.metadata.index) ) - #set( $ListRequest.IndexName = $ctx.stash.metadata.index ) + #set( $ListRequest.index = $ctx.stash.metadata.index ) #end $util.toJson($ListRequest) ## [End] List Request. **", From 4d227b003d2a20db940691b11524dc60f72905a9 Mon Sep 17 00:00:00 2001 From: Phani Srikar Edupuganti <55896475+phani-srikar@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:53:27 -0700 Subject: [PATCH 05/15] chore: upgrade cdk library dependency to 2.158.0 (#2876) * chore: upgrade cdk dependency to 2.158.0 * chore: install and use nvm * chore: use full version for nvm * chore: testing linux build with nvm * chore: fix version in cdk tests * chore: update jsii files * update: increase memory size * add: debug statement * update: mem size back to 8096, use ps1 file for shell script * fix: path to Setup-NodeVersion.ps1 * fix: path to codebuild_specs/Setup-NodeVersion.ps1 * add: set runtime version * update: image * add: debug statement * update: use earlier code * add: debug statements * update: clean up code * update: use the correct image * add: list installed node versions and used nodejs.install * restart: install nvm using choco * add: back mem size variable * add: nvm install and use 18.20.4 * add: env var NVM_HOME and NVM_SYMLINK * add: spawn powershell as admin * update: remove all other builds * add: debug statement * add: env var path * update: print env var * add: commands * update: env var set up * add: refresh env var * update: more debug statement * update * revamp: find nvm.exe * update: install nvm windows directly * update: launch new shell if current shell does not recognize nvm * update: install node in buildspec * add: install and use node in build spec * update: use single quote to prevent interpreting \ * add: 2 scripts, one for installing nvm, another for using nvm * fix: path error * test: which way set env var * update: set up env var in pre_build * update: use choco in pre-build * fix: syntax error * update: build_windows working, running all tests * test: remove bootstrap in test code * debug: _runGqlE2ETests * update: debug_workflow * update: debug_workflow * update: debug_workflow * update: debug_workflow * add: debug statement * add: debug test * add: debug * update: use uuid for bucket name * remove: use of uuid * add: debug statement * update: use differrent bucket name * add: mili second timestamp * add: debug statement * remove: debug statement * remove: redundant code --------- Co-authored-by: Bobby Yu Co-authored-by: Tim Schmelter --- codebuild_specs/build_linux.yml | 2 + codebuild_specs/build_windows.yml | 9 +- dependency_licenses.txt | 291 +++++++++++++++++- package.json | 2 +- packages/amplify-data-construct/.jsii | 87 +++++- packages/amplify-data-construct/package.json | 4 +- .../package.json | 2 +- .../__snapshots__/base-cdk.test.ts.snap | 4 +- .../src/__tests__/admin-role.test.ts | 4 +- .../src/__tests__/base-cdk.test.ts | 2 +- .../src/__tests__/data-construct.test.ts | 2 +- .../src/commands.ts | 2 +- packages/amplify-graphql-api-construct/.jsii | 87 +++++- .../package.json | 4 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../amplify-graphql-transformer/package.json | 2 +- .../resources/jsonServer/package.json | 4 +- scripts/package.json | 10 +- scripts/yarn.lock | 28 +- shared-scripts.sh | 9 + yarn.lock | 29 +- 35 files changed, 514 insertions(+), 100 deletions(-) diff --git a/codebuild_specs/build_linux.yml b/codebuild_specs/build_linux.yml index e55b633040..a5d9a51520 100644 --- a/codebuild_specs/build_linux.yml +++ b/codebuild_specs/build_linux.yml @@ -1,6 +1,8 @@ version: 0.2 env: shell: bash + variables: + NODE_OPTIONS: --max-old-space-size=8096 phases: build: commands: diff --git a/codebuild_specs/build_windows.yml b/codebuild_specs/build_windows.yml index 4c1c2f0157..11515e8b1c 100644 --- a/codebuild_specs/build_windows.yml +++ b/codebuild_specs/build_windows.yml @@ -1,12 +1,19 @@ version: 0.2 env: shell: powershell.exe + variables: + NODE_OPTIONS: --max-old-space-size=8096 phases: + pre_build: + commands: + - choco install -fy nodejs-lts --version=18.20.4 + - | + $nodeVersion = node -v + Write-Host "Node version: $nodeVersion" build: commands: - yarn run production-build - yarn build-tests - artifacts: files: - 'shared-scripts.sh' diff --git a/dependency_licenses.txt b/dependency_licenses.txt index 5cfb5fe011..e8a076a8b4 100644 --- a/dependency_licenses.txt +++ b/dependency_licenses.txt @@ -1247,6 +1247,262 @@ Copyright 2018-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. ----- +The following software may be included in this product: @aws-cdk/cloud-assembly-schema. A copy of the source code may be downloaded from https://github.com/cdklabs/cloud-assembly-schema.git. This software contains the following license and notice below: + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +NOTICE + +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +------------------------------------------------------------------------------- + +The AWS CDK includes the following third-party software/licensing: + +** jsonschema - https://www.npmjs.com/package/jsonschema +Copyright (C) 2012-2015 Tom de Grunt + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + +** semver - https://www.npmjs.com/package/semver +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------- + +----- + The following software may be included in this product: @aws-crypto/crc32, @aws-crypto/crc32c, @aws-crypto/util. A copy of the source code may be downloaded from git@github.com:aws/aws-sdk-js-crypto-helpers.git (@aws-crypto/crc32), git@github.com:aws/aws-sdk-js-crypto-helpers.git (@aws-crypto/crc32c), git@github.com:aws/aws-sdk-js-crypto-helpers.git (@aws-crypto/util). This software contains the following license and notice below: Apache License @@ -9025,18 +9281,37 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------- -** uri-js - https://www.npmjs.com/package/uri-js/v/4.4.1 | BSD-2-Clause -Copyright 2011 Gary Court. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +** fast-uri - https://www.npmjs.com/package/fast-uri/v/3.0.1 | BSD-3-Clause +Copyright (c) 2021 The Fastify Team +Copyright (c) 2011-2021, Gary Court until https://github.com/garycourt/uri-js/commit/a1acf730b4bba3f1097c9f52e7d9d3aba8cdcaae +All rights reserved. -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * The names of any contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * * * -The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. +The complete list of contributors can be found at: +- https://github.com/garycourt/uri-js/graphs/contributors ---------------- diff --git a/package.json b/package.json index 38132b84e3..5359ecb809 100755 --- a/package.json +++ b/package.json @@ -406,7 +406,7 @@ "**/istanbul/async": "^2.6.4", "**/jake/async": "^2.6.4", "**/nth-check": "^2.0.1", - "aws-cdk-lib": "~2.152.0", + "aws-cdk-lib": "~2.158.0", "lodash": "^4.17.21", "node-fetch": "^2.6.7", "cross-fetch": "^2.2.6", diff --git a/packages/amplify-data-construct/.jsii b/packages/amplify-data-construct/.jsii index d3008a4540..2db7108892 100644 --- a/packages/amplify-data-construct/.jsii +++ b/packages/amplify-data-construct/.jsii @@ -131,7 +131,7 @@ }, "dependencies": { "@aws-amplify/graphql-api-construct": "1.14.0", - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "dependencyClosure": { @@ -220,6 +220,36 @@ } } }, + "@aws-cdk/cloud-assembly-schema": { + "targets": { + "dotnet": { + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/main/logo/default-256-dark.png", + "namespace": "Amazon.CDK.CloudAssembly.Schema", + "packageId": "Amazon.CDK.CloudAssembly.Schema" + }, + "go": { + "moduleName": "github.com/cdklabs/cloud-assembly-schema-go" + }, + "java": { + "maven": { + "artifactId": "cdk-cloud-assembly-schema", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.cloudassembly.schema" + }, + "js": { + "npm": "@aws-cdk/cloud-assembly-schema" + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 2" + ], + "distName": "aws-cdk.cloud-assembly-schema", + "module": "aws_cdk.cloud_assembly_schema" + } + } + }, "aws-cdk-lib": { "submodules": { "aws-cdk-lib.alexa_ask": { @@ -2290,6 +2320,19 @@ } } }, + "aws-cdk-lib.aws_launchwizard": { + "targets": { + "dotnet": { + "package": "Amazon.CDK.AWS.LaunchWizard" + }, + "java": { + "package": "software.amazon.awscdk.services.launchwizard" + }, + "python": { + "module": "aws_cdk.aws_launchwizard" + } + } + }, "aws-cdk-lib.aws_lex": { "targets": { "dotnet": { @@ -2784,6 +2827,19 @@ } } }, + "aws-cdk-lib.aws_pcaconnectorscep": { + "targets": { + "dotnet": { + "package": "Amazon.CDK.AWS.PCAConnectorSCEP" + }, + "java": { + "package": "software.amazon.awscdk.services.pcaconnectorscep" + }, + "python": { + "module": "aws_cdk.aws_pcaconnectorscep" + } + } + }, "aws-cdk-lib.aws_personalize": { "targets": { "dotnet": { @@ -3499,6 +3555,19 @@ } } }, + "aws-cdk-lib.aws_ssmquicksetup": { + "targets": { + "dotnet": { + "package": "Amazon.CDK.AWS.SSMQuickSetup" + }, + "java": { + "package": "software.amazon.awscdk.services.ssmquicksetup" + }, + "python": { + "module": "aws_cdk.aws_ssmquicksetup" + } + } + }, "aws-cdk-lib.aws_sso": { "targets": { "dotnet": { @@ -3746,19 +3815,7 @@ } } }, - "aws-cdk-lib.cloud_assembly_schema": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CloudAssembly.Schema" - }, - "java": { - "package": "software.amazon.awscdk.cloudassembly.schema" - }, - "python": { - "module": "aws_cdk.cloud_assembly_schema" - } - } - }, + "aws-cdk-lib.cloud_assembly_schema": {}, "aws-cdk-lib.cloudformation_include": { "targets": { "dotnet": { @@ -3970,5 +4027,5 @@ }, "types": {}, "version": "1.10.2", - "fingerprint": "Ur2Iw+iIJolzSkulUyEDgbJ9aLqUExVDrAiHRMhbnL4=" + "fingerprint": "naLvPjdr9z9wuDIK2e15PtMBvodNvsprfPj2tyr4FaA=" } \ No newline at end of file diff --git a/packages/amplify-data-construct/package.json b/packages/amplify-data-construct/package.json index 3448238420..cdc8c53c16 100644 --- a/packages/amplify-data-construct/package.json +++ b/packages/amplify-data-construct/package.json @@ -284,7 +284,7 @@ "devDependencies": { "@types/fs-extra": "^8.0.1", "@types/node": "^18.0.0", - "aws-cdk-lib": "2.152.0", + "aws-cdk-lib": "2.158.0", "constructs": "10.3.0", "jsii": "^5.4.23", "jsii-pacmak": "^1.84.0", @@ -294,7 +294,7 @@ "ts-node": "^8.10.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "stability": "stable", diff --git a/packages/amplify-graphql-api-construct-tests/package.json b/packages/amplify-graphql-api-construct-tests/package.json index bcfd8214e2..251a687fbf 100644 --- a/packages/amplify-graphql-api-construct-tests/package.json +++ b/packages/amplify-graphql-api-construct-tests/package.json @@ -45,7 +45,7 @@ "@aws-amplify/graphql-transformer-core": "3.1.2", "@aws-amplify/graphql-transformer-interfaces": "4.1.1", "@types/node": "^18.0.0", - "aws-cdk-lib": "2.152.0", + "aws-cdk-lib": "2.158.0", "constructs": "10.3.0", "ts-node": "^8.10.2" }, diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/__snapshots__/base-cdk.test.ts.snap b/packages/amplify-graphql-api-construct-tests/src/__tests__/__snapshots__/base-cdk.test.ts.snap index 82089cc0d1..22228fd1ce 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/__snapshots__/base-cdk.test.ts.snap +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/__snapshots__/base-cdk.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CDK GraphQL Transformer CDK base case - aws-cdk-lib@2.152.0 1`] = ` +exports[`CDK GraphQL Transformer CDK base case - aws-cdk-lib@2.158.0 1`] = ` Object { "body": Object { "data": Object { @@ -14,7 +14,7 @@ Object { } `; -exports[`CDK GraphQL Transformer CDK base case - aws-cdk-lib@2.152.0 2`] = ` +exports[`CDK GraphQL Transformer CDK base case - aws-cdk-lib@2.158.0 2`] = ` Object { "body": Object { "data": Object { diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/admin-role.test.ts b/packages/amplify-graphql-api-construct-tests/src/__tests__/admin-role.test.ts index 20e27fb73a..90fffa0541 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/admin-role.test.ts +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/admin-role.test.ts @@ -29,10 +29,10 @@ describe('CDK Auth Modes', () => { test('Can be invoked with Admin Roles defined', async () => { const templatePath = path.resolve(path.join(__dirname, 'backends', 'admin-role')); const name = await initCDKProject(projRoot, templatePath, { - cdkVersion: '2.152.0', // Explicitly declaring this, since this version needs to match cognito idp + cdkVersion: '2.158.0', // Explicitly declaring this, since this version needs to match cognito idp additionalDependencies: [ 'esbuild', // required to bundle the lambda function - '@aws-cdk/aws-cognito-identitypool-alpha@2.152.0-alpha.0', // using alpha cognito idp resource for auth config + '@aws-cdk/aws-cognito-identitypool-alpha@2.158.0-alpha.0', // using alpha cognito idp resource for auth config '@aws-crypto/sha256-js', // All remaining deps are required for the lambda to sign the request to appsync '@aws-sdk/credential-provider-node', '@aws-sdk/protocol-http', diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/base-cdk.test.ts b/packages/amplify-graphql-api-construct-tests/src/__tests__/base-cdk.test.ts index cc3ffdf06a..c206678415 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/base-cdk.test.ts +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/base-cdk.test.ts @@ -30,7 +30,7 @@ describe('CDK GraphQL Transformer', () => { deleteProjectDir(projRoot); }); - ['2.152.0', 'latest'].forEach((cdkVersion) => { + ['2.158.0', 'latest'].forEach((cdkVersion) => { test(`CDK base case - aws-cdk-lib@${cdkVersion}`, async () => { const templatePath = path.resolve(path.join(__dirname, 'backends', 'base-cdk')); const name = await initCDKProject(projRoot, templatePath, { cdkVersion }); diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/data-construct.test.ts b/packages/amplify-graphql-api-construct-tests/src/__tests__/data-construct.test.ts index 1cc196ccc4..b900210c8c 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/data-construct.test.ts +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/data-construct.test.ts @@ -25,7 +25,7 @@ describe('Data Construct', () => { deleteProjectDir(projRoot); }); - ['2.152.0', 'latest'].forEach((cdkVersion) => { + ['2.158.0', 'latest'].forEach((cdkVersion) => { test(`Data Construct - aws-cdk-lib@${cdkVersion}`, async () => { const templatePath = path.resolve(path.join(__dirname, 'backends', 'data-construct')); const name = await initCDKProject(projRoot, templatePath, { cdkVersion, construct: 'Data' }); diff --git a/packages/amplify-graphql-api-construct-tests/src/commands.ts b/packages/amplify-graphql-api-construct-tests/src/commands.ts index 062cc5b298..caaa1d4c7c 100644 --- a/packages/amplify-graphql-api-construct-tests/src/commands.ts +++ b/packages/amplify-graphql-api-construct-tests/src/commands.ts @@ -68,7 +68,7 @@ export type InitCDKProjectProps = { * @returns a promise which resolves to the stack name */ export const initCDKProject = async (cwd: string, templatePath: string, props?: InitCDKProjectProps): Promise => { - const { cdkVersion = '2.152.0', additionalDependencies = [] } = props ?? {}; + const { cdkVersion = '2.158.0', additionalDependencies = [] } = props ?? {}; await spawn(getNpxPath(), ['cdk', 'init', 'app', '--language', 'typescript'], { cwd, diff --git a/packages/amplify-graphql-api-construct/.jsii b/packages/amplify-graphql-api-construct/.jsii index 736fae8895..e8c89a0f8a 100644 --- a/packages/amplify-graphql-api-construct/.jsii +++ b/packages/amplify-graphql-api-construct/.jsii @@ -130,7 +130,7 @@ "zod": "^3.22.2" }, "dependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "dependencyClosure": { @@ -212,6 +212,36 @@ } } }, + "@aws-cdk/cloud-assembly-schema": { + "targets": { + "dotnet": { + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/main/logo/default-256-dark.png", + "namespace": "Amazon.CDK.CloudAssembly.Schema", + "packageId": "Amazon.CDK.CloudAssembly.Schema" + }, + "go": { + "moduleName": "github.com/cdklabs/cloud-assembly-schema-go" + }, + "java": { + "maven": { + "artifactId": "cdk-cloud-assembly-schema", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.cloudassembly.schema" + }, + "js": { + "npm": "@aws-cdk/cloud-assembly-schema" + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 2" + ], + "distName": "aws-cdk.cloud-assembly-schema", + "module": "aws_cdk.cloud_assembly_schema" + } + } + }, "aws-cdk-lib": { "submodules": { "aws-cdk-lib.alexa_ask": { @@ -2282,6 +2312,19 @@ } } }, + "aws-cdk-lib.aws_launchwizard": { + "targets": { + "dotnet": { + "package": "Amazon.CDK.AWS.LaunchWizard" + }, + "java": { + "package": "software.amazon.awscdk.services.launchwizard" + }, + "python": { + "module": "aws_cdk.aws_launchwizard" + } + } + }, "aws-cdk-lib.aws_lex": { "targets": { "dotnet": { @@ -2776,6 +2819,19 @@ } } }, + "aws-cdk-lib.aws_pcaconnectorscep": { + "targets": { + "dotnet": { + "package": "Amazon.CDK.AWS.PCAConnectorSCEP" + }, + "java": { + "package": "software.amazon.awscdk.services.pcaconnectorscep" + }, + "python": { + "module": "aws_cdk.aws_pcaconnectorscep" + } + } + }, "aws-cdk-lib.aws_personalize": { "targets": { "dotnet": { @@ -3491,6 +3547,19 @@ } } }, + "aws-cdk-lib.aws_ssmquicksetup": { + "targets": { + "dotnet": { + "package": "Amazon.CDK.AWS.SSMQuickSetup" + }, + "java": { + "package": "software.amazon.awscdk.services.ssmquicksetup" + }, + "python": { + "module": "aws_cdk.aws_ssmquicksetup" + } + } + }, "aws-cdk-lib.aws_sso": { "targets": { "dotnet": { @@ -3738,19 +3807,7 @@ } } }, - "aws-cdk-lib.cloud_assembly_schema": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CloudAssembly.Schema" - }, - "java": { - "package": "software.amazon.awscdk.cloudassembly.schema" - }, - "python": { - "module": "aws_cdk.cloud_assembly_schema" - } - } - }, + "aws-cdk-lib.cloud_assembly_schema": {}, "aws-cdk-lib.cloudformation_include": { "targets": { "dotnet": { @@ -8902,5 +8959,5 @@ } }, "version": "1.14.0", - "fingerprint": "izzdFUzhDEGlYfYipZndJkvmkwoDkGn6UZivsangA2g=" + "fingerprint": "u4jHwzwBKRAlcky7o7NnOthEdgdtKR1CtWBsGA5tXq4=" } \ No newline at end of file diff --git a/packages/amplify-graphql-api-construct/package.json b/packages/amplify-graphql-api-construct/package.json index e112399579..e8b51e2717 100644 --- a/packages/amplify-graphql-api-construct/package.json +++ b/packages/amplify-graphql-api-construct/package.json @@ -285,7 +285,7 @@ "@aws-amplify/graphql-transformer-test-utils": "1.0.3", "@types/fs-extra": "^8.0.1", "@types/node": "^18.0.0", - "aws-cdk-lib": "2.152.0", + "aws-cdk-lib": "2.158.0", "constructs": "10.3.0", "jsii": "^5.4.23", "jsii-docgen": "9.1.2", @@ -296,7 +296,7 @@ "ts-node": "^8.10.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "stability": "stable", diff --git a/packages/amplify-graphql-auth-transformer/package.json b/packages/amplify-graphql-auth-transformer/package.json index 3732b8619e..253e21ae8f 100644 --- a/packages/amplify-graphql-auth-transformer/package.json +++ b/packages/amplify-graphql-auth-transformer/package.json @@ -50,7 +50,7 @@ "@types/node": "^18.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "jest": { diff --git a/packages/amplify-graphql-conversation-transformer/package.json b/packages/amplify-graphql-conversation-transformer/package.json index 7651b88e5c..29c03d1359 100644 --- a/packages/amplify-graphql-conversation-transformer/package.json +++ b/packages/amplify-graphql-conversation-transformer/package.json @@ -40,7 +40,7 @@ "@aws-amplify/graphql-transformer-test-utils": "1.0.3" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "jest": { diff --git a/packages/amplify-graphql-function-transformer/package.json b/packages/amplify-graphql-function-transformer/package.json index d5989db8d2..10902b1dde 100644 --- a/packages/amplify-graphql-function-transformer/package.json +++ b/packages/amplify-graphql-function-transformer/package.json @@ -40,7 +40,7 @@ "@aws-amplify/graphql-transformer-test-utils": "1.0.3" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "jest": { diff --git a/packages/amplify-graphql-generation-transformer/package.json b/packages/amplify-graphql-generation-transformer/package.json index 4f473b8bc1..acd0e60933 100644 --- a/packages/amplify-graphql-generation-transformer/package.json +++ b/packages/amplify-graphql-generation-transformer/package.json @@ -36,7 +36,7 @@ "@aws-amplify/graphql-transformer-test-utils": "1.0.3" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "jest": { diff --git a/packages/amplify-graphql-http-transformer/package.json b/packages/amplify-graphql-http-transformer/package.json index c80a75577c..3faf4ca491 100644 --- a/packages/amplify-graphql-http-transformer/package.json +++ b/packages/amplify-graphql-http-transformer/package.json @@ -40,7 +40,7 @@ "@aws-amplify/graphql-transformer-test-utils": "1.0.3" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "jest": { diff --git a/packages/amplify-graphql-index-transformer/package.json b/packages/amplify-graphql-index-transformer/package.json index 4657fd05de..c798198f21 100644 --- a/packages/amplify-graphql-index-transformer/package.json +++ b/packages/amplify-graphql-index-transformer/package.json @@ -41,7 +41,7 @@ "@aws-amplify/graphql-transformer-test-utils": "1.0.3" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "jest": { diff --git a/packages/amplify-graphql-model-transformer/package.json b/packages/amplify-graphql-model-transformer/package.json index 14c7a07bd8..bc14a8414f 100644 --- a/packages/amplify-graphql-model-transformer/package.json +++ b/packages/amplify-graphql-model-transformer/package.json @@ -41,7 +41,7 @@ "graphql-transformer-common": "5.0.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "devDependencies": { diff --git a/packages/amplify-graphql-name-mapping-transformer/package.json b/packages/amplify-graphql-name-mapping-transformer/package.json index 40a5f0cc28..aef585bf61 100644 --- a/packages/amplify-graphql-name-mapping-transformer/package.json +++ b/packages/amplify-graphql-name-mapping-transformer/package.json @@ -38,7 +38,7 @@ "graphql-transformer-common": "5.0.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "devDependencies": { diff --git a/packages/amplify-graphql-predictions-transformer/package.json b/packages/amplify-graphql-predictions-transformer/package.json index 505549b9a2..3e4eda2d11 100644 --- a/packages/amplify-graphql-predictions-transformer/package.json +++ b/packages/amplify-graphql-predictions-transformer/package.json @@ -37,7 +37,7 @@ "graphql-transformer-common": "5.0.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "devDependencies": { diff --git a/packages/amplify-graphql-relational-transformer/package.json b/packages/amplify-graphql-relational-transformer/package.json index 1ac43398b5..2dc8415806 100644 --- a/packages/amplify-graphql-relational-transformer/package.json +++ b/packages/amplify-graphql-relational-transformer/package.json @@ -43,7 +43,7 @@ "@aws-amplify/graphql-transformer-test-utils": "1.0.3" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "jest": { diff --git a/packages/amplify-graphql-searchable-transformer/package.json b/packages/amplify-graphql-searchable-transformer/package.json index 37a7c8ed1a..3b607feebf 100644 --- a/packages/amplify-graphql-searchable-transformer/package.json +++ b/packages/amplify-graphql-searchable-transformer/package.json @@ -38,7 +38,7 @@ "graphql-transformer-common": "5.0.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "devDependencies": { diff --git a/packages/amplify-graphql-sql-transformer/package.json b/packages/amplify-graphql-sql-transformer/package.json index 9a9bb3ef59..66f1a5eea1 100644 --- a/packages/amplify-graphql-sql-transformer/package.json +++ b/packages/amplify-graphql-sql-transformer/package.json @@ -41,7 +41,7 @@ "@aws-amplify/graphql-transformer-test-utils": "1.0.3" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "jest": { diff --git a/packages/amplify-graphql-transformer-core/package.json b/packages/amplify-graphql-transformer-core/package.json index b097066dd9..ac6e110be7 100644 --- a/packages/amplify-graphql-transformer-core/package.json +++ b/packages/amplify-graphql-transformer-core/package.json @@ -42,7 +42,7 @@ "ts-dedent": "^2.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "devDependencies": { diff --git a/packages/amplify-graphql-transformer-interfaces/package.json b/packages/amplify-graphql-transformer-interfaces/package.json index ac8f14205e..e3b3cd97c5 100644 --- a/packages/amplify-graphql-transformer-interfaces/package.json +++ b/packages/amplify-graphql-transformer-interfaces/package.json @@ -31,7 +31,7 @@ "graphql": "^15.5.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "jest": { diff --git a/packages/amplify-graphql-transformer-test-utils/package.json b/packages/amplify-graphql-transformer-test-utils/package.json index 75e62d27f3..ab8bb29160 100644 --- a/packages/amplify-graphql-transformer-test-utils/package.json +++ b/packages/amplify-graphql-transformer-test-utils/package.json @@ -39,7 +39,7 @@ "ts-jest": "^29.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "jest": { diff --git a/packages/amplify-graphql-transformer/package.json b/packages/amplify-graphql-transformer/package.json index d6bf346ceb..8505fc9b34 100644 --- a/packages/amplify-graphql-transformer/package.json +++ b/packages/amplify-graphql-transformer/package.json @@ -53,7 +53,7 @@ "ts-jest": "^29.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.152.0", + "aws-cdk-lib": "^2.158.0", "constructs": "^10.3.0" }, "jest": { diff --git a/packages/graphql-transformers-e2e-tests/resources/jsonServer/package.json b/packages/graphql-transformers-e2e-tests/resources/jsonServer/package.json index 6ecd712c57..fd02cc444c 100644 --- a/packages/graphql-transformers-e2e-tests/resources/jsonServer/package.json +++ b/packages/graphql-transformers-e2e-tests/resources/jsonServer/package.json @@ -3,12 +3,12 @@ "private": true, "version": "1.0.0", "devDependencies": { - "aws-cdk": "~2.152.0", + "aws-cdk": "~2.158.0", "@types/node": "*", "typescript": "*" }, "dependencies": { - "aws-cdk-lib": "~2.152.0", + "aws-cdk-lib": "~2.158.0", "constructs": "^10.3.0" } } diff --git a/scripts/package.json b/scripts/package.json index ed86fcbb02..46f978c349 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -9,21 +9,21 @@ "author": "", "license": "ISC", "dependencies": { + "aws-sdk": "2.1414.0", "axios": "^1.6.0", + "cli-progress": "^3.12.0", "execa": "^5.1.1", "fs-extra": "^10.0.0", "glob": "^10.3.0", "js-yaml": "^4.1.0", "ts-node": "^10.4.0", - "typescript": "^4.4.4", - "aws-sdk": "2.1414.0", - "cli-progress": "3.12.0" + "typescript": "^4.4.4" }, "devDependencies": { + "@types/cli-progress": "3.11.0", "@types/fs-extra": "^9.0.13", "@types/glob": "^7.2.0", "@types/js-yaml": "^4.0.4", - "@types/node": "^18.0.0", - "@types/cli-progress": "3.11.0" + "@types/node": "^18.0.0" } } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index d78bd8513c..978bfeb57f 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -101,10 +101,12 @@ resolved "https://registry.npmjs.org/@types/node/-/node-20.2.1.tgz#de559d4b33be9a808fd43372ccee822c70f39704" integrity sha512-DqJociPbZP1lbZ5SQPk4oag6W7AyaGMO6gSfRwq3PWl4PXTwJpRQJhDq4W0kzrg3w6tJ1SwlvGZ5uKFHY13LIg== -"@types/node@^16.11.5": - version "16.18.31" - resolved "https://registry.npmjs.org/@types/node/-/node-16.18.31.tgz#7de39c2b9363f0d95b129cc969fcbf98e870251c" - integrity sha512-KPXltf4z4g517OlVJO9XQ2357CYw7fvuJ3ZuBynjXC5Jos9i+K7LvFb7bUIwtJXSZj0vTp9Q6NJBSQpkwwO8Zw== +"@types/node@^18.0.0": + version "18.19.54" + resolved "https://registry.npmjs.org/@types/node/-/node-18.19.54.tgz#f1048dc083f81b242640f04f18fb3e4ccf13fcdb" + integrity sha512-+BRgt0G5gYjTvdLac9sIeE0iZcJxi4Jc4PV5EUzqi+88jmQLr+fRZdv2tCTV7IHKSGxM6SaLoOXQWWUiLUItMw== + dependencies: + undici-types "~5.26.4" acorn-walk@^8.1.1: version "8.2.0" @@ -217,7 +219,7 @@ call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -cli-progress@3.12.0: +cli-progress@^3.12.0: version "3.12.0" resolved "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" integrity sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A== @@ -626,16 +628,7 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -703,6 +696,11 @@ typescript@^4.4.4: resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" diff --git a/shared-scripts.sh b/shared-scripts.sh index 7c359819ea..54312513a6 100755 --- a/shared-scripts.sh +++ b/shared-scripts.sh @@ -40,11 +40,13 @@ function storeCacheForBuildJob { # upload [repo, .cache] to s3 storeCache $CODEBUILD_SRC_DIR repo storeCache $HOME/.cache .cache + storeCache $HOME/.nvm .nvm } function loadCacheFromBuildJob { # download [repo, .cache] from s3 loadCache repo $CODEBUILD_SRC_DIR loadCache .cache $HOME/.cache + loadCache .nvm $HOME/.nvm } function storeCacheFile { localFilePath="$1" @@ -80,8 +82,10 @@ function _setShell { echo "Setting Shell" yarn config set script-shell $(which bash) } + function _buildLinux { _setShell + _setupNodeVersion 18.20.4 echo "Linux Build" node --version yarn run production-build @@ -92,6 +96,7 @@ function _buildLinux { # used when build is not necessary for codebuild project function _installLinux { _setShell + _setupNodeVersion 18.20.4 echo "Linux Install" yarn run production-install storeCacheForBuildJob @@ -330,6 +335,10 @@ function _runCDKTestsLinux { function _runGqlE2ETests { echo "RUN GraphQL E2E tests" + + # Set Node.js version to 18.20.4 as one of the package requires version ">= 18.18.0" + _setupNodeVersion 18.20.4 + loadCacheFromBuildJob _loadTestAccountCredentials retry runGraphQLE2eTest diff --git a/yarn.lock b/yarn.lock index d2f12b7977..a610c02ed5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -454,7 +454,7 @@ resolved "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz#d8e20b5f5dc20128ea2000dc479ca3c7ddc27248" integrity sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg== -"@aws-cdk/asset-node-proxy-agent-v6@^2.0.3": +"@aws-cdk/asset-node-proxy-agent-v6@^2.1.0": version "2.1.0" resolved "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz#6d3c7860354d4856a7e75375f2f0ecab313b4989" integrity sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A== @@ -464,6 +464,14 @@ resolved "https://registry.npmjs.org/@aws-cdk/aws-cognito-identitypool-alpha/-/aws-cognito-identitypool-alpha-2.152.0-alpha.0.tgz#5d29345675171afd1eaa594da59561b38ff0ea9d" integrity sha512-6KbF4jog139kAARMBNDbbGQYSon+ggG4peQgv3hDFH49l6r6w+SbrW+jFzgfTGhQLDrlQEB4rPh7EW64xxtfVg== +"@aws-cdk/cloud-assembly-schema@^36.0.24": + version "36.0.25" + resolved "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-36.0.25.tgz#3e98d99d109636e4b65cee7684b4d41bffef13cd" + integrity sha512-AK86v4IMV4zcWfp392e3wlaVJPT72/dk39Lo2SDDFxQR+sikMOyY2IGrULyhK1TwQmPiyxM7QB/0MkTbMDAPrw== + dependencies: + jsonschema "^1.4.1" + semver "^7.6.3" + "@aws-crypto/crc32@2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-2.0.0.tgz#4ad432a3c03ec3087c5540ff6e41e6565d2dc153" @@ -8265,23 +8273,24 @@ aws-appsync@^4.1.1: url "^0.11.0" uuid "3.x" -aws-cdk-lib@2.152.0, aws-cdk-lib@~2.129.0, aws-cdk-lib@~2.152.0: - version "2.152.0" - resolved "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.152.0.tgz#f34eb97e61b46fe8c2b0b5f0b9dc244e21fee36c" - integrity sha512-w8Dn92xYCUWSRL9ncoXzpGzyDAHbljnrhXFm/pTR1YZTxdbLHvIOdh7D9b9qhtghUnq2i522I6/z2NJggPcNWQ== +aws-cdk-lib@2.158.0, aws-cdk-lib@~2.129.0, aws-cdk-lib@~2.158.0: + version "2.158.0" + resolved "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.158.0.tgz#f39a3ee7afdd16986cdd9811ff4e1a593b931304" + integrity sha512-Pl9CCLM+XRTy6nyyRJM1INEMtwIlZOib0FWyq9i9E388vurw7sNVJ6tAsfLpGIOLHsFQCbF4f6OZ0KSVxmMaiA== dependencies: "@aws-cdk/asset-awscli-v1" "^2.2.202" "@aws-cdk/asset-kubectl-v20" "^2.1.2" - "@aws-cdk/asset-node-proxy-agent-v6" "^2.0.3" + "@aws-cdk/asset-node-proxy-agent-v6" "^2.1.0" + "@aws-cdk/cloud-assembly-schema" "^36.0.24" "@balena/dockerignore" "^1.0.2" case "1.6.3" fs-extra "^11.2.0" - ignore "^5.3.1" + ignore "^5.3.2" jsonschema "^1.4.1" mime-types "^2.1.35" minimatch "^3.1.2" punycode "^2.3.1" - semver "^7.6.2" + semver "^7.6.3" table "^6.8.2" yaml "1.10.2" @@ -11538,7 +11547,7 @@ ignore-walk@^6.0.0: dependencies: minimatch "^9.0.0" -ignore@^5.0.4, ignore@^5.2.0, ignore@^5.3.1: +ignore@^5.0.4, ignore@^5.2.0, ignore@^5.3.2: version "5.3.2" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -15693,7 +15702,7 @@ semver-utils@^1.1.4: resolved "https://registry.npmjs.org/semver-utils/-/semver-utils-1.1.4.tgz#cf0405e669a57488913909fc1c3f29bf2a4871e2" integrity sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA== -"semver@2 || 3 || 4 || 5", semver@7.3.7, semver@7.5.4, semver@^5.5.0, semver@^5.6.0, semver@^6.0.0, semver@^6.3.0, semver@^6.3.1, semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.2, semver@^7.6.3, semver@~7.5.4: +"semver@2 || 3 || 4 || 5", semver@7.3.7, semver@7.5.4, semver@^5.5.0, semver@^5.6.0, semver@^6.0.0, semver@^6.3.0, semver@^6.3.1, semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3, semver@~7.5.4: version "7.6.3" resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== From e98c56188d4c1a57041c02d2b3f644057e3ecb93 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Mon, 7 Oct 2024 11:33:20 -0600 Subject: [PATCH 06/15] test: fix gen 1 init (#2924) --- .../src/__tests__/utils/amplifyCLI.ts | 4 ++++ .../src/init/initProjectHelper.ts | 16 ++++++++++++++++ .../src/init-special-cases/index.ts | 4 ++++ 3 files changed, 24 insertions(+) diff --git a/client-test-apps/js/api-model-relationship-app/src/__tests__/utils/amplifyCLI.ts b/client-test-apps/js/api-model-relationship-app/src/__tests__/utils/amplifyCLI.ts index 767d90a70e..6124aeab1d 100644 --- a/client-test-apps/js/api-model-relationship-app/src/__tests__/utils/amplifyCLI.ts +++ b/client-test-apps/js/api-model-relationship-app/src/__tests__/utils/amplifyCLI.ts @@ -273,6 +273,10 @@ export class AmplifyCLI { console.log(`Using CLI path '${cliPath}'`); console.log(`Project root: '${this.projectRoot}'`); const chain = spawn(cliPath, cliArgs, { cwd: this.projectRoot, env, disableCIDetection: s.disableCIDetection }) + .wait('Do you want to continue with Amplify Gen 1?') + .sendConfirmYes() + .wait('Why would you like to use Amplify Gen 1?') + .sendCarriageReturn() .wait('Enter a name for the project') .sendLine(s.name) .wait('Initialize the project with the above configuration?') diff --git a/packages/amplify-e2e-core/src/init/initProjectHelper.ts b/packages/amplify-e2e-core/src/init/initProjectHelper.ts index a5bc8afc46..85e899e377 100644 --- a/packages/amplify-e2e-core/src/init/initProjectHelper.ts +++ b/packages/amplify-e2e-core/src/init/initProjectHelper.ts @@ -56,6 +56,10 @@ export function initJSProjectWithProfile(cwd: string, settings?: Partial { const chain = spawn(getCLIPath(), ['init'], { cwd, stripColors: true }) + .wait('Do you want to continue with Amplify Gen 1?') + .sendConfirmYes() + .wait('Why would you like to use Amplify Gen 1?') + .sendCarriageReturn() .wait('Enter a name for the project') .sendLine(s.name) .wait('Initialize the project with the above configuration?') @@ -250,6 +262,10 @@ export function initProjectWithAccessKey( CLI_DEV_INTERNAL_DISABLE_AMPLIFY_APP_CREATION: '1', }, }) + .wait('Do you want to continue with Amplify Gen 1?') + .sendConfirmYes() + .wait('Why would you like to use Amplify Gen 1?') + .sendCarriageReturn() .wait('Enter a name for the project') .sendLine(s.name) .wait('Initialize the project with the above configuration?') diff --git a/packages/amplify-e2e-tests/src/init-special-cases/index.ts b/packages/amplify-e2e-tests/src/init-special-cases/index.ts index d2490ad33f..d31e60b710 100644 --- a/packages/amplify-e2e-tests/src/init-special-cases/index.ts +++ b/packages/amplify-e2e-tests/src/init-special-cases/index.ts @@ -51,6 +51,10 @@ async function initWorkflow(cwd: string, settings: { accessKeyId: string; secret CLI_DEV_INTERNAL_DISABLE_AMPLIFY_APP_CREATION: '1', }, }) + .wait('Do you want to continue with Amplify Gen 1?') + .sendConfirmYes() + .wait('Why would you like to use Amplify Gen 1?') + .sendCarriageReturn() .wait('Enter a name for the project') .sendCarriageReturn() .wait('Initialize the project with the above configuration?') From 713e2b6d52fd17ba29968934e416604564a0132f Mon Sep 17 00:00:00 2001 From: Ian Saultz <52051793+atierian@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:22:28 -0400 Subject: [PATCH 07/15] fix(conversation): allow changes to systemPrompt, inferenceConfig, aiModel to be hotswapped (#2923) --- ...phql-conversation-transformer.test.ts.snap | 500 +++++++++++++++--- ...y-graphql-conversation-transformer.test.ts | 18 +- .../resolvers/init-resolver-fn.template.js | 1 + .../src/resolvers/init-resolver.ts | 19 +- .../invoke-lambda-resolver-fn.template.js | 3 +- .../src/resolvers/invoke-lambda-resolver.ts | 13 +- .../conversation-resolver-generator.ts | 6 +- 7 files changed, 469 insertions(+), 91 deletions(-) diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap b/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap index 7fe1b58ce2..3747205910 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap @@ -43,24 +43,118 @@ export const response = (ctx) => { `; exports[`ConversationTransformer valid schemas should transform conversation route with inference configuration 2`] = ` -{ - "Fn::Join": [ - "", - [ - "import { util } from '@aws-appsync/utils'; +"export function request(ctx) { + ctx.stash.hasAuth = true; + const isAuthorized = false; + + if (util.authType() === 'User Pool Authorization') { + if (!isAuthorized) { + const authFilter = []; + let ownerClaim0 = ctx.identity['claims']['sub']; + ctx.args.owner = ownerClaim0; + const currentClaim1 = ctx.identity['claims']['username'] ?? ctx.identity['claims']['cognito:username']; + if (ownerClaim0 && currentClaim1) { + ownerClaim0 = ownerClaim0 + '::' + currentClaim1; + authFilter.push({ owner: { eq: ownerClaim0 } }); + } + const role0_0 = ctx.identity['claims']['sub']; + if (role0_0) { + authFilter.push({ owner: { eq: role0_0 } }); + } + // we can just reuse currentClaim1 here, but doing this (for now) to mirror the existing + // vtl auth resolver. + const role0_1 = ctx.identity['claims']['username'] ?? ctx.identity['claims']['cognito:username']; + if (role0_1) { + authFilter.push({ owner: { eq: role0_1 } }); + } + if (authFilter.length !== 0) { + ctx.stash.authFilter = { or: authFilter }; + } + } + } + if (!isAuthorized && ctx.stash.authFilter.length === 0) { + util.unauthorized(); + } + return { version: '2018-05-29', payload: {} }; +} + +export function response(ctx) { + return {}; +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with inference configuration 3`] = ` +"export function request(ctx) { + const { authFilter } = ctx.stash; + + const query = { + expression: 'id = :id', + expressionValues: util.dynamodb.toMapValues({ + ':id': ctx.args.conversationId, + }), + }; + + const filter = JSON.parse(util.transform.toDynamoDBFilterExpression(authFilter)); + + return { + operation: 'Query', + query, + filter, + }; +} + +export function response(ctx) { + if (ctx.error) { + util.error(ctx.error.message, ctx.error.type); + } + + if (ctx.result.items.length !== 0) { + return ctx.result.items[0]; + } + + util.error('Conversation not found', 'ResourceNotFound'); +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with inference configuration 4`] = ` +"import { util } from '@aws-appsync/utils'; +import * as ddb from '@aws-appsync/utils/dynamodb'; + +export function request(ctx) { + const args = ctx.stash.transformedArgs ?? ctx.args; + const defaultValues = ctx.stash.defaultValues ?? {}; + const message = { + __typename: 'ConversationMessagepirateChat', + role: 'user', + ...args, + ...defaultValues, + }; + const id = ctx.stash.defaultValues.id; + + return ddb.put({ key: { id }, item: message }); +} + +export function response(ctx) { + if (ctx.error) { + util.error(ctx.error.message, ctx.error.type); + } else { + return ctx.result; + } +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with inference configuration 5`] = ` +"import { util } from '@aws-appsync/utils'; export function request(ctx) { const { args, request, prev } = ctx; + const { graphqlApiEndpoint } = ctx.stash; + const selectionSet = 'id conversationId content { image { format source { bytes }} text toolUse { toolUseId name input } toolResult { status toolUseId content { json text image { format source { bytes }} document { format name source { bytes }} }}} role owner createdAt updatedAt'; - const graphqlApiEndpoint = '", - { - "Fn::GetAtt": [ - "GraphQLAPI", - "GraphQLUrl", - ], - }, - "'; const messages = prev.result.items; const responseMutation = { @@ -116,10 +210,7 @@ export function response(ctx) { }; return response; } -", - ], - ], -} +" `; exports[`ConversationTransformer valid schemas should transform conversation route with model query tool 1`] = ` @@ -165,24 +256,118 @@ export const response = (ctx) => { `; exports[`ConversationTransformer valid schemas should transform conversation route with model query tool 2`] = ` -{ - "Fn::Join": [ - "", - [ - "import { util } from '@aws-appsync/utils'; +"export function request(ctx) { + ctx.stash.hasAuth = true; + const isAuthorized = false; + + if (util.authType() === 'User Pool Authorization') { + if (!isAuthorized) { + const authFilter = []; + let ownerClaim0 = ctx.identity['claims']['sub']; + ctx.args.owner = ownerClaim0; + const currentClaim1 = ctx.identity['claims']['username'] ?? ctx.identity['claims']['cognito:username']; + if (ownerClaim0 && currentClaim1) { + ownerClaim0 = ownerClaim0 + '::' + currentClaim1; + authFilter.push({ owner: { eq: ownerClaim0 } }); + } + const role0_0 = ctx.identity['claims']['sub']; + if (role0_0) { + authFilter.push({ owner: { eq: role0_0 } }); + } + // we can just reuse currentClaim1 here, but doing this (for now) to mirror the existing + // vtl auth resolver. + const role0_1 = ctx.identity['claims']['username'] ?? ctx.identity['claims']['cognito:username']; + if (role0_1) { + authFilter.push({ owner: { eq: role0_1 } }); + } + if (authFilter.length !== 0) { + ctx.stash.authFilter = { or: authFilter }; + } + } + } + if (!isAuthorized && ctx.stash.authFilter.length === 0) { + util.unauthorized(); + } + return { version: '2018-05-29', payload: {} }; +} + +export function response(ctx) { + return {}; +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with model query tool 3`] = ` +"export function request(ctx) { + const { authFilter } = ctx.stash; + + const query = { + expression: 'id = :id', + expressionValues: util.dynamodb.toMapValues({ + ':id': ctx.args.conversationId, + }), + }; + + const filter = JSON.parse(util.transform.toDynamoDBFilterExpression(authFilter)); + + return { + operation: 'Query', + query, + filter, + }; +} + +export function response(ctx) { + if (ctx.error) { + util.error(ctx.error.message, ctx.error.type); + } + + if (ctx.result.items.length !== 0) { + return ctx.result.items[0]; + } + + util.error('Conversation not found', 'ResourceNotFound'); +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with model query tool 4`] = ` +"import { util } from '@aws-appsync/utils'; +import * as ddb from '@aws-appsync/utils/dynamodb'; + +export function request(ctx) { + const args = ctx.stash.transformedArgs ?? ctx.args; + const defaultValues = ctx.stash.defaultValues ?? {}; + const message = { + __typename: 'ConversationMessagepirateChat', + role: 'user', + ...args, + ...defaultValues, + }; + const id = ctx.stash.defaultValues.id; + + return ddb.put({ key: { id }, item: message }); +} + +export function response(ctx) { + if (ctx.error) { + util.error(ctx.error.message, ctx.error.type); + } else { + return ctx.result; + } +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with model query tool 5`] = ` +"import { util } from '@aws-appsync/utils'; export function request(ctx) { const { args, request, prev } = ctx; + const { graphqlApiEndpoint } = ctx.stash; + const toolDefinitions = {"tools":[{"name":"listTodos","description":"lists todos","inputSchema":{"json":{"type":"object","properties":{},"required":[]}},"graphqlRequestInputDescriptor":{"selectionSet":"items { content isDone id createdAt updatedAt owner } nextToken","propertyTypes":{},"queryName":"listTodos"}}]}; const selectionSet = 'id conversationId content { image { format source { bytes }} text toolUse { toolUseId name input } toolResult { status toolUseId content { json text image { format source { bytes }} document { format name source { bytes }} }}} role owner createdAt updatedAt'; - const graphqlApiEndpoint = '", - { - "Fn::GetAtt": [ - "GraphQLAPI", - "GraphQLUrl", - ], - }, - "'; const messages = prev.result.items; const responseMutation = { @@ -240,10 +425,7 @@ export function response(ctx) { }; return response; } -", - ], - ], -} +" `; exports[`ConversationTransformer valid schemas should transform conversation route with model query tool including relationships 1`] = ` @@ -289,24 +471,118 @@ export const response = (ctx) => { `; exports[`ConversationTransformer valid schemas should transform conversation route with model query tool including relationships 2`] = ` -{ - "Fn::Join": [ - "", - [ - "import { util } from '@aws-appsync/utils'; +"export function request(ctx) { + ctx.stash.hasAuth = true; + const isAuthorized = false; + + if (util.authType() === 'User Pool Authorization') { + if (!isAuthorized) { + const authFilter = []; + let ownerClaim0 = ctx.identity['claims']['sub']; + ctx.args.owner = ownerClaim0; + const currentClaim1 = ctx.identity['claims']['username'] ?? ctx.identity['claims']['cognito:username']; + if (ownerClaim0 && currentClaim1) { + ownerClaim0 = ownerClaim0 + '::' + currentClaim1; + authFilter.push({ owner: { eq: ownerClaim0 } }); + } + const role0_0 = ctx.identity['claims']['sub']; + if (role0_0) { + authFilter.push({ owner: { eq: role0_0 } }); + } + // we can just reuse currentClaim1 here, but doing this (for now) to mirror the existing + // vtl auth resolver. + const role0_1 = ctx.identity['claims']['username'] ?? ctx.identity['claims']['cognito:username']; + if (role0_1) { + authFilter.push({ owner: { eq: role0_1 } }); + } + if (authFilter.length !== 0) { + ctx.stash.authFilter = { or: authFilter }; + } + } + } + if (!isAuthorized && ctx.stash.authFilter.length === 0) { + util.unauthorized(); + } + return { version: '2018-05-29', payload: {} }; +} + +export function response(ctx) { + return {}; +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with model query tool including relationships 3`] = ` +"export function request(ctx) { + const { authFilter } = ctx.stash; + + const query = { + expression: 'id = :id', + expressionValues: util.dynamodb.toMapValues({ + ':id': ctx.args.conversationId, + }), + }; + + const filter = JSON.parse(util.transform.toDynamoDBFilterExpression(authFilter)); + + return { + operation: 'Query', + query, + filter, + }; +} + +export function response(ctx) { + if (ctx.error) { + util.error(ctx.error.message, ctx.error.type); + } + + if (ctx.result.items.length !== 0) { + return ctx.result.items[0]; + } + + util.error('Conversation not found', 'ResourceNotFound'); +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with model query tool including relationships 4`] = ` +"import { util } from '@aws-appsync/utils'; +import * as ddb from '@aws-appsync/utils/dynamodb'; + +export function request(ctx) { + const args = ctx.stash.transformedArgs ?? ctx.args; + const defaultValues = ctx.stash.defaultValues ?? {}; + const message = { + __typename: 'ConversationMessagepirateChat', + role: 'user', + ...args, + ...defaultValues, + }; + const id = ctx.stash.defaultValues.id; + + return ddb.put({ key: { id }, item: message }); +} + +export function response(ctx) { + if (ctx.error) { + util.error(ctx.error.message, ctx.error.type); + } else { + return ctx.result; + } +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with model query tool including relationships 5`] = ` +"import { util } from '@aws-appsync/utils'; export function request(ctx) { const { args, request, prev } = ctx; + const { graphqlApiEndpoint } = ctx.stash; + const toolDefinitions = {"tools":[{"name":"listCustomers","description":"Provides data about the customer sending a message","inputSchema":{"json":{"type":"object","properties":{},"required":[]}},"graphqlRequestInputDescriptor":{"selectionSet":"items { name email activeCart { products { name price } customerId id createdAt updatedAt owner } orderHistory { items { products { name price } customerId id createdAt updatedAt owner } nextToken } id createdAt updatedAt owner } nextToken","propertyTypes":{},"queryName":"listCustomers"}}]}; const selectionSet = 'id conversationId content { image { format source { bytes }} text toolUse { toolUseId name input } toolResult { status toolUseId content { json text image { format source { bytes }} document { format name source { bytes }} }}} role owner createdAt updatedAt'; - const graphqlApiEndpoint = '", - { - "Fn::GetAtt": [ - "GraphQLAPI", - "GraphQLUrl", - ], - }, - "'; const messages = prev.result.items; const responseMutation = { @@ -364,10 +640,7 @@ export function response(ctx) { }; return response; } -", - ], - ], -} +" `; exports[`ConversationTransformer valid schemas should transform conversation route with query tools 1`] = ` @@ -413,24 +686,118 @@ export const response = (ctx) => { `; exports[`ConversationTransformer valid schemas should transform conversation route with query tools 2`] = ` -{ - "Fn::Join": [ - "", - [ - "import { util } from '@aws-appsync/utils'; +"export function request(ctx) { + ctx.stash.hasAuth = true; + const isAuthorized = false; + + if (util.authType() === 'User Pool Authorization') { + if (!isAuthorized) { + const authFilter = []; + let ownerClaim0 = ctx.identity['claims']['sub']; + ctx.args.owner = ownerClaim0; + const currentClaim1 = ctx.identity['claims']['username'] ?? ctx.identity['claims']['cognito:username']; + if (ownerClaim0 && currentClaim1) { + ownerClaim0 = ownerClaim0 + '::' + currentClaim1; + authFilter.push({ owner: { eq: ownerClaim0 } }); + } + const role0_0 = ctx.identity['claims']['sub']; + if (role0_0) { + authFilter.push({ owner: { eq: role0_0 } }); + } + // we can just reuse currentClaim1 here, but doing this (for now) to mirror the existing + // vtl auth resolver. + const role0_1 = ctx.identity['claims']['username'] ?? ctx.identity['claims']['cognito:username']; + if (role0_1) { + authFilter.push({ owner: { eq: role0_1 } }); + } + if (authFilter.length !== 0) { + ctx.stash.authFilter = { or: authFilter }; + } + } + } + if (!isAuthorized && ctx.stash.authFilter.length === 0) { + util.unauthorized(); + } + return { version: '2018-05-29', payload: {} }; +} + +export function response(ctx) { + return {}; +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with query tools 3`] = ` +"export function request(ctx) { + const { authFilter } = ctx.stash; + + const query = { + expression: 'id = :id', + expressionValues: util.dynamodb.toMapValues({ + ':id': ctx.args.conversationId, + }), + }; + + const filter = JSON.parse(util.transform.toDynamoDBFilterExpression(authFilter)); + + return { + operation: 'Query', + query, + filter, + }; +} + +export function response(ctx) { + if (ctx.error) { + util.error(ctx.error.message, ctx.error.type); + } + + if (ctx.result.items.length !== 0) { + return ctx.result.items[0]; + } + + util.error('Conversation not found', 'ResourceNotFound'); +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with query tools 4`] = ` +"import { util } from '@aws-appsync/utils'; +import * as ddb from '@aws-appsync/utils/dynamodb'; + +export function request(ctx) { + const args = ctx.stash.transformedArgs ?? ctx.args; + const defaultValues = ctx.stash.defaultValues ?? {}; + const message = { + __typename: 'ConversationMessagepirateChat', + role: 'user', + ...args, + ...defaultValues, + }; + const id = ctx.stash.defaultValues.id; + + return ddb.put({ key: { id }, item: message }); +} + +export function response(ctx) { + if (ctx.error) { + util.error(ctx.error.message, ctx.error.type); + } else { + return ctx.result; + } +} +" +`; + +exports[`ConversationTransformer valid schemas should transform conversation route with query tools 5`] = ` +"import { util } from '@aws-appsync/utils'; export function request(ctx) { const { args, request, prev } = ctx; + const { graphqlApiEndpoint } = ctx.stash; + const toolDefinitions = {"tools":[{"name":"getTemperature","description":"does a thing","inputSchema":{"json":{"type":"object","properties":{"city":{"type":"string","description":"A UTF-8 character sequence."}},"required":["city"]}},"graphqlRequestInputDescriptor":{"selectionSet":"value unit","propertyTypes":{"city":"String!"},"queryName":"getTemperature"}},{"name":"plus","description":"does a different thing","inputSchema":{"json":{"type":"object","properties":{"a":{"type":"number","description":"A signed 32-bit integer value."},"b":{"type":"number","description":"A signed 32-bit integer value."}},"required":[]}},"graphqlRequestInputDescriptor":{"selectionSet":"","propertyTypes":{"a":"Int","b":"Int"},"queryName":"plus"}}]}; const selectionSet = 'id conversationId content { image { format source { bytes }} text toolUse { toolUseId name input } toolResult { status toolUseId content { json text image { format source { bytes }} document { format name source { bytes }} }}} role owner createdAt updatedAt'; - const graphqlApiEndpoint = '", - { - "Fn::GetAtt": [ - "GraphQLAPI", - "GraphQLUrl", - ], - }, - "'; const messages = prev.result.items; const responseMutation = { @@ -488,8 +855,5 @@ export function response(ctx) { }; return response; } -", - ], - ], -} +" `; diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts b/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts index 1b20ccd17f..3ac99b9890 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts @@ -83,9 +83,21 @@ const assertResolverSnapshot = (routeName: string, resources: DeploymentResource expect(resolverCode).toBeDefined(); expect(resolverCode).toMatchSnapshot(); - const resolverFnCode = getResolverFnResource(routeName, resources); - expect(resolverFnCode).toBeDefined(); - expect(resolverFnCode).toMatchSnapshot(); + const authFn = resources?.resolvers[`Mutation.${routeName}.auth.js`]; + expect(authFn).toBeDefined(); + expect(authFn).toMatchSnapshot(); + + const verifySessionOwnerFn = resources?.resolvers[`Mutation.${routeName}.verify-session-owner.js`]; + expect(verifySessionOwnerFn).toBeDefined(); + expect(verifySessionOwnerFn).toMatchSnapshot(); + + const writeMessageToTableFn = resources?.resolvers[`Mutation.${routeName}.write-message-to-table.js`]; + expect(writeMessageToTableFn).toBeDefined(); + expect(writeMessageToTableFn).toMatchSnapshot(); + + const invokeLambdaFn = resources?.resolvers[`Mutation.${routeName}.invoke-lambda.js`]; + expect(invokeLambdaFn).toBeDefined(); + expect(invokeLambdaFn).toMatchSnapshot(); }; const getResolverResource = (mutationName: string, resources?: Record): Record => { diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/init-resolver-fn.template.js b/packages/amplify-graphql-conversation-transformer/src/resolvers/init-resolver-fn.template.js index e5064eac1c..e5aefdd038 100644 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/init-resolver-fn.template.js +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/init-resolver-fn.template.js @@ -1,4 +1,5 @@ export function request(ctx) { + ctx.stash.graphqlApiEndpoint = '[[GRAPHQL_API_ENDPOINT]]'; ctx.stash.defaultValues = ctx.stash.defaultValues ?? {}; ctx.stash.defaultValues.id = util.autoId(); const createdAt = util.time.nowISO8601(); diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/init-resolver.ts b/packages/amplify-graphql-conversation-transformer/src/resolvers/init-resolver.ts index 5683b01e24..b15e7e8b33 100644 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/init-resolver.ts +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/init-resolver.ts @@ -1,8 +1,7 @@ import { MappingTemplate } from '@aws-amplify/graphql-transformer-core'; -import { MappingTemplateProvider } from '@aws-amplify/graphql-transformer-interfaces'; +import { MappingTemplateProvider, TransformerContextProvider } from '@aws-amplify/graphql-transformer-interfaces'; import fs from 'fs'; import path from 'path'; -import { ConversationDirectiveConfiguration } from '../grapqhl-conversation-transformer'; /** * Creates and returns the mapping template for the init resolver. @@ -10,8 +9,16 @@ import { ConversationDirectiveConfiguration } from '../grapqhl-conversation-tran * * @returns {MappingTemplateProvider} An object containing request and response MappingTemplateProviders. */ -export const initMappingTemplate = (config: ConversationDirectiveConfiguration): MappingTemplateProvider => { - const resolver = fs.readFileSync(path.join(__dirname, 'init-resolver-fn.template.js'), 'utf8'); - const templateName = `Mutation.${config.field.name.value}.init.js`; - return MappingTemplate.s3MappingFunctionCodeFromString(resolver, templateName); +export const initMappingTemplate = (ctx: TransformerContextProvider): MappingTemplateProvider => { + const substitutions = { + GRAPHQL_API_ENDPOINT: ctx.api.graphqlUrl, + }; + + let resolver = fs.readFileSync(path.join(__dirname, 'init-resolver-fn.template.js'), 'utf8'); + Object.entries(substitutions).forEach(([key, value]) => { + const replaced = resolver.replace(new RegExp(`\\[\\[${key}\\]\\]`, 'g'), value); + resolver = replaced; + }); + + return MappingTemplate.inlineTemplateFromString(resolver); }; diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver-fn.template.js b/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver-fn.template.js index 988b2519d4..00c8c6e19c 100644 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver-fn.template.js +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver-fn.template.js @@ -2,9 +2,10 @@ import { util } from '@aws-appsync/utils'; export function request(ctx) { const { args, request, prev } = ctx; + const { graphqlApiEndpoint } = ctx.stash; + [[TOOL_DEFINITIONS_LINE]] const selectionSet = '[[SELECTION_SET]]'; - const graphqlApiEndpoint = '[[GRAPHQL_API_ENDPOINT]]'; const messages = prev.result.items; const responseMutation = { diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver.ts b/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver.ts index b869a4d69f..0fd3e9d16d 100644 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver.ts +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver.ts @@ -12,13 +12,9 @@ import dedent from 'ts-dedent'; * @param {TransformerContextProvider} ctx - The transformer context provider. * @returns {MappingTemplateProvider} An object containing request and response mapping functions. */ -export const invokeLambdaMappingTemplate = ( - config: ConversationDirectiveConfiguration, - ctx: TransformerContextProvider, -): MappingTemplateProvider => { +export const invokeLambdaMappingTemplate = (config: ConversationDirectiveConfiguration): MappingTemplateProvider => { const { TOOL_DEFINITIONS_LINE, TOOLS_CONFIGURATION_LINE } = generateToolLines(config); const SELECTION_SET = selectionSet; - const GRAPHQL_API_ENDPOINT = ctx.api.graphqlUrl; const MODEL_CONFIGURATION_LINE = generateModelConfigurationLine(config); const RESPONSE_MUTATION_NAME = config.responseMutationName; const RESPONSE_MUTATION_INPUT_TYPE_NAME = config.responseMutationInputTypeName; @@ -28,7 +24,6 @@ export const invokeLambdaMappingTemplate = ( TOOL_DEFINITIONS_LINE, TOOLS_CONFIGURATION_LINE, SELECTION_SET, - GRAPHQL_API_ENDPOINT, MODEL_CONFIGURATION_LINE, RESPONSE_MUTATION_NAME, RESPONSE_MUTATION_INPUT_TYPE_NAME, @@ -40,11 +35,9 @@ export const invokeLambdaMappingTemplate = ( const replaced = resolver.replace(new RegExp(`\\[\\[${key}\\]\\]`, 'g'), value); resolver = replaced; }); + const templateName = `Mutation.${config.field.name.value}.invoke-lambda.js`; - // This unfortunately needs to be an inline template because an s3 mapping template doesn't allow the CDK - // to substitute token values, which is necessary for this resolver function due to its reference of - // `ctx.api.graphqlUrl`. - return MappingTemplate.inlineTemplateFromString(resolver); + return MappingTemplate.s3MappingFunctionCodeFromString(resolver, templateName); }; const generateToolLines = (config: ConversationDirectiveConfiguration) => { diff --git a/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts b/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts index 5612ccf892..1d272523c3 100644 --- a/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts +++ b/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts @@ -51,7 +51,7 @@ export class ConversationResolverGenerator { this.createAssistantResponseSubscriptionResolver(ctx, directive, capitalizedFieldName); const functionDataSource = this.addLambdaDataSource(ctx, functionDataSourceId, referencedFunction, capitalizedFieldName); - const invokeLambdaFunction = invokeLambdaMappingTemplate(directive, ctx); + const invokeLambdaFunction = invokeLambdaMappingTemplate(directive); this.setupMessageTableIndex(ctx, directive); @@ -203,7 +203,7 @@ export class ConversationResolverGenerator { directive: ConversationDirectiveConfiguration, ): void { // Add init function - const initFunction = initMappingTemplate(directive); + const initFunction = initMappingTemplate(ctx); resolver.addJsFunctionToSlot('init', initFunction); // Add auth function @@ -218,7 +218,7 @@ export class ConversationResolverGenerator { resolver.addJsFunctionToSlot('verifySessionOwner', verifySessionOwnerFunction, conversationSessionDDBDataSource as any); // Add writeMessageToTable function - const writeMessageToTableFunction = writeMessageToTableMappingTemplate(capitalizedFieldName); + const writeMessageToTableFunction = writeMessageToTableMappingTemplate(directive.field.name.value); const messageModelName = `ConversationMessage${capitalizedFieldName}`; const messageModelDDBDataSourceName = getModelDataSourceNameForTypeName(ctx, messageModelName); const messageDDBDataSource = ctx.api.host.getDataSource(messageModelDDBDataSourceName); From 4e66ac3a9d276ca063c76a97aee791618b3c71ab Mon Sep 17 00:00:00 2001 From: "Peter V." <98245483+p5quared@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:27:36 -0700 Subject: [PATCH 08/15] feat: auto increment support (#2883) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(graphql-default-value-transformer): tidy tests * test(graphql-default-value-transformer): add unit tests for auto increment support * feat: 🎸 utils to detect Postgres datasource * feat: 🎸 support auto increment Implements support for auto increment (serial) fields from Postgres datasources. Such fields are denoted by an empty `@default` applied to an `Int` field. * test(graphql-default-value-transformer): pk can be auto increment * test(graphql-default-value-transformer): auto-increment crud e2e * chore: describe test purpose * chore: removing logging * chore: describe why invalid cases are invalid * chore: remove unecessary e2e test case * chore: test messaging clarity * chore: type safety * chore: alphabetize list * chore: type of return value asserts against string Co-authored-by: Tim Schmelter * chore: test ensures customers can insert to serial fields with custom values * chore: verify that @default(value) works on mysql * chore: remove unecessary ssm test case * chore: update branch from main * test: value cannot be null on ddb --------- Co-authored-by: Tim Schmelter --- codebuild_specs/e2e_workflow.yml | 136 +++--- .../__tests__/sql-pg-auto-increment.test.ts | 55 +++ .../schemas/sql-auto-increment/field-map.ts | 7 + .../schemas/sql-auto-increment/schema.graphql | 5 + .../schemas/sql-models/schema.graphql | 2 +- .../sql-models-auto-increment.ts | 174 +++++++ .../src/sql-tests-common/sql-models.ts | 28 ++ .../package.json | 3 +- ...hql-default-value-transformer.test.ts.snap | 438 ++++++++++++++++++ ...grapphql-default-value-transformer.test.ts | 296 ++++++------ .../src/graphql-default-value-transformer.ts | 26 +- .../__snapshots__/index.test.ts.snap | 2 +- .../src/directives/default.ts | 2 +- .../amplify-graphql-transformer-core/API.md | 6 + .../src/index.ts | 2 + .../utils/model-datasource-strategy-utils.ts | 21 + 16 files changed, 963 insertions(+), 240 deletions(-) create mode 100644 packages/amplify-graphql-api-construct-tests/src/__tests__/sql-pg-auto-increment.test.ts create mode 100644 packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-auto-increment/field-map.ts create mode 100644 packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-auto-increment/schema.graphql create mode 100644 packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models-auto-increment.ts diff --git a/codebuild_specs/e2e_workflow.yml b/codebuild_specs/e2e_workflow.yml index 1f15a19006..51453c0803 100644 --- a/codebuild_specs/e2e_workflow.yml +++ b/codebuild_specs/e2e_workflow.yml @@ -763,7 +763,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=14848' TEST_SUITE: >- src/__tests__/utils.test.ts|src/__tests__/ddb-iam-access.test.ts|src/__tests__/data-construct.test.ts|src/__tests__/custom-logic.test.ts|src/__tests__/amplify-table-5.test.ts - CLI_REGION: eu-south-1 + CLI_REGION: eu-west-1 depend-on: - publish_to_local_registry - identifier: >- @@ -775,7 +775,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=14848' TEST_SUITE: >- src/__tests__/add-resources.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/single-gsi-single-record.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/single-gsi-empty-table.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/single-gsi-1k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/single-gsi-10k-records.test.ts - CLI_REGION: ap-northeast-2 + CLI_REGION: ap-south-1 depend-on: - publish_to_local_registry - identifier: >- @@ -787,7 +787,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=14848' TEST_SUITE: >- src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-update-attr-single-record.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-update-attr-empty-table.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-update-attr-1k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-update-attr-10k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-single-record.test.ts - CLI_REGION: ap-south-1 + CLI_REGION: ap-southeast-1 depend-on: - publish_to_local_registry - identifier: >- @@ -799,7 +799,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=14848' TEST_SUITE: >- src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-empty-table.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-1k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/replace-2-gsis-10k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/3-gsis-single-record.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/3-gsis-empty-table.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 depend-on: - publish_to_local_registry - identifier: 3_gsis_1k_records_3_gsis_10k_records @@ -810,7 +810,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=14848' TEST_SUITE: >- src/__tests__/deploy-velocity-temporarily-disabled/3-gsis-1k-records.test.ts|src/__tests__/deploy-velocity-temporarily-disabled/3-gsis-10k-records.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: sql_pg_models @@ -834,6 +834,16 @@ batch: USE_PARENT_ACCOUNT: 1 depend-on: - publish_to_local_registry + - identifier: sql_pg_auto_increment + buildspec: codebuild_specs/run_cdk_tests.yml + env: + compute-type: BUILD_GENERAL1_MEDIUM + variables: + NODE_OPTIONS: '--max-old-space-size=6656' + TEST_SUITE: src/__tests__/sql-pg-auto-increment.test.ts + CLI_REGION: ap-southeast-2 + depend-on: + - publish_to_local_registry - identifier: sql_pg_array_objects buildspec: codebuild_specs/run_cdk_tests.yml env: @@ -841,7 +851,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/sql-pg-array-objects.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: sql_mysql_canary @@ -851,7 +861,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/sql-mysql-canary.test.ts - CLI_REGION: ca-central-1 + CLI_REGION: eu-central-1 depend-on: - publish_to_local_registry - identifier: sql_models_2 @@ -861,7 +871,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/sql-models-2.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: eu-north-1 depend-on: - publish_to_local_registry - identifier: sql_models_1 @@ -871,7 +881,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/sql-models-1.test.ts - CLI_REGION: eu-north-1 + CLI_REGION: eu-west-1 depend-on: - publish_to_local_registry - identifier: sql_iam_access @@ -881,7 +891,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/sql-iam-access.test.ts - CLI_REGION: eu-west-1 + CLI_REGION: eu-west-2 depend-on: - publish_to_local_registry - identifier: default_ddb_canary @@ -891,7 +901,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/default-ddb-canary.test.ts - CLI_REGION: eu-south-1 + CLI_REGION: eu-west-1 depend-on: - publish_to_local_registry - identifier: base_cdk_ap_east_1 @@ -1091,7 +1101,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/amplify-table-4.test.ts - CLI_REGION: eu-west-2 + CLI_REGION: eu-west-3 depend-on: - publish_to_local_registry - identifier: amplify_table_3 @@ -1101,7 +1111,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/amplify-table-3.test.ts - CLI_REGION: eu-west-3 + CLI_REGION: me-south-1 depend-on: - publish_to_local_registry - identifier: amplify_table_2 @@ -1111,7 +1121,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/amplify-table-2.test.ts - CLI_REGION: me-south-1 + CLI_REGION: sa-east-1 depend-on: - publish_to_local_registry - identifier: amplify_table_1 @@ -1121,7 +1131,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/amplify-table-1.test.ts - CLI_REGION: sa-east-1 + CLI_REGION: us-east-1 depend-on: - publish_to_local_registry - identifier: amplify_ddb_canary @@ -1131,7 +1141,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/amplify-ddb-canary.test.ts - CLI_REGION: us-east-1 + CLI_REGION: us-east-2 depend-on: - publish_to_local_registry - identifier: all_auth_modes @@ -1141,7 +1151,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/all-auth-modes.test.ts - CLI_REGION: us-east-2 + CLI_REGION: us-west-1 depend-on: - publish_to_local_registry - identifier: admin_role @@ -1151,7 +1161,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/admin-role.test.ts - CLI_REGION: us-west-1 + CLI_REGION: us-west-2 depend-on: - publish_to_local_registry - identifier: sql_custom_ssl @@ -1161,7 +1171,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/sql-custom-ssl/sql-custom-ssl.test.ts - CLI_REGION: ca-central-1 + CLI_REGION: eu-central-1 depend-on: - publish_to_local_registry - identifier: restricted_field_auth_gen2 @@ -1172,7 +1182,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/restricted-field-auth/restricted-field-auth-gen2.test.ts - CLI_REGION: ap-south-1 + CLI_REGION: ap-southeast-1 depend-on: - publish_to_local_registry - identifier: restricted_field_auth_gen1 @@ -1183,7 +1193,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/restricted-field-auth/restricted-field-auth-gen1.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 depend-on: - publish_to_local_registry - identifier: restricted_field_auth_gen2_subscriptions_off @@ -1194,7 +1204,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/restricted-field-auth/subscriptions-off/restricted-field-auth-gen2-subscriptions-off.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: restricted_field_auth_gen1_subscriptions_off @@ -1205,7 +1215,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/restricted-field-auth/subscriptions-off/restricted-field-auth-gen1-subscriptions-off.test.ts - CLI_REGION: ca-central-1 + CLI_REGION: eu-central-1 depend-on: - publish_to_local_registry - identifier: references_sqlprimary_sqlrelated @@ -1216,7 +1226,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/references/references-sqlprimary-sqlrelated.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 depend-on: - publish_to_local_registry - identifier: references_sqlprimary_ddbrelated @@ -1227,7 +1237,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/references/references-sqlprimary-ddbrelated.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: references_ddbprimary_sqlrelated @@ -1238,7 +1248,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/references/references-ddbprimary-sqlrelated.test.ts - CLI_REGION: ca-central-1 + CLI_REGION: eu-central-1 depend-on: - publish_to_local_registry - identifier: references_ddbprimary_ddbrelated @@ -1249,7 +1259,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/references/references-ddbprimary-ddbrelated.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: eu-north-1 depend-on: - publish_to_local_registry - identifier: recursive_relationships_sql @@ -1260,7 +1270,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/recursive/recursive-relationships-sql.test.ts - CLI_REGION: eu-north-1 + CLI_REGION: eu-south-1 depend-on: - publish_to_local_registry - identifier: recursive_relationships_ddb @@ -1271,7 +1281,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/recursive/recursive-relationships-ddb.test.ts - CLI_REGION: eu-south-1 + CLI_REGION: eu-west-1 depend-on: - publish_to_local_registry - identifier: uuid_pk_sqlprimary_sqlrelated @@ -1282,7 +1292,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/postgres-uuid-pk/uuid-pk-sqlprimary-sqlrelated.test.ts - CLI_REGION: eu-west-1 + CLI_REGION: eu-west-2 depend-on: - publish_to_local_registry - identifier: uuid_pk_sqlprimary_ddbrelated @@ -1293,7 +1303,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/postgres-uuid-pk/uuid-pk-sqlprimary-ddbrelated.test.ts - CLI_REGION: eu-west-2 + CLI_REGION: eu-west-3 depend-on: - publish_to_local_registry - identifier: uuid_pk_ddbprimary_sqlrelated @@ -1304,7 +1314,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/postgres-uuid-pk/uuid-pk-ddbprimary-sqlrelated.test.ts - CLI_REGION: eu-west-3 + CLI_REGION: me-south-1 depend-on: - publish_to_local_registry - identifier: multi_relationship_sqlprimary_sqlrelated @@ -1315,7 +1325,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/multi-relationship/multi-relationship-sqlprimary-sqlrelated.test.ts - CLI_REGION: me-south-1 + CLI_REGION: sa-east-1 depend-on: - publish_to_local_registry - identifier: multi_relationship_sqlprimary_ddbrelated @@ -1326,7 +1336,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/multi-relationship/multi-relationship-sqlprimary-ddbrelated.test.ts - CLI_REGION: sa-east-1 + CLI_REGION: us-east-1 depend-on: - publish_to_local_registry - identifier: multi_relationship_ddbprimary_sqlrelated @@ -1337,7 +1347,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/multi-relationship/multi-relationship-ddbprimary-sqlrelated.test.ts - CLI_REGION: us-east-1 + CLI_REGION: us-east-2 depend-on: - publish_to_local_registry - identifier: multi_relationship_ddbprimary_ddbrelated @@ -1348,7 +1358,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/relationships/multi-relationship/multi-relationship-ddbprimary-ddbrelated.test.ts - CLI_REGION: us-east-2 + CLI_REGION: us-west-1 depend-on: - publish_to_local_registry - identifier: relationships_gen1 @@ -1358,7 +1368,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/relationships/gen1/relationships-gen1.test.ts - CLI_REGION: us-west-1 + CLI_REGION: us-west-2 depend-on: - publish_to_local_registry - identifier: assoc_field_subscriptions_off @@ -1369,7 +1379,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/owner-auth/subscriptions-off/assoc-field-subscriptions-off.test.ts - CLI_REGION: ap-south-1 + CLI_REGION: ap-southeast-1 depend-on: - publish_to_local_registry - identifier: bind_sql_ids @@ -1379,7 +1389,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/owner-auth/bind-sql-ids/bind-sql-ids.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 depend-on: - publish_to_local_registry - identifier: assoc_field_sqlprimary_sqlrelated @@ -1390,7 +1400,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/owner-auth/assoc-field/assoc-field-sqlprimary-sqlrelated.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: assoc_field_sqlprimary_ddbrelated @@ -1401,7 +1411,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/owner-auth/assoc-field/assoc-field-sqlprimary-ddbrelated.test.ts - CLI_REGION: ca-central-1 + CLI_REGION: eu-central-1 depend-on: - publish_to_local_registry - identifier: assoc_field_ddbprimary_sqlrelated @@ -1412,7 +1422,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/owner-auth/assoc-field/assoc-field-ddbprimary-sqlrelated.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: eu-north-1 depend-on: - publish_to_local_registry - identifier: assoc_field_ddbprimary_ddbrelated @@ -1423,7 +1433,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/owner-auth/assoc-field/assoc-field-ddbprimary-ddbrelated.test.ts - CLI_REGION: eu-north-1 + CLI_REGION: eu-south-1 depend-on: - publish_to_local_registry - identifier: static_group_auth_sqlprimary_sqlrelated_subscriptions_off @@ -1434,7 +1444,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/static-group-auth/static-group-auth-sqlprimary-sqlrelated-subscriptions-off.test.ts - CLI_REGION: eu-south-1 + CLI_REGION: eu-west-1 depend-on: - publish_to_local_registry - identifier: static_group_auth_sqlprimary_ddbrelated_subscriptions_off @@ -1445,7 +1455,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/static-group-auth/static-group-auth-sqlprimary-ddbrelated-subscriptions-off.test.ts - CLI_REGION: eu-west-1 + CLI_REGION: eu-west-2 depend-on: - publish_to_local_registry - identifier: static_group_auth_ddbprimary_sqlrelated_subscriptions_off @@ -1456,7 +1466,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/static-group-auth/static-group-auth-ddbprimary-sqlrelated-subscriptions-off.test.ts - CLI_REGION: eu-west-2 + CLI_REGION: eu-west-3 depend-on: - publish_to_local_registry - identifier: static_group_auth_ddbprimary_ddbrelated_subscriptions_off @@ -1467,7 +1477,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/static-group-auth/static-group-auth-ddbprimary-ddbrelated-subscriptions-off.test.ts - CLI_REGION: eu-west-3 + CLI_REGION: me-south-1 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_sqlprimary_sqlrelated_subscriptions_off @@ -1478,7 +1488,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/dynamic-group-auth/dynamic-group-auth-sqlprimary-sqlrelated-subscriptions-off.test.ts - CLI_REGION: me-south-1 + CLI_REGION: sa-east-1 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_sqlprimary_ddbrelated_subscriptions_off @@ -1489,7 +1499,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/dynamic-group-auth/dynamic-group-auth-sqlprimary-ddbrelated-subscriptions-off.test.ts - CLI_REGION: sa-east-1 + CLI_REGION: us-east-1 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_ddbprimary_sqlrelated_subscriptions_off @@ -1500,7 +1510,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/dynamic-group-auth/dynamic-group-auth-ddbprimary-sqlrelated-subscriptions-off.test.ts - CLI_REGION: us-east-1 + CLI_REGION: us-east-2 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_ddbprimary_ddbrelated_subscriptions_off @@ -1511,7 +1521,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/subscriptions-off/dynamic-group-auth/dynamic-group-auth-ddbprimary-ddbrelated-subscriptions-off.test.ts - CLI_REGION: us-east-2 + CLI_REGION: us-west-1 depend-on: - publish_to_local_registry - identifier: static_group_auth_sqlprimary_sqlrelated @@ -1522,7 +1532,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/static-group-auth/static-group-auth-sqlprimary-sqlrelated.test.ts - CLI_REGION: us-west-1 + CLI_REGION: us-west-2 depend-on: - publish_to_local_registry - identifier: static_group_auth_sqlprimary_ddbrelated @@ -1533,7 +1543,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/static-group-auth/static-group-auth-sqlprimary-ddbrelated.test.ts - CLI_REGION: us-west-2 + CLI_REGION: ap-northeast-1 depend-on: - publish_to_local_registry - identifier: static_group_auth_ddbprimary_sqlrelated @@ -1544,7 +1554,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/static-group-auth/static-group-auth-ddbprimary-sqlrelated.test.ts - CLI_REGION: ap-northeast-1 + CLI_REGION: ap-northeast-2 depend-on: - publish_to_local_registry - identifier: static_group_auth_ddbprimary_ddbrelated @@ -1555,7 +1565,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/static-group-auth/static-group-auth-ddbprimary-ddbrelated.test.ts - CLI_REGION: ap-northeast-2 + CLI_REGION: ap-south-1 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_sqlprimary_sqlrelated @@ -1566,7 +1576,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/dynamic-group-auth/dynamic-group-auth-sqlprimary-sqlrelated.test.ts - CLI_REGION: ap-south-1 + CLI_REGION: ap-southeast-1 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_sqlprimary_ddbrelated @@ -1577,7 +1587,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/dynamic-group-auth/dynamic-group-auth-sqlprimary-ddbrelated.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_ddbprimary_sqlrelated @@ -1588,7 +1598,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/dynamic-group-auth/dynamic-group-auth-ddbprimary-sqlrelated.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: ca-central-1 depend-on: - publish_to_local_registry - identifier: dynamic_group_auth_ddbprimary_ddbrelated @@ -1599,7 +1609,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/group-auth/dynamic-group-auth/dynamic-group-auth-ddbprimary-ddbrelated.test.ts - CLI_REGION: ca-central-1 + CLI_REGION: eu-central-1 depend-on: - publish_to_local_registry - identifier: generation @@ -1620,7 +1630,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/deploy-velocity/single-gsi-100k-records.test.ts - CLI_REGION: ca-central-1 + CLI_REGION: eu-central-1 depend-on: - publish_to_local_registry - identifier: replace_2_gsis_update_attr_100k_records @@ -1631,7 +1641,7 @@ batch: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: >- src/__tests__/deploy-velocity/replace-2-gsis-update-attr-100k-records.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: eu-north-1 depend-on: - publish_to_local_registry - identifier: replace_2_gsis_100k_records @@ -1641,7 +1651,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/deploy-velocity/replace-2-gsis-100k-records.test.ts - CLI_REGION: eu-north-1 + CLI_REGION: eu-south-1 depend-on: - publish_to_local_registry - identifier: 3_gsis_100k_records @@ -1651,7 +1661,7 @@ batch: variables: NODE_OPTIONS: '--max-old-space-size=6656' TEST_SUITE: src/__tests__/deploy-velocity/3-gsis-100k-records.test.ts - CLI_REGION: eu-south-1 + CLI_REGION: eu-west-1 depend-on: - publish_to_local_registry - identifier: conversation diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/sql-pg-auto-increment.test.ts b/packages/amplify-graphql-api-construct-tests/src/__tests__/sql-pg-auto-increment.test.ts new file mode 100644 index 0000000000..acbdc63398 --- /dev/null +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/sql-pg-auto-increment.test.ts @@ -0,0 +1,55 @@ +import generator from 'generate-password'; +import { getResourceNamesForStrategyName, ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; +import { getRDSTableNamePrefix } from 'amplify-category-api-e2e-core'; +import { SqlDatatabaseController } from '../sql-datatabase-controller'; +import { DURATION_1_HOUR } from '../utils/duration-constants'; +import { testGraphQLAPIAutoIncrement } from '../sql-tests-common/sql-models-auto-increment'; + +jest.setTimeout(DURATION_1_HOUR); + +describe('CDK GraphQL Transformer deployments with Postgres SQL datasources', () => { + const projFolderName = 'pgmodels'; + + // sufficient password length that meets the requirements for RDS cluster/instance + const [username, password, identifier] = generator.generateMultiple(3, { length: 11 }); + const region = process.env.CLI_REGION ?? 'us-west-2'; + const engine = 'postgres'; + + const databaseController: SqlDatatabaseController = new SqlDatatabaseController( + [ + `CREATE TABLE "${getRDSTableNamePrefix()}coffee_queue" ("orderNumber" SERIAL PRIMARY KEY, "order" VARCHAR(256) NOT NULL, "customer" VARCHAR(256))`, + ], + { + identifier, + engine, + username, + password, + region, + }, + ); + + const strategyName = `${engine}DBStrategy`; + const resourceNames = getResourceNamesForStrategyName(strategyName); + + beforeAll(async () => { + await databaseController.setupDatabase(); + }); + + afterAll(async () => { + await databaseController.cleanupDatabase(); + }); + + const constructTestOptions = (connectionConfigName: string) => ({ + projFolderName, + region, + connectionConfigName, + dbController: databaseController, + resourceNames, + }); + + testGraphQLAPIAutoIncrement( + constructTestOptions('connectionUri'), + 'creates a GraphQL API from SQL-based models using Connection String SSM parameter', + ImportedRDSType.POSTGRESQL, + ); +}); diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-auto-increment/field-map.ts b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-auto-increment/field-map.ts new file mode 100644 index 0000000000..92ddfb51cb --- /dev/null +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-auto-increment/field-map.ts @@ -0,0 +1,7 @@ +import { FieldMap } from '../../../utils/sql-crudl-helper'; + +export const coffeeQueueFieldMap: FieldMap = { + orderNumber: true, + order: true, + customer: true, +}; diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-auto-increment/schema.graphql b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-auto-increment/schema.graphql new file mode 100644 index 0000000000..3a762837df --- /dev/null +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-auto-increment/schema.graphql @@ -0,0 +1,5 @@ +type CoffeeQueue @model @refersTo(name: "e2e_test_coffee_queue") { + orderNumber: Int! @primaryKey @default + order: String! + customer: String +} diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql index b99fe7eeb8..e5138d8a6a 100644 --- a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql @@ -1,6 +1,6 @@ type Todo @model @refersTo(name: "e2e_test_todos") { id: ID! @primaryKey - description: String! + description: String! @default(value: "Lorem ipsum yadda yadda...") } type Student @model @refersTo(name: "e2e_test_students") { studentId: Int! @primaryKey(sortKeyFields: ["classId"]) diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models-auto-increment.ts b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models-auto-increment.ts new file mode 100644 index 0000000000..c30d1187f7 --- /dev/null +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models-auto-increment.ts @@ -0,0 +1,174 @@ +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; +import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync'; +import { createNewProjectDir, deleteProjectDir, getRDSTableNamePrefix } from 'amplify-category-api-e2e-core'; +import { initCDKProject, cdkDeploy, cdkDestroy } from '../commands'; +import { SqlDatatabaseController } from '../sql-datatabase-controller'; +import { CRUDLHelper } from '../utils/sql-crudl-helper'; +import { ONE_MINUTE } from '../utils/duration-constants'; +import { coffeeQueueFieldMap } from './schemas/sql-auto-increment/field-map'; + +export const testGraphQLAPIAutoIncrement = ( + options: { + projFolderName: string; + region: string; + connectionConfigName: string; + dbController: SqlDatatabaseController; + resourceNames: { sqlLambdaAliasName: string }; + }, + testBlockDescription: string, + engine: ImportedRDSType, +): void => { + describe(`${testBlockDescription} - ${engine}`, () => { + // In particular, we want to verify that the new CREATE operation + // is allowed to omit the primary key field, and that the primary key + // we get back is the correct, db generated value. + // NOTE: Expects underlying orderNumber column to be a serial primary key in Postgres table + const schemaPath = path.resolve(path.join(__dirname, '..', 'sql-tests-common', 'schemas', 'sql-auto-increment', 'schema.graphql')); + const schemaConfigString = fs.readFileSync(schemaPath).toString(); + const { projFolderName, region, connectionConfigName, dbController } = options; + const templatePath = path.resolve(path.join(__dirname, '..', '__tests__', 'backends', 'sql-models')); + + let projRoot: string; + let name: string; + let outputs: Promise; + let coffeeQueueTableCRUDLHelper: CRUDLHelper; + + beforeAll(async () => { + projRoot = await createNewProjectDir(projFolderName); + name = await initCDKProject(projRoot, templatePath); + dbController.writeDbDetails(projRoot, connectionConfigName, schemaConfigString); + outputs = await cdkDeploy(projRoot, '--all', { postDeployWaitMs: ONE_MINUTE }); + const { awsAppsyncApiEndpoint: apiEndpoint, awsAppsyncApiKey: apiKey } = outputs[name]; + + const appSyncClient = new AWSAppSyncClient({ + url: apiEndpoint, + region, + disableOffline: true, + auth: { + type: AUTH_TYPE.API_KEY, + apiKey, + }, + }); + + coffeeQueueTableCRUDLHelper = new CRUDLHelper(appSyncClient, 'CoffeeQueue', 'CoffeeQueues', coffeeQueueFieldMap); + }); + + afterAll(async () => { + try { + await cdkDestroy(projRoot, '--all'); + await dbController.clearDatabase(); + } catch (err) { + console.log(`Error invoking 'cdk destroy': ${err}`); + } + + deleteProjectDir(projRoot); + }); + + test(`check CRUDL on coffee queue table with auto increment primary key - ${engine}`, async () => { + // Order Coffee Mutation + const createCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.create({ customer: 'petesv', order: 'cold brew' }); + + expect(createCoffeeOrder1).toBeDefined(); + expect(createCoffeeOrder1.orderNumber).toBeDefined(); + expect(createCoffeeOrder1.customer).toEqual('petesv'); + expect(createCoffeeOrder1.order).toEqual('cold brew'); + + // Get Todo Query + const getCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.get({ orderNumber: createCoffeeOrder1.orderNumber }); + + expect(getCoffeeOrder1.orderNumber).toEqual(createCoffeeOrder1.orderNumber); + expect(getCoffeeOrder1.customer).toEqual(createCoffeeOrder1.customer); + + // Update Todo Mutation + const updateCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.update({ + orderNumber: createCoffeeOrder1.orderNumber, + customer: 'petesv', + order: 'hot brew', + }); + + expect(updateCoffeeOrder1.orderNumber).toEqual(createCoffeeOrder1.orderNumber); + expect(updateCoffeeOrder1.order).toEqual('hot brew'); + + // Get Todo Query after update + const getUpdatedCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.get({ orderNumber: createCoffeeOrder1.orderNumber }); + + expect(getUpdatedCoffeeOrder1.orderNumber).toEqual(createCoffeeOrder1.orderNumber); + expect(getUpdatedCoffeeOrder1.order).toEqual('hot brew'); + + // List Todo Query & Create with custom SERIAL field value + const customOrderNumber = 42; + const createCofffeeOrder2 = await coffeeQueueTableCRUDLHelper.create({ orderNumber: customOrderNumber, order: 'latte' }); + expect(createCofffeeOrder2.orderNumber).toEqual(customOrderNumber); + + const listTodo = await coffeeQueueTableCRUDLHelper.list(); + expect(listTodo.items.length).toEqual(2); + expect(listTodo.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + orderNumber: getUpdatedCoffeeOrder1.orderNumber, + order: 'hot brew', + }), + expect.objectContaining({ + orderNumber: createCofffeeOrder2.orderNumber, + order: 'latte', + }), + ]), + ); + + // Delete Todo Mutation + const deleteCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.delete({ orderNumber: createCoffeeOrder1.orderNumber }); + + expect(deleteCoffeeOrder1.orderNumber).toEqual(createCoffeeOrder1.orderNumber); + expect(deleteCoffeeOrder1.order).toEqual('hot brew'); + + const getDeletedCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.get({ orderNumber: createCoffeeOrder1.orderNumber }); + + expect(getDeletedCoffeeOrder1).toBeNull(); + + // List Todo Query after delete + const listCoffeeOrdersAfterDelete = await coffeeQueueTableCRUDLHelper.list(); + + expect(listCoffeeOrdersAfterDelete.items.length).toEqual(1); + expect(listCoffeeOrdersAfterDelete.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + orderNumber: createCofffeeOrder2.orderNumber, + order: 'latte', + }), + ]), + ); + + // Check invalid CRUD operation returns generic error message + const createTodo6 = await coffeeQueueTableCRUDLHelper.create({ order: 'mocha' }); + + try { + // Invalid because the pk (orderNumber) already exists + await coffeeQueueTableCRUDLHelper.create({ orderNumber: createTodo6.orderNumber, order: 'americano' }); + } catch (error) { + coffeeQueueTableCRUDLHelper.checkGenericError(error?.message); + } + + const biggerThanAnyExistingOrderNumber = 99999999; + + try { + await coffeeQueueTableCRUDLHelper.get({ orderNumber: biggerThanAnyExistingOrderNumber }); + } catch (error) { + coffeeQueueTableCRUDLHelper.checkGenericError(error?.message); + } + + try { + await coffeeQueueTableCRUDLHelper.update({ orderNumber: biggerThanAnyExistingOrderNumber, order: 'cortado' }); + } catch (error) { + coffeeQueueTableCRUDLHelper.checkGenericError(error?.message); + } + + try { + await coffeeQueueTableCRUDLHelper.delete({ orderNumber: biggerThanAnyExistingOrderNumber }); + } catch (error) { + coffeeQueueTableCRUDLHelper.checkGenericError(error?.message); + } + }); + }); +}; diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts index 0411dd2290..0e58d04213 100644 --- a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts @@ -73,6 +73,34 @@ export const testGraphQLAPI = ( deleteProjectDir(projRoot); }); + test(`check default value on todo table - ${engine}`, async () => { + const defaultTodoDescription = 'Lorem ipsum yadda yadda...'; + + // Create Todo Mutation + const createTodo1 = await toDoTableCRUDLHelper.create({}); + + expect(createTodo1).toBeDefined(); + expect(createTodo1.id).toBeDefined(); + expect(createTodo1.description).toEqual(defaultTodoDescription); + + // Get Todo Query + const getTodo1 = await toDoTableCRUDLHelper.getById(createTodo1.id); + expect(getTodo1.id).toEqual(createTodo1.id); + expect(getTodo1.description).toEqual(createTodo1.description); + + // Update Todo Mutation + const updateTodo1 = await toDoTableCRUDLHelper.update({ id: createTodo1.id, description: 'Updated Todo #1' }); + + expect(updateTodo1.id).toEqual(createTodo1.id); + expect(updateTodo1.description).toEqual('Updated Todo #1'); + + // Get Todo Query after update + const getUpdatedTodo1 = await toDoTableCRUDLHelper.getById(createTodo1.id); + + expect(getUpdatedTodo1.id).toEqual(createTodo1.id); + expect(getUpdatedTodo1.description).toEqual('Updated Todo #1'); + }); + test(`check CRUDL on todo table with default primary key - ${engine}`, async () => { // Create Todo Mutation const createTodo1 = await toDoTableCRUDLHelper.create({ description: 'Todo #1' }); diff --git a/packages/amplify-graphql-default-value-transformer/package.json b/packages/amplify-graphql-default-value-transformer/package.json index e6bf424dfa..d38613a33e 100644 --- a/packages/amplify-graphql-default-value-transformer/package.json +++ b/packages/amplify-graphql-default-value-transformer/package.json @@ -75,7 +75,8 @@ "src/**/*.ts" ], "coveragePathIgnorePatterns": [ - "/__tests__/" + "/__tests__/", + "types.ts" ], "snapshotFormat": { "escapeString": true, diff --git a/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap b/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap index 320a27b4e9..e37213cd0a 100644 --- a/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap +++ b/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap @@ -1,5 +1,227 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`DefaultValueModelTransformer: should allow auto-increment primary key 1`] = ` +"type CoffeeQueue { + orderNumber: Int! + order: String! + customer: String +} + +input ModelStringInput { + ne: String + eq: String + le: String + lt: String + ge: String + gt: String + contains: String + notContains: String + between: [String] + beginsWith: String + attributeExists: Boolean + attributeType: ModelAttributeTypes + size: ModelSizeInput +} + +input ModelIntInput { + ne: Int + eq: Int + le: Int + lt: Int + ge: Int + gt: Int + between: [Int] + attributeExists: Boolean + attributeType: ModelAttributeTypes +} + +input ModelFloatInput { + ne: Float + eq: Float + le: Float + lt: Float + ge: Float + gt: Float + between: [Float] + attributeExists: Boolean + attributeType: ModelAttributeTypes +} + +input ModelBooleanInput { + ne: Boolean + eq: Boolean + attributeExists: Boolean + attributeType: ModelAttributeTypes +} + +input ModelIDInput { + ne: ID + eq: ID + le: ID + lt: ID + ge: ID + gt: ID + contains: ID + notContains: ID + between: [ID] + beginsWith: ID + attributeExists: Boolean + attributeType: ModelAttributeTypes + size: ModelSizeInput +} + +input ModelSubscriptionStringInput { + ne: String + eq: String + le: String + lt: String + ge: String + gt: String + contains: String + notContains: String + between: [String] + beginsWith: String + in: [String] + notIn: [String] +} + +input ModelSubscriptionIntInput { + ne: Int + eq: Int + le: Int + lt: Int + ge: Int + gt: Int + between: [Int] + in: [Int] + notIn: [Int] +} + +input ModelSubscriptionFloatInput { + ne: Float + eq: Float + le: Float + lt: Float + ge: Float + gt: Float + between: [Float] + in: [Float] + notIn: [Float] +} + +input ModelSubscriptionBooleanInput { + ne: Boolean + eq: Boolean +} + +input ModelSubscriptionIDInput { + ne: ID + eq: ID + le: ID + lt: ID + ge: ID + gt: ID + contains: ID + notContains: ID + between: [ID] + beginsWith: ID + in: [ID] + notIn: [ID] +} + +enum ModelAttributeTypes { + binary + binarySet + bool + list + map + number + numberSet + string + stringSet + _null +} + +input ModelSizeInput { + ne: Int + eq: Int + le: Int + lt: Int + ge: Int + gt: Int + between: [Int] +} + +enum ModelSortDirection { + ASC + DESC +} + +type ModelCoffeeQueueConnection { + items: [CoffeeQueue]! + nextToken: String +} + +input ModelCoffeeQueueFilterInput { + orderNumber: ModelIntInput + order: ModelStringInput + customer: ModelStringInput + and: [ModelCoffeeQueueFilterInput] + or: [ModelCoffeeQueueFilterInput] + not: ModelCoffeeQueueFilterInput +} + +type Query { + getCoffeeQueue(orderNumber: Int!): CoffeeQueue + listCoffeeQueues(orderNumber: Int, filter: ModelCoffeeQueueFilterInput, limit: Int, nextToken: String, sortDirection: ModelSortDirection): ModelCoffeeQueueConnection +} + +input ModelCoffeeQueueConditionInput { + order: ModelStringInput + customer: ModelStringInput + and: [ModelCoffeeQueueConditionInput] + or: [ModelCoffeeQueueConditionInput] + not: ModelCoffeeQueueConditionInput +} + +input CreateCoffeeQueueInput { + orderNumber: Int + order: String! + customer: String +} + +input UpdateCoffeeQueueInput { + orderNumber: Int! + order: String + customer: String +} + +input DeleteCoffeeQueueInput { + orderNumber: Int! +} + +type Mutation { + createCoffeeQueue(input: CreateCoffeeQueueInput!, condition: ModelCoffeeQueueConditionInput): CoffeeQueue + updateCoffeeQueue(input: UpdateCoffeeQueueInput!, condition: ModelCoffeeQueueConditionInput): CoffeeQueue + deleteCoffeeQueue(input: DeleteCoffeeQueueInput!, condition: ModelCoffeeQueueConditionInput): CoffeeQueue +} + +input ModelSubscriptionCoffeeQueueFilterInput { + orderNumber: ModelSubscriptionIntInput + order: ModelSubscriptionStringInput + customer: ModelSubscriptionStringInput + and: [ModelSubscriptionCoffeeQueueFilterInput] + or: [ModelSubscriptionCoffeeQueueFilterInput] +} + +type Subscription { + onCreateCoffeeQueue(filter: ModelSubscriptionCoffeeQueueFilterInput): CoffeeQueue @aws_subscribe(mutations: [\\"createCoffeeQueue\\"]) + onUpdateCoffeeQueue(filter: ModelSubscriptionCoffeeQueueFilterInput): CoffeeQueue @aws_subscribe(mutations: [\\"updateCoffeeQueue\\"]) + onDeleteCoffeeQueue(filter: ModelSubscriptionCoffeeQueueFilterInput): CoffeeQueue @aws_subscribe(mutations: [\\"deleteCoffeeQueue\\"]) +} +" +`; + exports[`DefaultValueModelTransformer: should be supported on a required field. 1`] = ` "type Test { id: ID! @@ -231,6 +453,222 @@ $util.qr($context.args.input.put(\\"stringValue\\", $util.defaultIfNull($ctx.arg {}" `; +exports[`DefaultValueModelTransformer: should successfully transform a simple schema with @default 1`] = ` +"type TestAutoIncrement { + id: ID! + value: Int +} + +input ModelStringInput { + ne: String + eq: String + le: String + lt: String + ge: String + gt: String + contains: String + notContains: String + between: [String] + beginsWith: String + attributeExists: Boolean + attributeType: ModelAttributeTypes + size: ModelSizeInput +} + +input ModelIntInput { + ne: Int + eq: Int + le: Int + lt: Int + ge: Int + gt: Int + between: [Int] + attributeExists: Boolean + attributeType: ModelAttributeTypes +} + +input ModelFloatInput { + ne: Float + eq: Float + le: Float + lt: Float + ge: Float + gt: Float + between: [Float] + attributeExists: Boolean + attributeType: ModelAttributeTypes +} + +input ModelBooleanInput { + ne: Boolean + eq: Boolean + attributeExists: Boolean + attributeType: ModelAttributeTypes +} + +input ModelIDInput { + ne: ID + eq: ID + le: ID + lt: ID + ge: ID + gt: ID + contains: ID + notContains: ID + between: [ID] + beginsWith: ID + attributeExists: Boolean + attributeType: ModelAttributeTypes + size: ModelSizeInput +} + +input ModelSubscriptionStringInput { + ne: String + eq: String + le: String + lt: String + ge: String + gt: String + contains: String + notContains: String + between: [String] + beginsWith: String + in: [String] + notIn: [String] +} + +input ModelSubscriptionIntInput { + ne: Int + eq: Int + le: Int + lt: Int + ge: Int + gt: Int + between: [Int] + in: [Int] + notIn: [Int] +} + +input ModelSubscriptionFloatInput { + ne: Float + eq: Float + le: Float + lt: Float + ge: Float + gt: Float + between: [Float] + in: [Float] + notIn: [Float] +} + +input ModelSubscriptionBooleanInput { + ne: Boolean + eq: Boolean +} + +input ModelSubscriptionIDInput { + ne: ID + eq: ID + le: ID + lt: ID + ge: ID + gt: ID + contains: ID + notContains: ID + between: [ID] + beginsWith: ID + in: [ID] + notIn: [ID] +} + +enum ModelAttributeTypes { + binary + binarySet + bool + list + map + number + numberSet + string + stringSet + _null +} + +input ModelSizeInput { + ne: Int + eq: Int + le: Int + lt: Int + ge: Int + gt: Int + between: [Int] +} + +enum ModelSortDirection { + ASC + DESC +} + +type ModelTestAutoIncrementConnection { + items: [TestAutoIncrement]! + nextToken: String +} + +input ModelTestAutoIncrementFilterInput { + id: ModelIDInput + value: ModelIntInput + and: [ModelTestAutoIncrementFilterInput] + or: [ModelTestAutoIncrementFilterInput] + not: ModelTestAutoIncrementFilterInput +} + +type Query { + getTestAutoIncrement(id: ID!): TestAutoIncrement + listTestAutoIncrements(id: ID, filter: ModelTestAutoIncrementFilterInput, limit: Int, nextToken: String, sortDirection: ModelSortDirection): ModelTestAutoIncrementConnection +} + +input ModelTestAutoIncrementConditionInput { + value: ModelIntInput + and: [ModelTestAutoIncrementConditionInput] + or: [ModelTestAutoIncrementConditionInput] + not: ModelTestAutoIncrementConditionInput +} + +input CreateTestAutoIncrementInput { + id: ID + value: Int +} + +input UpdateTestAutoIncrementInput { + id: ID! + value: Int +} + +input DeleteTestAutoIncrementInput { + id: ID! +} + +type Mutation { + createTestAutoIncrement(input: CreateTestAutoIncrementInput!, condition: ModelTestAutoIncrementConditionInput): TestAutoIncrement + updateTestAutoIncrement(input: UpdateTestAutoIncrementInput!, condition: ModelTestAutoIncrementConditionInput): TestAutoIncrement + deleteTestAutoIncrement(input: DeleteTestAutoIncrementInput!, condition: ModelTestAutoIncrementConditionInput): TestAutoIncrement +} + +input ModelSubscriptionTestAutoIncrementFilterInput { + id: ModelSubscriptionIDInput + value: ModelSubscriptionIntInput + and: [ModelSubscriptionTestAutoIncrementFilterInput] + or: [ModelSubscriptionTestAutoIncrementFilterInput] +} + +type Subscription { + onCreateTestAutoIncrement(filter: ModelSubscriptionTestAutoIncrementFilterInput): TestAutoIncrement @aws_subscribe(mutations: [\\"createTestAutoIncrement\\"]) + onUpdateTestAutoIncrement(filter: ModelSubscriptionTestAutoIncrementFilterInput): TestAutoIncrement @aws_subscribe(mutations: [\\"updateTestAutoIncrement\\"]) + onDeleteTestAutoIncrement(filter: ModelSubscriptionTestAutoIncrementFilterInput): TestAutoIncrement @aws_subscribe(mutations: [\\"deleteTestAutoIncrement\\"]) +} +" +`; + exports[`DefaultValueModelTransformer: should successfully transform simple valid schema 1`] = ` "type Post { id: ID! diff --git a/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts b/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts index 344b350d5f..48861f3f13 100644 --- a/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts +++ b/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts @@ -1,5 +1,12 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { constructDataSourceStrategies, getResourceNamesForStrategy, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { + constructDataSourceStrategies, + DDB_DEFAULT_DATASOURCE_STRATEGY, + getResourceNamesForStrategy, + MYSQL_DB_TYPE, + POSTGRES_DB_TYPE, + validateModelSchema, +} from '@aws-amplify/graphql-transformer-core'; import { parse } from 'graphql'; import { mockSqlDataSourceStrategy, testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; @@ -41,180 +48,35 @@ describe('DefaultValueModelTransformer:', () => { ).toThrow('The @default directive may only be added to scalar or enum field types.'); }); - it('throws if @default is used with a null value', () => { + it.each([ + { + type: 'String', + value: undefined, + expectedError: 'The @default directive requires a value property on non-Postgres datasources.', + }, + { type: 'Int', value: '"text"', expectedError: 'Default value "text" is not a valid Int.' }, + { type: 'Boolean', value: '"text"', expectedError: 'Default value "text" is not a valid Boolean.' }, + { type: 'AWSJSON', value: '"text"', expectedError: 'Default value "text" is not a valid AWSJSON.' }, + { type: 'AWSDate', value: '"text"', expectedError: 'Default value "text" is not a valid AWSDate.' }, + { type: 'AWSDateTime', value: '"text"', expectedError: 'Default value "text" is not a valid AWSDateTime.' }, + { type: 'AWSTime', value: '"text"', expectedError: 'Default value "text" is not a valid AWSTime.' }, + { type: 'AWSTimestamp', value: '"text"', expectedError: 'Default value "text" is not a valid AWSTimestamp.' }, + { type: 'AWSURL', value: '"text"', expectedError: 'Default value "text" is not a valid AWSURL.' }, + { type: 'AWSPhone', value: '"text"', expectedError: 'Default value "text" is not a valid AWSPhone.' }, + { type: 'AWSIPAddress', value: '"text"', expectedError: 'Default value "text" is not a valid AWSIPAddress.' }, + ])(`throws if @default is used with invalid type. %type check.`, ({ type, value, expectedError }) => { const schema = ` type Test @model { - id: ID! - name: String @default - } - `; - - expect(() => - testTransform({ - schema, - transformers: [new ModelTransformer(), new DefaultValueTransformer()], - }), - ).toThrow('Directive "@default" argument "value" of type "String!" is required, but it was not provided.'); - }); - - it('throws if @default is used with invalid type. Int check.', () => { - const schema = ` - type Test @model { - id: ID! - value: Int @default(value: "text") - } - `; - - expect(() => - testTransform({ - schema, - transformers: [new ModelTransformer(), new DefaultValueTransformer()], - }), - ).toThrow('Default value "text" is not a valid Int.'); - }); - - it('throws if @default is used with invalid type. Boolean check.', () => { - const schema = ` - type Test @model { - id: ID! - value: Boolean @default(value: "text") - } - `; - - expect(() => - testTransform({ - schema, - transformers: [new ModelTransformer(), new DefaultValueTransformer()], - }), - ).toThrow('Default value "text" is not a valid Boolean.'); - }); - - it('throws if @default is used with invalid type. AWSJSON check.', () => { - const schema = ` - type Test @model { - id: ID! - value: AWSJSON @default(value: "text") - } - `; - - expect(() => - testTransform({ - schema, - transformers: [new ModelTransformer(), new DefaultValueTransformer()], - }), - ).toThrow('Default value "text" is not a valid AWSJSON.'); - }); - - it('throws if @default is used with invalid type. AWSDate check.', () => { - const schema = ` - type Test @model { - id: ID! - value: AWSDate @default(value: "text") - } - `; - - expect(() => - testTransform({ - schema, - transformers: [new ModelTransformer(), new DefaultValueTransformer()], - }), - ).toThrow('Default value "text" is not a valid AWSDate.'); - }); - - it('throws if @default is used with invalid type. AWSDateTime check.', () => { - const schema = ` - type Test @model { - id: ID! - value: AWSDateTime @default(value: "text") - } - `; - - expect(() => - testTransform({ - schema, - transformers: [new ModelTransformer(), new DefaultValueTransformer()], - }), - ).toThrow('Default value "text" is not a valid AWSDateTime.'); - }); - - it('throws if @default is used with invalid type. AWSTime check.', () => { - const schema = ` - type Test @model { - id: ID! - value: AWSTime @default(value: "text") - } - `; - - expect(() => - testTransform({ - schema, - transformers: [new ModelTransformer(), new DefaultValueTransformer()], - }), - ).toThrow('Default value "text" is not a valid AWSTime.'); - }); - - it('throws if @default is used with invalid type. AWSTimestamp check.', () => { - const schema = ` - type Test @model { - id: ID! - value: AWSTimestamp @default(value: "text") - } - `; - - expect(() => - testTransform({ - schema, - transformers: [new ModelTransformer(), new DefaultValueTransformer()], - }), - ).toThrow('Default value "text" is not a valid AWSTimestamp.'); - }); - - it('throws if @default is used with invalid type. AWSURL check.', () => { - const schema = ` - type Test @model { - id: ID! - value: AWSURL @default(value: "text") - } - `; - - expect(() => - testTransform({ - schema, - transformers: [new ModelTransformer(), new DefaultValueTransformer()], - }), - ).toThrow('Default value "text" is not a valid AWSURL.'); - }); - - it('throws if @default is used with invalid type. AWSPhone check.', () => { - const schema = ` - type Test @model { - id: ID! - value: AWSPhone @default(value: "text") - } - `; - - expect(() => - testTransform({ - schema, - transformers: [new ModelTransformer(), new DefaultValueTransformer()], - }), - ).toThrow('Default value "text" is not a valid AWSPhone.'); - }); - - it('throws if @default is used with invalid type. AWSIPAddress check.', () => { - const schema = ` - type Test @model { - id: ID! - value: AWSIPAddress @default(value: "text") + id: ID! + value: ${type} ${value !== undefined ? `@default(value: ${value})` : '@default'} } `; - expect(() => testTransform({ schema, transformers: [new ModelTransformer(), new DefaultValueTransformer()], }), - ).toThrow('Default value "text" is not a valid AWSIPAddress.'); + ).toThrow(expectedError); }); it('should validate enum values', async () => { @@ -338,4 +200,106 @@ describe('DefaultValueModelTransformer:', () => { expect(out.resolvers['Mutation.createNote.init.1.req.vtl']).toBeDefined(); expect(out.resolvers['Mutation.createNote.init.2.req.vtl']).toBeUndefined(); }); + + it('cannot use `null` as a default value', () => { + const schema = ` + type CoffeeQueue @model { + id: ID! @primaryKey + orderNumber: Int! @default(value: null) + name: String + }`; + + expect(() => { + testTransform({ + schema: schema, + transformers: [new ModelTransformer(), new DefaultValueTransformer(), new PrimaryKeyTransformer()], + }); + }).toThrow('The @default directive does not support null values.'); + }); + + it.each([ + { strategy: mockSqlDataSourceStrategy({ dbType: MYSQL_DB_TYPE }), datasourceName: 'mysql' }, + { strategy: DDB_DEFAULT_DATASOURCE_STRATEGY, datasourceName: 'DynamoDB (Deafult)' }, + ])('throws if @default (no args) is used non-Postgres datasource (%datasourceName)', ({ strategy }) => { + const schema = ` + type CoffeeQueue @model { + id: ID! @primaryKey + orderNumber: Int! @default + name: String + }`; + expect(() => { + testTransform({ + schema: schema, + transformers: [new ModelTransformer(), new DefaultValueTransformer(), new PrimaryKeyTransformer()], + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + }); + }).toThrow('The @default directive requires a value property on non-Postgres datasources.'); + }); + + it.each([ + { typeStr: 'Boolean' }, + { typeStr: 'AWSJSON' }, + { typeStr: 'AWSDate' }, + { typeStr: 'AWSDateTime' }, + { typeStr: 'AWSTime' }, + { typeStr: 'AWSTime' }, + { typeStr: 'AWSURL' }, + { typeStr: 'AWSPhone' }, + { typeStr: 'AWSIPAddress' }, + ])('throws if @default (no args) is used on non-int types (case: $typeStr)', ({ typeStr }) => { + const strategy = mockSqlDataSourceStrategy({ dbType: POSTGRES_DB_TYPE }); + + expect(() => { + const schema = ` + type Test @model { + id: ID! @primaryKey + value: ${typeStr} @default + } + `; + testTransform({ + schema, + transformers: [new ModelTransformer(), new DefaultValueTransformer(), new PrimaryKeyTransformer()], + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + }); + }).toThrow('The @default directive requires a value property on non-Int types.'); + }); + + it('should successfully transform a simple schema with @default', async () => { + const schema = ` + type TestAutoIncrement @model { + id: ID! @primaryKey + value: Int @default + } + `; + const strategy = mockSqlDataSourceStrategy({ dbType: POSTGRES_DB_TYPE }); + const out = testTransform({ + schema, + transformers: [new ModelTransformer(), new DefaultValueTransformer(), new PrimaryKeyTransformer()], + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + }); + expect(out).toBeDefined(); + expect(out.schema).toMatchSnapshot(); + const parsedSchema = parse(out.schema); + validateModelSchema(parsedSchema); + }); + + it('should allow auto-increment primary key', async () => { + const schema = ` + type CoffeeQueue @model { + orderNumber: Int! @primaryKey @default + order: String! + customer: String + } + `; + const strategy = mockSqlDataSourceStrategy({ dbType: POSTGRES_DB_TYPE }); + const out = testTransform({ + schema, + transformers: [new ModelTransformer(), new DefaultValueTransformer(), new PrimaryKeyTransformer()], + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + }); + expect(out).toBeDefined(); + expect(out.schema).toMatchSnapshot(); + const parsedSchema = parse(out.schema); + validateModelSchema(parsedSchema); + }); }); diff --git a/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts b/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts index 7178eaad41..2ed188d645 100644 --- a/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts +++ b/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts @@ -4,6 +4,7 @@ import { InputObjectDefinitionWrapper, InvalidDirectiveError, isDynamoDbModel, + isPostgresModel, MappingTemplate, TransformerPluginBase, } from '@aws-amplify/graphql-transformer-core'; @@ -22,7 +23,6 @@ import { Kind, ObjectTypeDefinitionNode, StringValueNode, - TypeNode, } from 'graphql'; import { methodCall, printBlock, qref, raw, ref, str } from 'graphql-mapping-template'; import { getBaseType, isEnum, isListType, isScalarOrEnum, ModelResourceIDs, toCamelCase } from 'graphql-transformer-common'; @@ -31,16 +31,24 @@ import { TypeValidators } from './validators'; const nonStringTypes = ['Int', 'Float', 'Boolean', 'AWSTimestamp', 'AWSJSON']; -const validateFieldType = (ctx: TransformerSchemaVisitStepContextProvider, type: TypeNode): void => { +const validateFieldType = (ctx: TransformerSchemaVisitStepContextProvider, config: DefaultValueDirectiveConfiguration): void => { + const type = config.field.type; + const argc = config.directive.arguments?.length ?? 0; const enums = ctx.output.getTypeDefinitionsOfKind(Kind.ENUM_TYPE_DEFINITION) as EnumTypeDefinitionNode[]; if (isListType(type) || !isScalarOrEnum(type, enums)) { throw new InvalidDirectiveError('The @default directive may only be added to scalar or enum field types.'); } + if (isPostgresModel(ctx, config.object.name.value) && argc === 0 && getBaseType(type) !== 'Int') { + throw new InvalidDirectiveError('The @default directive requires a value property on non-Int types.'); + } }; -const validateDirectiveArguments = (directive: DirectiveNode): void => { - if (directive.arguments!.length === 0) throw new InvalidDirectiveError('The @default directive must have a value property'); - if (directive.arguments!.length > 1) throw new InvalidDirectiveError('The @default directive only takes a value property'); +const validateDirectiveArguments = (ctx: TransformerSchemaVisitStepContextProvider, config: DefaultValueDirectiveConfiguration): void => { + const argc = config.directive.arguments?.length ?? 0; + const isPostgres = isPostgresModel(ctx, config.object.name.value); + if (!isPostgres && argc === 0) + throw new InvalidDirectiveError('The @default directive requires a value property on non-Postgres datasources.'); + if (argc > 1) throw new InvalidDirectiveError('The @default directive only takes a value property'); }; const validateModelDirective = (config: DefaultValueDirectiveConfiguration): void => { @@ -74,8 +82,8 @@ const validateDefaultValueType = (ctx: TransformerSchemaVisitStepContextProvider const validate = (ctx: TransformerSchemaVisitStepContextProvider, config: DefaultValueDirectiveConfiguration): void => { validateModelDirective(config); - validateFieldType(ctx, config.field.type); - validateDirectiveArguments(config.directive); + validateFieldType(ctx, config); + validateDirectiveArguments(ctx, config); // Validate the default values only for the DynamoDB datasource. // For SQL, the database determines and sets the default value. We will not validate the value in transformers. @@ -123,6 +131,10 @@ export class DefaultValueTransformer extends TransformerPluginBase { const input = InputObjectDefinitionWrapper.fromObject(name, config.object, ctx.inputDocument); const fieldWrapper = input.fields.find((f) => f.name === config.field.name.value); fieldWrapper?.makeNullable(); + + if (isPostgresModel(ctx, typeName)) { + ctx.output.updateInput(input.serialize()); + } } } }; diff --git a/packages/amplify-graphql-directives/src/__tests__/__snapshots__/index.test.ts.snap b/packages/amplify-graphql-directives/src/__tests__/__snapshots__/index.test.ts.snap index 8e5b36eecb..c0a20f5bdf 100644 --- a/packages/amplify-graphql-directives/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/amplify-graphql-directives/src/__tests__/__snapshots__/index.test.ts.snap @@ -255,7 +255,7 @@ exports[`Directive Definitions DefaultDirective 1`] = ` Object { "defaults": Object {}, "definition": " - directive @default(value: String!) on FIELD_DEFINITION + directive @default(value: String) on FIELD_DEFINITION ", "name": "default", } diff --git a/packages/amplify-graphql-directives/src/directives/default.ts b/packages/amplify-graphql-directives/src/directives/default.ts index 4aa8c33020..c378c08073 100644 --- a/packages/amplify-graphql-directives/src/directives/default.ts +++ b/packages/amplify-graphql-directives/src/directives/default.ts @@ -2,7 +2,7 @@ import { Directive } from './directive'; const name = 'default'; const definition = /* GraphQL */ ` - directive @${name}(value: String!) on FIELD_DEFINITION + directive @${name}(value: String) on FIELD_DEFINITION `; const defaults = {}; diff --git a/packages/amplify-graphql-transformer-core/API.md b/packages/amplify-graphql-transformer-core/API.md index ad5de175f2..f498d42d04 100644 --- a/packages/amplify-graphql-transformer-core/API.md +++ b/packages/amplify-graphql-transformer-core/API.md @@ -487,6 +487,12 @@ export const isMutationNode: (obj: DefinitionNode) => obj is ObjectTypeDefinitio // @public (undocumented) export const isObjectTypeDefinitionNode: (obj: DefinitionNode) => obj is ObjectTypeDefinitionNode; +// @public (undocumented) +export const isPostgresDbType: (dbType: ModelDataSourceStrategyDbType) => dbType is "POSTGRES"; + +// @public (undocumented) +export const isPostgresModel: (ctx: DataSourceStrategiesProvider, typename: string) => boolean; + // @public (undocumented) export const isQueryNode: (obj: DefinitionNode) => obj is ObjectTypeDefinitionNode | (InterfaceTypeDefinitionNode & { name: { diff --git a/packages/amplify-graphql-transformer-core/src/index.ts b/packages/amplify-graphql-transformer-core/src/index.ts index 999869500d..ac8f8f796b 100644 --- a/packages/amplify-graphql-transformer-core/src/index.ts +++ b/packages/amplify-graphql-transformer-core/src/index.ts @@ -64,6 +64,8 @@ export { isModelType, isMutationNode, isObjectTypeDefinitionNode, + isPostgresDbType, + isPostgresModel, isQueryNode, isQueryType, isSqlDbType, diff --git a/packages/amplify-graphql-transformer-core/src/utils/model-datasource-strategy-utils.ts b/packages/amplify-graphql-transformer-core/src/utils/model-datasource-strategy-utils.ts index 5e460a0b7d..863877e78a 100644 --- a/packages/amplify-graphql-transformer-core/src/utils/model-datasource-strategy-utils.ts +++ b/packages/amplify-graphql-transformer-core/src/utils/model-datasource-strategy-utils.ts @@ -141,6 +141,27 @@ export const isSqlModel = (ctx: DataSourceStrategiesProvider, typename: string): return isSqlDbType(modelDataSourceType.dbType); }; +/** + * Checks if the given model is a PostgreSQL model + * @param ctx Transformer Context + * @param typename Model name + * @returns boolean + */ +export const isPostgresModel = (ctx: DataSourceStrategiesProvider, typename: string): boolean => { + if (isBuiltInGraphqlType(typename)) { + return false; + } + const modelDataSourceType = getModelDataSourceStrategy(ctx, typename); + return isPostgresDbType(modelDataSourceType.dbType); +}; + +/** + * Type predicate that returns true if `dbType` is a PostgreSQL database type + */ +export const isPostgresDbType = (dbType: ModelDataSourceStrategyDbType): dbType is 'POSTGRES' => { + return dbType === POSTGRES_DB_TYPE; +}; + /** * Type predicate that returns true if `obj` is a SQLLambdaModelDataSourceStrategy */ From d8d9eefedc5ac411c9dc358a62c2461cfbb6a98a Mon Sep 17 00:00:00 2001 From: Ian Saultz <52051793+atierian@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:02:36 -0400 Subject: [PATCH 09/15] fix(conversation): use functionMap for custom handler IFunction reference (#2922) --- dependency_licenses.txt | 26 +++ .../API.md | 3 +- .../package.json | 3 +- ...phql-conversation-transformer.test.ts.snap | 32 ++-- ...y-graphql-conversation-transformer.test.ts | 37 ++++- .../conversation-route-custom-handler.graphql | 13 ++ ...nversation-route-custom-query-tool.graphql | 1 - ...e-inference-configuration-template.graphql | 1 - ...ion-route-invalid-missing-ai-model.graphql | 1 - ...ersation-route-invalid-return-type.graphql | 1 - ...odel-query-tool-with-relationships.graphql | 1 - ...onversation-route-model-query-tool.graphql | 1 - ...route-with-inference-configuration.graphql | 1 - .../src/grapqhl-conversation-transformer.ts | 4 +- .../conversation-resolver-generator.ts | 32 ++-- .../API.md | 1 + .../src/test-transform.ts | 4 +- .../src/graphql-transformer.ts | 2 +- yarn.lock | 150 ++++++++++++++++++ 19 files changed, 260 insertions(+), 54 deletions(-) create mode 100644 packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-custom-handler.graphql diff --git a/dependency_licenses.txt b/dependency_licenses.txt index e8a076a8b4..42cc8a97e0 100644 --- a/dependency_licenses.txt +++ b/dependency_licenses.txt @@ -14486,6 +14486,32 @@ SOFTWARE. ----- +The following software may be included in this product: esbuild. A copy of the source code may be downloaded from git+https://github.com/evanw/esbuild.git. This software contains the following license and notice below: + +MIT License + +Copyright (c) 2020 Evan Wallace + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +----- + The following software may be included in this product: escalade. A copy of the source code may be downloaded from https://github.com/lukeed/escalade.git. This software contains the following license and notice below: MIT License diff --git a/packages/amplify-graphql-conversation-transformer/API.md b/packages/amplify-graphql-conversation-transformer/API.md index f320e81c87..814d98c96d 100644 --- a/packages/amplify-graphql-conversation-transformer/API.md +++ b/packages/amplify-graphql-conversation-transformer/API.md @@ -9,6 +9,7 @@ import { DirectiveNode } from 'graphql'; import { FieldDefinitionNode } from 'graphql'; import { HasManyTransformer } from '@aws-amplify/graphql-relational-transformer'; import { InterfaceTypeDefinitionNode } from 'graphql'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; import { ObjectTypeDefinitionNode } from 'graphql'; import { TransformerAuthProvider } from '@aws-amplify/graphql-transformer-interfaces'; @@ -19,7 +20,7 @@ import { TransformerSchemaVisitStepContextProvider } from '@aws-amplify/graphql- // @public (undocumented) export class ConversationTransformer extends TransformerPluginBase { - constructor(modelTransformer: ModelTransformer, hasManyTransformer: HasManyTransformer, belongsToTransformer: BelongsToTransformer, authProvider: TransformerAuthProvider); + constructor(modelTransformer: ModelTransformer, hasManyTransformer: HasManyTransformer, belongsToTransformer: BelongsToTransformer, authProvider: TransformerAuthProvider, functionNameMap?: Record); // (undocumented) field: (parent: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode, definition: FieldDefinitionNode, directive: DirectiveNode, context: TransformerSchemaVisitStepContextProvider) => void; // (undocumented) diff --git a/packages/amplify-graphql-conversation-transformer/package.json b/packages/amplify-graphql-conversation-transformer/package.json index 29c03d1359..6d03d4a561 100644 --- a/packages/amplify-graphql-conversation-transformer/package.json +++ b/packages/amplify-graphql-conversation-transformer/package.json @@ -37,7 +37,8 @@ "immer": "^9.0.12" }, "devDependencies": { - "@aws-amplify/graphql-transformer-test-utils": "1.0.3" + "@aws-amplify/graphql-transformer-test-utils": "1.0.3", + "esbuild": "^0.24.0" }, "peerDependencies": { "aws-cdk-lib": "^2.158.0", diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap b/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap index 3747205910..3ea92189bc 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap @@ -22,11 +22,9 @@ exports[`ConversationTransformer valid schemas should transform conversation rou ctx.stash.connectionAttributes = {}; ctx.stash.lambdaFunctionArn = "", { - "Fn::Sub": [ - "arn:aws:lambda:\${AWS::Region}:\${AWS::AccountId}:function:\${name}", - { - "name": "conversation-handler", - }, + "Fn::GetAtt": [ + "PirateChatConversationDirectiveLambdaStack", + "Outputs.transformerrootstackPirateChatConversationDirectiveLambdaStackPirateChatDefaultConversationHandlerconversationHandlerFunction2B526F1AArn", ], }, ""; @@ -235,11 +233,9 @@ exports[`ConversationTransformer valid schemas should transform conversation rou ctx.stash.connectionAttributes = {}; ctx.stash.lambdaFunctionArn = "", { - "Fn::Sub": [ - "arn:aws:lambda:\${AWS::Region}:\${AWS::AccountId}:function:\${name}", - { - "name": "conversation-handler", - }, + "Fn::GetAtt": [ + "PirateChatConversationDirectiveLambdaStack", + "Outputs.transformerrootstackPirateChatConversationDirectiveLambdaStackPirateChatDefaultConversationHandlerconversationHandlerFunction2B526F1AArn", ], }, ""; @@ -450,11 +446,9 @@ exports[`ConversationTransformer valid schemas should transform conversation rou ctx.stash.connectionAttributes = {}; ctx.stash.lambdaFunctionArn = "", { - "Fn::Sub": [ - "arn:aws:lambda:\${AWS::Region}:\${AWS::AccountId}:function:\${name}", - { - "name": "conversation-handler", - }, + "Fn::GetAtt": [ + "PirateChatConversationDirectiveLambdaStack", + "Outputs.transformerrootstackPirateChatConversationDirectiveLambdaStackPirateChatDefaultConversationHandlerconversationHandlerFunction2B526F1AArn", ], }, ""; @@ -665,11 +659,9 @@ exports[`ConversationTransformer valid schemas should transform conversation rou ctx.stash.connectionAttributes = {}; ctx.stash.lambdaFunctionArn = "", { - "Fn::Sub": [ - "arn:aws:lambda:\${AWS::Region}:\${AWS::AccountId}:function:\${name}", - { - "name": "conversation-handler", - }, + "Fn::GetAtt": [ + "PirateChatConversationDirectiveLambdaStack", + "Outputs.transformerrootstackPirateChatConversationDirectiveLambdaStackPirateChatDefaultConversationHandlerconversationHandlerFunction2B526F1AArn", ], }, ""; diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts b/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts index 3ac99b9890..16be984c44 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts @@ -3,12 +3,13 @@ import { IndexTransformer, PrimaryKeyTransformer } from '@aws-amplify/graphql-in import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; import { validateModelSchema } from '@aws-amplify/graphql-transformer-core'; import { AppSyncAuthConfiguration, ModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; -import { DeploymentResources, testTransform } from '@aws-amplify/graphql-transformer-test-utils'; +import { DeploymentResources, testTransform, TransformManager } from '@aws-amplify/graphql-transformer-test-utils'; import { parse } from 'graphql'; import { ConversationTransformer } from '..'; import { BelongsToTransformer, HasManyTransformer, HasOneTransformer } from '@aws-amplify/graphql-relational-transformer'; import * as fs from 'fs-extra'; import * as path from 'path'; +import { Code, Function, IFunction, Runtime } from 'aws-cdk-lib/aws-lambda'; import { GenerationTransformer } from '@aws-amplify/graphql-generation-transformer'; import { toUpper } from 'graphql-transformer-common'; @@ -44,6 +45,35 @@ describe('ConversationTransformer', () => { const schema = parse(out.schema); validateModelSchema(schema); }); + + it('uses functionMap for custom handler', () => { + const routeName = 'pirateChat'; + const inputSchema = getSchema('conversation-route-custom-handler.graphql', { ROUTE_NAME: routeName }); + + const transformerManager = new TransformManager(); + const stack = transformerManager.getTransformScope(); + const customHandler = new Function(stack, 'conversation-handler', { + runtime: Runtime.NODEJS_18_X, + code: Code.fromInline('exports.handler = async (event) => { return "Hello World"; }'), + handler: 'index.handler', + }); + + const functionMap = { + [`Fn${routeName}`]: customHandler, + }; + + const out = transform(inputSchema, {}, defaultAuthConfig, functionMap, transformerManager); + expect(out).toBeDefined(); + + const expectedCustomHandlerArn = out.rawRootStack.resolve(customHandler.functionArn); + const conversationLambdaStackName = `${toUpper(routeName)}ConversationDirectiveLambdaStack`; + const conversationLambdaDataSourceName = `Fn${routeName}LambdaDataSource`; + const conversationLambdaDataSourceFunctionArnRef = + out.stacks[conversationLambdaStackName].Resources?.[conversationLambdaDataSourceName].Properties.LambdaConfig.LambdaFunctionArn.Ref; + const lambdaDataSourceFunctionArn = + out.rootStack.Resources?.[conversationLambdaStackName].Properties?.Parameters?.[conversationLambdaDataSourceFunctionArnRef]; + expect(lambdaDataSourceFunctionArn).toEqual(expectedCustomHandlerArn); + }); }); describe('invalid schemas', () => { @@ -126,6 +156,8 @@ function transform( inputSchema: string, dataSourceStrategies?: Record, authConfig: AppSyncAuthConfiguration = defaultAuthConfig, + functionMap?: Record, + transformerManager?: TransformManager, ): DeploymentResources { const modelTransformer = new ModelTransformer(); const authTransformer = new AuthTransformer(); @@ -141,7 +173,7 @@ function transform( hasManyTransformer, hasOneTransformer, belongsToTransformer, - new ConversationTransformer(modelTransformer, hasManyTransformer, belongsToTransformer, authTransformer), + new ConversationTransformer(modelTransformer, hasManyTransformer, belongsToTransformer, authTransformer, functionMap), new GenerationTransformer(), authTransformer, ]; @@ -151,6 +183,7 @@ function transform( authConfig, transformers, dataSourceStrategies, + transformerManager, }); return out; diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-custom-handler.graphql b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-custom-handler.graphql new file mode 100644 index 0000000000..db9cdccdbc --- /dev/null +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-custom-handler.graphql @@ -0,0 +1,13 @@ +type Mutation { + ROUTE_NAME( + conversationId: ID!, + content: [ContentBlockInput], + aiContext: AWSJSON, + toolConfiguration: ToolConfigurationInput + ): ConversationMessage + @conversation( + aiModel: "anthropic.claude-3-haiku-20240307-v1:0", + functionName: "FnROUTE_NAME", + systemPrompt: "You are a helpful chatbot. Answer questions to the best of your ability.", + ) +} diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-custom-query-tool.graphql b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-custom-query-tool.graphql index cfc2a157fe..e4e3ca2418 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-custom-query-tool.graphql +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-custom-query-tool.graphql @@ -17,7 +17,6 @@ type Mutation { ): ConversationMessage @conversation( aiModel: "anthropic.claude-3-haiku-20240307-v1:0", - functionName: "conversation-handler", systemPrompt: "You are a helpful chatbot. Answer questions to the best of your ability.", tools: [{ name: "getTemperature", description: "does a thing" }, { name: "plus", description: "does a different thing" }] ) diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-inference-configuration-template.graphql b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-inference-configuration-template.graphql index e50ebea487..483e131000 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-inference-configuration-template.graphql +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-inference-configuration-template.graphql @@ -7,7 +7,6 @@ type Mutation { ): ConversationMessage @conversation( aiModel: "anthropic.claude-3-haiku-20240307-v1:0", - functionName: "conversation-handler", systemPrompt: "You are a helpful chatbot. Answer questions to the best of your ability.", INFERENENCE_CONFIGURATION ) diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-invalid-missing-ai-model.graphql b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-invalid-missing-ai-model.graphql index 30c1493f15..0c7ab129bf 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-invalid-missing-ai-model.graphql +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-invalid-missing-ai-model.graphql @@ -6,7 +6,6 @@ type Mutation { toolConfiguration: ToolConfigurationInput ): ConversationMessage @conversation( - functionName: "conversation-handler", systemPrompt: "You are a helpful chatbot." ) } \ No newline at end of file diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-invalid-return-type.graphql b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-invalid-return-type.graphql index f29ff4996a..a935ec1b24 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-invalid-return-type.graphql +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-invalid-return-type.graphql @@ -7,7 +7,6 @@ type Mutation { ): String @conversation( aiModel: "anthropic.claude-3-haiku-20240307-v1:0", - functionName: "conversation-handler", systemPrompt: "You are a helpful chatbot." ) } \ No newline at end of file diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-model-query-tool-with-relationships.graphql b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-model-query-tool-with-relationships.graphql index d90d8e4ea8..25bb739d1b 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-model-query-tool-with-relationships.graphql +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-model-query-tool-with-relationships.graphql @@ -31,7 +31,6 @@ type Mutation { ): ConversationMessage @conversation( aiModel: "anthropic.claude-3-haiku-20240307-v1:0", - functionName: "conversation-handler", systemPrompt: "You are a helpful chatbot. Answer questions to the best of your ability.", tools: [{ name: "listCustomers", description: "Provides data about the customer sending a message" }] ) diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-model-query-tool.graphql b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-model-query-tool.graphql index da3423b7c3..41c95f8365 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-model-query-tool.graphql +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-model-query-tool.graphql @@ -12,7 +12,6 @@ type Mutation { ): ConversationMessage @conversation( aiModel: "anthropic.claude-3-haiku-20240307-v1:0", - functionName: "conversation-handler", systemPrompt: "You are a helpful chatbot. Answer questions to the best of your ability.", tools: [{ name: "listTodos", description: "lists todos" }] ) diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-with-inference-configuration.graphql b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-with-inference-configuration.graphql index 53be8f4da4..8c6118a9df 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-with-inference-configuration.graphql +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-route-with-inference-configuration.graphql @@ -8,7 +8,6 @@ type Mutation { @conversation( aiModel: "anthropic.claude-3-haiku-20240307-v1:0", systemPrompt: "You are a helpful chatbot. Answer questions to the best of your ability.", - functionName: "conversation-handler", inferenceConfiguration: { temperature: 0.5, topP: 0.9, diff --git a/packages/amplify-graphql-conversation-transformer/src/grapqhl-conversation-transformer.ts b/packages/amplify-graphql-conversation-transformer/src/grapqhl-conversation-transformer.ts index 2626db5310..a9c618549f 100644 --- a/packages/amplify-graphql-conversation-transformer/src/grapqhl-conversation-transformer.ts +++ b/packages/amplify-graphql-conversation-transformer/src/grapqhl-conversation-transformer.ts @@ -15,6 +15,7 @@ import { type ToolDefinition, type Tools } from './utils/tools'; import { ConversationPrepareHandler } from './transformer-steps/conversation-prepare-handler'; import { ConversationResolverGenerator } from './transformer-steps/conversation-resolver-generator'; import { ConversationFieldHandler } from './transformer-steps/conversation-field-handler'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; /** * Configuration for the Conversation Directive @@ -58,11 +59,12 @@ export class ConversationTransformer extends TransformerPluginBase { hasManyTransformer: HasManyTransformer, belongsToTransformer: BelongsToTransformer, authProvider: TransformerAuthProvider, + functionNameMap?: Record, ) { super('amplify-conversation-transformer', ConversationDirective.definition); this.fieldHandler = new ConversationFieldHandler(); this.prepareHandler = new ConversationPrepareHandler(modelTransformer, hasManyTransformer, belongsToTransformer, authProvider); - this.resolverGenerator = new ConversationResolverGenerator(); + this.resolverGenerator = new ConversationResolverGenerator(functionNameMap); } /** diff --git a/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts b/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts index 1d272523c3..e6025fe373 100644 --- a/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts +++ b/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts @@ -5,7 +5,7 @@ import { APPSYNC_JS_RUNTIME, TransformerResolver } from '@aws-amplify/graphql-tr import { ResolverResourceIDs, FunctionResourceIDs, ResourceConstants, toUpper } from 'graphql-transformer-common'; import * as cdk from 'aws-cdk-lib'; import { conversation } from '@aws-amplify/ai-constructs'; -import { IFunction, Function } from 'aws-cdk-lib/aws-lambda'; +import { IFunction } from 'aws-cdk-lib/aws-lambda'; import { getModelDataSourceNameForTypeName, getTable } from '@aws-amplify/graphql-transformer-core'; import { initMappingTemplate } from '../resolvers/init-resolver'; import { authMappingTemplate } from '../resolvers/auth-resolver'; @@ -24,6 +24,8 @@ type KeyAttributeDefinition = { // TODO: add explanation for the tool model queries export class ConversationResolverGenerator { + constructor(private readonly functionNameMap?: Record) {} + generateResolvers(directives: ConversationDirectiveConfiguration[], ctx: TransformerContextProvider): void { for (const directive of directives) { this.processToolsForDirective(directive, ctx); @@ -89,7 +91,7 @@ export class ConversationResolverGenerator { capitalizedFieldName: string, ): { functionDataSourceId: string; referencedFunction: IFunction } { if (directive.functionName) { - return this.setupExistingFunctionDataSource(directive.functionName, functionStack); + return this.setupExistingFunctionDataSource(directive.functionName); } else { return this.setupDefaultConversationHandler(functionStack, capitalizedFieldName, directive.aiModel); } @@ -101,28 +103,18 @@ export class ConversationResolverGenerator { * @param functionStack - The CDK stack to add the function to * @returns An object containing the function data source ID and the referenced function */ - private setupExistingFunctionDataSource( - functionName: string, - functionStack: cdk.Stack, - ): { functionDataSourceId: string; referencedFunction: IFunction } { + private setupExistingFunctionDataSource(functionName: string): { functionDataSourceId: string; referencedFunction: IFunction } { const functionDataSourceId = FunctionResourceIDs.FunctionDataSourceID(functionName); - const referencedFunction = Function.fromFunctionAttributes(functionStack, `${functionDataSourceId}Function`, { - functionArn: this.lambdaArnResource(functionName), - }); - + if (!this.functionNameMap) { + throw new Error('Function name map is not provided'); + } + const referencedFunction = this.functionNameMap[functionName]; + if (!referencedFunction) { + throw new Error(`Function ${functionName} not found in function name map`); + } return { functionDataSourceId, referencedFunction }; } - /** - * Generates the Lambda ARN resource string - * @param name - The name of the Lambda function - * @returns The Lambda ARN resource string - */ - private lambdaArnResource(name: string): string { - // eslint-disable-next-line no-template-curly-in-string - return cdk.Fn.sub('arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${name}', { name }); - } - /** * Sets up a default conversation handler function * @param functionStack - The CDK stack to add the function to diff --git a/packages/amplify-graphql-transformer-test-utils/API.md b/packages/amplify-graphql-transformer-test-utils/API.md index e567073fbd..a605de9905 100644 --- a/packages/amplify-graphql-transformer-test-utils/API.md +++ b/packages/amplify-graphql-transformer-test-utils/API.md @@ -226,6 +226,7 @@ export type TestTransformParameters = RDSLayerMappingProvider & RDSSNSTopicMappi transformers: TransformerPluginProvider[]; transformParameters?: Partial; userDefinedSlots?: Record; + transformerManager?: TransformManager; }; // @public (undocumented) diff --git a/packages/amplify-graphql-transformer-test-utils/src/test-transform.ts b/packages/amplify-graphql-transformer-test-utils/src/test-transform.ts index 6dad410c01..f68492d51e 100644 --- a/packages/amplify-graphql-transformer-test-utils/src/test-transform.ts +++ b/packages/amplify-graphql-transformer-test-utils/src/test-transform.ts @@ -31,6 +31,7 @@ export type TestTransformParameters = RDSLayerMappingProvider & transformers: TransformerPluginProvider[]; transformParameters?: Partial; userDefinedSlots?: Record; + transformerManager?: TransformManager; }; /** @@ -52,6 +53,7 @@ export const testTransform = (params: TestTransformParameters): DeploymentResour transformers, transformParameters, userDefinedSlots, + transformerManager, } = params; const transform = new GraphQLTransform({ @@ -63,7 +65,7 @@ export const testTransform = (params: TestTransformParameters): DeploymentResour resolverConfig, }); - const transformManager = new TransformManager(overrideConfig); + const transformManager = transformerManager ?? new TransformManager(overrideConfig); const authConfigTypes = [authConfig?.defaultAuthentication, ...(authConfig?.additionalAuthenticationProviders ?? [])].map( (authConfigEntry) => authConfigEntry?.authenticationType, diff --git a/packages/amplify-graphql-transformer/src/graphql-transformer.ts b/packages/amplify-graphql-transformer/src/graphql-transformer.ts index 9c00780974..8d9499ab7d 100644 --- a/packages/amplify-graphql-transformer/src/graphql-transformer.ts +++ b/packages/amplify-graphql-transformer/src/graphql-transformer.ts @@ -78,7 +78,7 @@ export const constructTransformerChain = (options?: TransformerFactoryArgs): Tra hasOneTransformer, new ManyToManyTransformer(modelTransformer, indexTransformer, hasOneTransformer, authTransformer), belongsToTransformer, - new ConversationTransformer(modelTransformer, hasManyTransformer, belongsToTransformer, authTransformer), + new ConversationTransformer(modelTransformer, hasManyTransformer, belongsToTransformer, authTransformer, options?.functionNameMap), new GenerationTransformer(), new DefaultValueTransformer(), authTransformer, diff --git a/yarn.lock b/yarn.lock index a610c02ed5..ee3f6b4290 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4961,6 +4961,126 @@ esquery "^1.5.0" jsdoc-type-pratt-parser "~4.0.0" +"@esbuild/aix-ppc64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c" + integrity sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw== + +"@esbuild/android-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz#1add7e0af67acefd556e407f8497e81fddad79c0" + integrity sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w== + +"@esbuild/android-arm@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz#ab7263045fa8e090833a8e3c393b60d59a789810" + integrity sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew== + +"@esbuild/android-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz#e8f8b196cfdfdd5aeaebbdb0110983460440e705" + integrity sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ== + +"@esbuild/darwin-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz#2d0d9414f2acbffd2d86e98253914fca603a53dd" + integrity sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw== + +"@esbuild/darwin-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz#33087aab31a1eb64c89daf3d2cf8ce1775656107" + integrity sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA== + +"@esbuild/freebsd-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz#bb76e5ea9e97fa3c753472f19421075d3a33e8a7" + integrity sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA== + +"@esbuild/freebsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz#e0e2ce9249fdf6ee29e5dc3d420c7007fa579b93" + integrity sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ== + +"@esbuild/linux-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz#d1b2aa58085f73ecf45533c07c82d81235388e75" + integrity sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g== + +"@esbuild/linux-arm@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz#8e4915df8ea3e12b690a057e77a47b1d5935ef6d" + integrity sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw== + +"@esbuild/linux-ia32@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz#8200b1110666c39ab316572324b7af63d82013fb" + integrity sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA== + +"@esbuild/linux-loong64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz#6ff0c99cf647504df321d0640f0d32e557da745c" + integrity sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g== + +"@esbuild/linux-mips64el@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz#3f720ccd4d59bfeb4c2ce276a46b77ad380fa1f3" + integrity sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA== + +"@esbuild/linux-ppc64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz#9d6b188b15c25afd2e213474bf5f31e42e3aa09e" + integrity sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ== + +"@esbuild/linux-riscv64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz#f989fdc9752dfda286c9cd87c46248e4dfecbc25" + integrity sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw== + +"@esbuild/linux-s390x@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz#29ebf87e4132ea659c1489fce63cd8509d1c7319" + integrity sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g== + +"@esbuild/linux-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz#4af48c5c0479569b1f359ffbce22d15f261c0cef" + integrity sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA== + +"@esbuild/netbsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz#1ae73d23cc044a0ebd4f198334416fb26c31366c" + integrity sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg== + +"@esbuild/openbsd-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz#5d904a4f5158c89859fd902c427f96d6a9e632e2" + integrity sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg== + +"@esbuild/openbsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz#4c8aa88c49187c601bae2971e71c6dc5e0ad1cdf" + integrity sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q== + +"@esbuild/sunos-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz#8ddc35a0ea38575fa44eda30a5ee01ae2fa54dd4" + integrity sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA== + +"@esbuild/win32-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz#6e79c8543f282c4539db684a207ae0e174a9007b" + integrity sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA== + +"@esbuild/win32-ia32@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz#057af345da256b7192d18b676a02e95d0fa39103" + integrity sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw== + +"@esbuild/win32-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz#168ab1c7e1c318b922637fad8f339d48b01e1244" + integrity sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA== + "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -10086,6 +10206,36 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.0.tgz#f2d470596885fcb2e91c21eb3da3b3c89c0b55e7" + integrity sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ== + optionalDependencies: + "@esbuild/aix-ppc64" "0.24.0" + "@esbuild/android-arm" "0.24.0" + "@esbuild/android-arm64" "0.24.0" + "@esbuild/android-x64" "0.24.0" + "@esbuild/darwin-arm64" "0.24.0" + "@esbuild/darwin-x64" "0.24.0" + "@esbuild/freebsd-arm64" "0.24.0" + "@esbuild/freebsd-x64" "0.24.0" + "@esbuild/linux-arm" "0.24.0" + "@esbuild/linux-arm64" "0.24.0" + "@esbuild/linux-ia32" "0.24.0" + "@esbuild/linux-loong64" "0.24.0" + "@esbuild/linux-mips64el" "0.24.0" + "@esbuild/linux-ppc64" "0.24.0" + "@esbuild/linux-riscv64" "0.24.0" + "@esbuild/linux-s390x" "0.24.0" + "@esbuild/linux-x64" "0.24.0" + "@esbuild/netbsd-x64" "0.24.0" + "@esbuild/openbsd-arm64" "0.24.0" + "@esbuild/openbsd-x64" "0.24.0" + "@esbuild/sunos-x64" "0.24.0" + "@esbuild/win32-arm64" "0.24.0" + "@esbuild/win32-ia32" "0.24.0" + "@esbuild/win32-x64" "0.24.0" + escalade@^3.1.1, escalade@^3.1.2: version "3.2.0" resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" From db34e424069b9d2ebf2a51b3474d2f83644e3174 Mon Sep 17 00:00:00 2001 From: Ian Saultz <52051793+atierian@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:03:49 -0400 Subject: [PATCH 10/15] fix(generation): gracefully handle stringified tool_use responses (#2919) --- .prettierignore | 1 + .../src/__tests__/generations/API.ts | 6 +- .../__tests__/generations/graphql/queries.ts | 1 - .../graphql/schema-generation.graphql | 3 +- ...raphql-generation-transformer.test.ts.snap | 197 +++++++++++++----- .../invoke-bedrock-resolver-fn.template.js | 33 ++- .../src/resolvers/invoke-bedrock.ts | 12 ++ .../src/utils/tools.ts | 4 +- 8 files changed, 185 insertions(+), 72 deletions(-) diff --git a/.prettierignore b/.prettierignore index d8801ad33a..747b549f25 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,6 +15,7 @@ packages/amplify-graphql-api-construct/README.md packages/amplify-graphql-api-construct/tsconfig.json packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/*.graphql packages/amplify-graphql-conversation-transformer/src/resolvers/*.template.js +packages/amplify-graphql-generation-transformer/src/resolvers/*.template.js packages/amplify-data-construct/README.md packages/amplify-data-construct/tsconfig.json packages/amplify-graphql-model-transformer/publish-notification-lambda/lib/ diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/API.ts b/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/API.ts index 2795eea455..bb9cce4ed6 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/API.ts +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/API.ts @@ -5,8 +5,7 @@ export type Recipe = { __typename: 'Recipe'; ingredients?: Array | null; - instructions?: string | null; - name?: string | null; + name: string; }; export type Todo = { @@ -26,8 +25,7 @@ export type GenerateRecipeQuery = { generateRecipe?: { __typename: 'Recipe'; ingredients?: Array | null; - instructions?: string | null; - name?: string | null; + name: string; } | null; }; diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/queries.ts b/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/queries.ts index 39ff593a2f..4feb06750b 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/queries.ts +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/queries.ts @@ -11,7 +11,6 @@ type GeneratedQuery = string & { export const generateRecipe = /* GraphQL */ `query GenerateRecipe($description: String) { generateRecipe(description: $description) { ingredients - instructions name __typename } diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/schema-generation.graphql b/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/schema-generation.graphql index 49cb839eec..334ebec9c8 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/schema-generation.graphql +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/schema-generation.graphql @@ -1,7 +1,6 @@ type Recipe { - name: String + name: String! ingredients: [String] - instructions: String } type Query { diff --git a/packages/amplify-graphql-generation-transformer/src/__tests__/__snapshots__/amplify-graphql-generation-transformer.test.ts.snap b/packages/amplify-graphql-generation-transformer/src/__tests__/__snapshots__/amplify-graphql-generation-transformer.test.ts.snap index d6dbbbdc2d..5af359d090 100644 --- a/packages/amplify-graphql-generation-transformer/src/__tests__/__snapshots__/amplify-graphql-generation-transformer.test.ts.snap +++ b/packages/amplify-graphql-generation-transformer/src/__tests__/__snapshots__/amplify-graphql-generation-transformer.test.ts.snap @@ -57,8 +57,10 @@ export const response = (ctx) => { exports[`generation route all scalar types 2`] = ` { - "makeBox-invoke-bedrock-fn": "export function request(ctx) { - const toolConfig = {"tools":[{"toolSpec":{"name":"responseType","description":"Generate a response type for the given field","inputSchema":{"json":{"type":"object","properties":{"value":{"type":"object","properties":{"int":{"type":"number","description":"A signed 32-bit integer value."},"float":{"type":"number","description":"An IEEE 754 floating point value."},"string":{"type":"string","description":"A UTF-8 character sequence."},"id":{"type":"string","description":"A unique identifier for an object. This scalar is serialized like a String but isn't meant to be human-readable."},"boolean":{"type":"boolean","description":"A boolean value."},"awsjson":{"type":"string","description":"A JSON string. Any valid JSON construct is automatically parsed and loaded in the resolver code as maps, lists, or scalar values rather than as the literal input strings. Unquoted strings or otherwise invalid JSON result in a GraphQL validation error."},"awsemail":{"type":"string","description":"An email address in the format local-part@domain-part as defined by RFC 822.","pattern":"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$"},"awsdate":{"type":"string","description":"An extended ISO 8601 date string in the format YYYY-MM-DD.","pattern":"^\\\\d{4}-d{2}-d{2}$"},"awstime":{"type":"string","description":"An extended ISO 8601 time string in the format hh:mm:ss.sss.","pattern":"^\\\\d{2}:\\\\d{2}:\\\\d{2}\\\\.\\\\d{3}$"},"awsdatetime":{"type":"string","description":"An extended ISO 8601 date and time string in the format YYYY-MM-DDThh:mm:ss.sssZ.","pattern":"^\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}\\\\.\\\\d{3}Z$"},"awstimestamp":{"type":"string","description":"An integer value representing the number of seconds before or after 1970-01-01-T00:00Z.","pattern":"^\\\\d+$"},"awsphone":{"type":"string","description":"A phone number. This value is stored as a string. Phone numbers can contain either spaces or hyphens to separate digit groups. Phone numbers without a country code are assumed to be US/North American numbers adhering to the North American Numbering Plan (NANP).","pattern":"^\\\\d{3}-d{3}-d{4}$"},"awsurl":{"type":"string","description":"A URL as defined by RFC 1738. For example, https://www.amazon.com/dp/B000NZW3KC/ or mailto:example@example.com. URLs must contain a schema (http, mailto) and can't contain two forward slashes (//) in the path part.","pattern":"^(https?|mailto)://[^s/$.?#].[^s]*$"},"awsipaddress":{"type":"string","description":"A valid IPv4 or IPv6 address. IPv4 addresses are expected in quad-dotted notation (123.12.34.56). IPv6 addresses are expected in non-bracketed, colon-separated format (1a2b:3c4b::1234:4567). You can include an optional CIDR suffix (123.45.67.89/16) to indicate subnet mask."}},"required":[]}},"required":["value"]}}}}],"toolChoice":{"tool":{"name":"responseType"}}}; + "makeBox-invoke-bedrock-fn": "import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const toolConfig = {"tools":[{"toolSpec":{"name":"responseType","description":"Generate a response type for the given field.","inputSchema":{"json":{"type":"object","properties":{"value":{"type":"object","properties":{"int":{"type":"number","description":"A signed 32-bit integer value."},"float":{"type":"number","description":"An IEEE 754 floating point value."},"string":{"type":"string","description":"A UTF-8 character sequence."},"id":{"type":"string","description":"A unique identifier for an object. This scalar is serialized like a String but isn't meant to be human-readable."},"boolean":{"type":"boolean","description":"A boolean value."},"awsjson":{"type":"string","description":"A JSON string. Any valid JSON construct is automatically parsed and loaded in the resolver code as maps, lists, or scalar values rather than as the literal input strings. Unquoted strings or otherwise invalid JSON result in a GraphQL validation error."},"awsemail":{"type":"string","description":"An email address in the format local-part@domain-part as defined by RFC 822.","pattern":"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$"},"awsdate":{"type":"string","description":"An extended ISO 8601 date string in the format YYYY-MM-DD.","pattern":"^\\\\d{4}-d{2}-d{2}$"},"awstime":{"type":"string","description":"An extended ISO 8601 time string in the format hh:mm:ss.sss.","pattern":"^\\\\d{2}:\\\\d{2}:\\\\d{2}\\\\.\\\\d{3}$"},"awsdatetime":{"type":"string","description":"An extended ISO 8601 date and time string in the format YYYY-MM-DDThh:mm:ss.sssZ.","pattern":"^\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}\\\\.\\\\d{3}Z$"},"awstimestamp":{"type":"string","description":"An integer value representing the number of seconds before or after 1970-01-01-T00:00Z.","pattern":"^\\\\d+$"},"awsphone":{"type":"string","description":"A phone number. This value is stored as a string. Phone numbers can contain either spaces or hyphens to separate digit groups. Phone numbers without a country code are assumed to be US/North American numbers adhering to the North American Numbering Plan (NANP).","pattern":"^\\\\d{3}-d{3}-d{4}$"},"awsurl":{"type":"string","description":"A URL as defined by RFC 1738. For example, https://www.amazon.com/dp/B000NZW3KC/ or mailto:example@example.com. URLs must contain a schema (http, mailto) and can't contain two forward slashes (//) in the path part.","pattern":"^(https?|mailto)://[^s/$.?#].[^s]*$"},"awsipaddress":{"type":"string","description":"A valid IPv4 or IPv6 address. IPv4 addresses are expected in quad-dotted notation (123.12.34.56). IPv6 addresses are expected in non-bracketed, colon-separated format (1a2b:3c4b::1234:4567). You can include an optional CIDR suffix (123.45.67.89/16) to indicate subnet mask."}},"required":[]}},"required":["value"]}}}}],"toolChoice":{"tool":{"name":"responseType"}}}; const prompt = ""; const args = JSON.stringify(ctx.args); const inferenceConfig = undefined; @@ -87,20 +89,37 @@ export function response(ctx) { if (ctx.error) { util.error(ctx.error.message, ctx.error.type); } - const body = JSON.parse(ctx.result.body); - const { content } = body.output.message; - if (content.length < 1) { - util.error('No content block in assistant response.', 'error'); + // Check for AccessDeniedException. + // This can happen if: + // 1. The Bedrock model isn't enabled in this region. + // 2. The IAM policy statement for the role assumed by the data source for this resolver doesn't include them model. + // This shouldn't happen because we're managing the policy statements. + // We're using a generic error description here (as opposed to using the response body) + // to prevent information about the system from leaking. + const errorType = ctx.result.headers['x-amzn-ErrorType']; + if (errorType) { + if (errorType.startsWith('AccessDeniedException')) { + const errorMessage = 'The model is disabled or this generation route is missing a necessary identity-based policy.'; + util.error(errorMessage, 'AccessDeniedException'); + } + + util.error('', errorType); } - const toolUse = content[0].toolUse; - if (!toolUse) { - util.error('Missing tool use block in assistant response.', 'error'); + const body = JSON.parse(ctx.result.body); + const value = body?.output?.message?.content?.[0]?.toolUse?.input?.value; + + if (!value) { + util.error('Invalid Bedrock response', 'InvalidResponseException'); } - const response = toolUse.input.value; - return response; + // Added for non-string scalar response types + // This catches the occasional stringified JSON response. + if (typeof value === 'string') { + return JSON.parse(value); + } + return value; } ", } @@ -163,8 +182,10 @@ export const response = (ctx) => { exports[`generation route custom query 2`] = ` { - "generateRecipe-invoke-bedrock-fn": "export function request(ctx) { - const toolConfig = {"tools":[{"toolSpec":{"name":"responseType","description":"Generate a response type for the given field","inputSchema":{"json":{"type":"object","properties":{"value":{"type":"object","properties":{"name":{"type":"string","description":"A UTF-8 character sequence."},"ingredients":{"type":"array","items":{"type":"string","description":"A UTF-8 character sequence."}},"instructions":{"type":"string","description":"A UTF-8 character sequence."},"meal":{"type":"object","properties":{"Meal":{"type":"string","enum":["BREAKFAST","LUNCH","DINNER"]}},"required":[]}},"required":[]}},"required":["value"]}}}}],"toolChoice":{"tool":{"name":"responseType"}}}; + "generateRecipe-invoke-bedrock-fn": "import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const toolConfig = {"tools":[{"toolSpec":{"name":"responseType","description":"Generate a response type for the given field.","inputSchema":{"json":{"type":"object","properties":{"value":{"type":"object","properties":{"name":{"type":"string","description":"A UTF-8 character sequence."},"ingredients":{"type":"array","items":{"type":"string","description":"A UTF-8 character sequence."}},"instructions":{"type":"string","description":"A UTF-8 character sequence."},"meal":{"type":"object","properties":{"Meal":{"type":"string","enum":["BREAKFAST","LUNCH","DINNER"]}},"required":[]}},"required":[]}},"required":["value"]}}}}],"toolChoice":{"tool":{"name":"responseType"}}}; const prompt = "You are a helpful assistant that generates recipes."; const args = JSON.stringify(ctx.args); const inferenceConfig = undefined; @@ -193,20 +214,37 @@ export function response(ctx) { if (ctx.error) { util.error(ctx.error.message, ctx.error.type); } - const body = JSON.parse(ctx.result.body); - const { content } = body.output.message; - if (content.length < 1) { - util.error('No content block in assistant response.', 'error'); + // Check for AccessDeniedException. + // This can happen if: + // 1. The Bedrock model isn't enabled in this region. + // 2. The IAM policy statement for the role assumed by the data source for this resolver doesn't include them model. + // This shouldn't happen because we're managing the policy statements. + // We're using a generic error description here (as opposed to using the response body) + // to prevent information about the system from leaking. + const errorType = ctx.result.headers['x-amzn-ErrorType']; + if (errorType) { + if (errorType.startsWith('AccessDeniedException')) { + const errorMessage = 'The model is disabled or this generation route is missing a necessary identity-based policy.'; + util.error(errorMessage, 'AccessDeniedException'); + } + + util.error('', errorType); } - const toolUse = content[0].toolUse; - if (!toolUse) { - util.error('Missing tool use block in assistant response.', 'error'); + const body = JSON.parse(ctx.result.body); + const value = body?.output?.message?.content?.[0]?.toolUse?.input?.value; + + if (!value) { + util.error('Invalid Bedrock response', 'InvalidResponseException'); } - const response = toolUse.input.value; - return response; + // Added for non-string scalar response types + // This catches the occasional stringified JSON response. + if (typeof value === 'string') { + return JSON.parse(value); + } + return value; } ", } @@ -268,8 +306,10 @@ export const response = (ctx) => { `; exports[`generation route model type with null timestamps 2`] = ` -"export function request(ctx) { - const toolConfig = {"tools":[{"toolSpec":{"name":"responseType","description":"Generate a response type for the given field","inputSchema":{"json":{"type":"object","properties":{"value":{"type":"object","properties":{"content":{"type":"string","description":"A UTF-8 character sequence."},"isDone":{"type":"boolean","description":"A boolean value."},"id":{"type":"string","description":"A unique identifier for an object. This scalar is serialized like a String but isn't meant to be human-readable."}},"required":["id"]}},"required":["value"]}}}}],"toolChoice":{"tool":{"name":"responseType"}}}; +"import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const toolConfig = {"tools":[{"toolSpec":{"name":"responseType","description":"Generate a response type for the given field.","inputSchema":{"json":{"type":"object","properties":{"value":{"type":"object","properties":{"content":{"type":"string","description":"A UTF-8 character sequence."},"isDone":{"type":"boolean","description":"A boolean value."},"id":{"type":"string","description":"A unique identifier for an object. This scalar is serialized like a String but isn't meant to be human-readable."}},"required":["id"]}},"required":["value"]}}}}],"toolChoice":{"tool":{"name":"responseType"}}}; const prompt = "Make a string based on the description."; const args = JSON.stringify(ctx.args); const inferenceConfig = undefined; @@ -298,20 +338,37 @@ export function response(ctx) { if (ctx.error) { util.error(ctx.error.message, ctx.error.type); } - const body = JSON.parse(ctx.result.body); - const { content } = body.output.message; - if (content.length < 1) { - util.error('No content block in assistant response.', 'error'); + // Check for AccessDeniedException. + // This can happen if: + // 1. The Bedrock model isn't enabled in this region. + // 2. The IAM policy statement for the role assumed by the data source for this resolver doesn't include them model. + // This shouldn't happen because we're managing the policy statements. + // We're using a generic error description here (as opposed to using the response body) + // to prevent information about the system from leaking. + const errorType = ctx.result.headers['x-amzn-ErrorType']; + if (errorType) { + if (errorType.startsWith('AccessDeniedException')) { + const errorMessage = 'The model is disabled or this generation route is missing a necessary identity-based policy.'; + util.error(errorMessage, 'AccessDeniedException'); + } + + util.error('', errorType); } - const toolUse = content[0].toolUse; - if (!toolUse) { - util.error('Missing tool use block in assistant response.', 'error'); + const body = JSON.parse(ctx.result.body); + const value = body?.output?.message?.content?.[0]?.toolUse?.input?.value; + + if (!value) { + util.error('Invalid Bedrock response', 'InvalidResponseException'); } - const response = toolUse.input.value; - return response; + // Added for non-string scalar response types + // This catches the occasional stringified JSON response. + if (typeof value === 'string') { + return JSON.parse(value); + } + return value; } " `; @@ -390,8 +447,10 @@ $util.toJson({"version":"2018-05-29","payload":{}}) "Query.makeTodo.auth.1.res.vtl": "## [Start] Return Source Field. ** $util.toJson($context.source["makeTodo"]) ## [End] Return Source Field. **", - "makeTodo-invoke-bedrock-fn": "export function request(ctx) { - const toolConfig = {"tools":[{"toolSpec":{"name":"responseType","description":"Generate a response type for the given field","inputSchema":{"json":{"type":"object","properties":{"value":{"type":"string","description":"A UTF-8 character sequence."}},"required":["value"]}}}}],"toolChoice":{"tool":{"name":"responseType"}}}; + "makeTodo-invoke-bedrock-fn": "import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const toolConfig = {"tools":[{"toolSpec":{"name":"responseType","description":"Generate a response type for the given field.","inputSchema":{"json":{"type":"object","properties":{"value":{"type":"string","description":"A UTF-8 character sequence."}},"required":["value"]}}}}],"toolChoice":{"tool":{"name":"responseType"}}}; const prompt = "Make a string based on the description."; const args = JSON.stringify(ctx.args); const inferenceConfig = undefined; @@ -420,20 +479,33 @@ export function response(ctx) { if (ctx.error) { util.error(ctx.error.message, ctx.error.type); } - const body = JSON.parse(ctx.result.body); - const { content } = body.output.message; - if (content.length < 1) { - util.error('No content block in assistant response.', 'error'); + // Check for AccessDeniedException. + // This can happen if: + // 1. The Bedrock model isn't enabled in this region. + // 2. The IAM policy statement for the role assumed by the data source for this resolver doesn't include them model. + // This shouldn't happen because we're managing the policy statements. + // We're using a generic error description here (as opposed to using the response body) + // to prevent information about the system from leaking. + const errorType = ctx.result.headers['x-amzn-ErrorType']; + if (errorType) { + if (errorType.startsWith('AccessDeniedException')) { + const errorMessage = 'The model is disabled or this generation route is missing a necessary identity-based policy.'; + util.error(errorMessage, 'AccessDeniedException'); + } + + util.error('', errorType); } - const toolUse = content[0].toolUse; - if (!toolUse) { - util.error('Missing tool use block in assistant response.', 'error'); + const body = JSON.parse(ctx.result.body); + const value = body?.output?.message?.content?.[0]?.toolUse?.input?.value; + + if (!value) { + util.error('Invalid Bedrock response', 'InvalidResponseException'); } - const response = toolUse.input.value; - return response; + + return value; } ", } @@ -441,8 +513,10 @@ export function response(ctx) { exports[`generation route with valid inference configuration 1`] = ` { - "generateWithConfig-invoke-bedrock-fn": "export function request(ctx) { - const toolConfig = {"tools":[{"toolSpec":{"name":"responseType","description":"Generate a response type for the given field","inputSchema":{"json":{"type":"object","properties":{"value":{"type":"string","description":"A UTF-8 character sequence."}},"required":["value"]}}}}],"toolChoice":{"tool":{"name":"responseType"}}}; + "generateWithConfig-invoke-bedrock-fn": "import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const toolConfig = {"tools":[{"toolSpec":{"name":"responseType","description":"Generate a response type for the given field.","inputSchema":{"json":{"type":"object","properties":{"value":{"type":"string","description":"A UTF-8 character sequence."}},"required":["value"]}}}}],"toolChoice":{"tool":{"name":"responseType"}}}; const prompt = "Generate a string based on the description."; const args = JSON.stringify(ctx.args); const inferenceConfig = { inferenceConfig: {"maxTokens":100,"temperature":0.7,"topP":0.9} },; @@ -471,20 +545,33 @@ export function response(ctx) { if (ctx.error) { util.error(ctx.error.message, ctx.error.type); } - const body = JSON.parse(ctx.result.body); - const { content } = body.output.message; - if (content.length < 1) { - util.error('No content block in assistant response.', 'error'); + // Check for AccessDeniedException. + // This can happen if: + // 1. The Bedrock model isn't enabled in this region. + // 2. The IAM policy statement for the role assumed by the data source for this resolver doesn't include them model. + // This shouldn't happen because we're managing the policy statements. + // We're using a generic error description here (as opposed to using the response body) + // to prevent information about the system from leaking. + const errorType = ctx.result.headers['x-amzn-ErrorType']; + if (errorType) { + if (errorType.startsWith('AccessDeniedException')) { + const errorMessage = 'The model is disabled or this generation route is missing a necessary identity-based policy.'; + util.error(errorMessage, 'AccessDeniedException'); + } + + util.error('', errorType); } - const toolUse = content[0].toolUse; - if (!toolUse) { - util.error('Missing tool use block in assistant response.', 'error'); + const body = JSON.parse(ctx.result.body); + const value = body?.output?.message?.content?.[0]?.toolUse?.input?.value; + + if (!value) { + util.error('Invalid Bedrock response', 'InvalidResponseException'); } - const response = toolUse.input.value; - return response; + + return value; } ", } diff --git a/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock-resolver-fn.template.js b/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock-resolver-fn.template.js index bd48071ed6..fb7c0cb795 100644 --- a/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock-resolver-fn.template.js +++ b/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock-resolver-fn.template.js @@ -1,3 +1,5 @@ +import { util } from '@aws-appsync/utils'; + export function request(ctx) { const toolConfig = [[TOOL_CONFIG]]; const prompt = [[SYSTEM_PROMPT]]; @@ -28,18 +30,31 @@ export function response(ctx) { if (ctx.error) { util.error(ctx.error.message, ctx.error.type); } - const body = JSON.parse(ctx.result.body); - const { content } = body.output.message; - if (content.length < 1) { - util.error('No content block in assistant response.', 'error'); + // Check for AccessDeniedException. + // This can happen if: + // 1. The Bedrock model isn't enabled in this region. + // 2. The IAM policy statement for the role assumed by the data source for this resolver doesn't include them model. + // This shouldn't happen because we're managing the policy statements. + // We're using a generic error description here (as opposed to using the response body) + // to prevent information about the system from leaking. + const errorType = ctx.result.headers['x-amzn-ErrorType']; + if (errorType) { + if (errorType.startsWith('AccessDeniedException')) { + const errorMessage = 'The model is disabled or this generation route is missing a necessary identity-based policy.'; + util.error(errorMessage, 'AccessDeniedException'); + } + + util.error('', errorType); } - const toolUse = content[0].toolUse; - if (!toolUse) { - util.error('Missing tool use block in assistant response.', 'error'); + const body = JSON.parse(ctx.result.body); + const value = body?.output?.message?.content?.[0]?.toolUse?.input?.value; + + if (!value) { + util.error('Invalid Bedrock response', 'InvalidResponseException'); } - const response = toolUse.input.value; - return response; + [[NON_STRING_RESPONSE_HANDLING]] + return value; } diff --git a/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock.ts b/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock.ts index 0a56488511..1bb95f81b5 100644 --- a/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock.ts +++ b/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock.ts @@ -3,6 +3,7 @@ import { MappingTemplateProvider } from '@aws-amplify/graphql-transformer-interf import { GenerationConfigurationWithToolConfig, InferenceConfiguration } from '../grapqhl-generation-transformer'; import fs from 'fs'; import path from 'path'; +import { getBaseType } from 'graphql-transformer-common'; /** * Creates the resolver functions for invoking Amazon Bedrock. @@ -17,11 +18,20 @@ export const createInvokeBedrockResolverFunction = (config: GenerationConfigurat const SYSTEM_PROMPT = JSON.stringify(config.systemPrompt); const INFERENCE_CONFIG = getInferenceConfigResolverDefinition(inferenceConfiguration); + const NON_STRING_RESPONSE_HANDLING = stringTypedScalarTypes.includes(getBaseType(config.field.type)) + ? '' + : `// Added for non-string scalar response types + // This catches the occasional stringified JSON response. + if (typeof value === 'string') { + return JSON.parse(value); + }`; + const resolver = generateResolver('invoke-bedrock-resolver-fn.template.js', { AI_MODEL, TOOL_CONFIG, SYSTEM_PROMPT, INFERENCE_CONFIG, + NON_STRING_RESPONSE_HANDLING, }); const templateName = `${field.name.value}-invoke-bedrock-fn`; @@ -48,3 +58,5 @@ const getInferenceConfigResolverDefinition = (inferenceConfiguration?: Inference ? `{ inferenceConfig: ${JSON.stringify(inferenceConfiguration)} },` : 'undefined'; }; + +const stringTypedScalarTypes = ['String', 'ID', 'AWSJSON']; diff --git a/packages/amplify-graphql-generation-transformer/src/utils/tools.ts b/packages/amplify-graphql-generation-transformer/src/utils/tools.ts index efa0976d21..4c990da972 100644 --- a/packages/amplify-graphql-generation-transformer/src/utils/tools.ts +++ b/packages/amplify-graphql-generation-transformer/src/utils/tools.ts @@ -56,11 +56,13 @@ export const createResponseTypeTool = (field: FieldDefinitionNode, ctx: Transfor }, required: ['value'], }; + + const description = 'Generate a response type for the given field.'; const tools: Tool[] = [ { toolSpec: { name: 'responseType', - description: 'Generate a response type for the given field', + description, inputSchema: { json: boxedSchema, }, From 874a30ace18885f63c6be3274f32e4331bca98ed Mon Sep 17 00:00:00 2001 From: Ian Saultz <52051793+atierian@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:43:39 -0400 Subject: [PATCH 11/15] feat(conversation): per message items and lambda history retrieval pattern (#2914) --- packages/amplify-data-construct/.jsii | 4 +- packages/amplify-data-construct/package.json | 2 +- .../src/__tests__/conversations/API.ts | 30 ------ .../conversations/graphql/mutations.ts | 16 ---- .../conversations/graphql/queries.ts | 4 - packages/amplify-graphql-api-construct/.jsii | 4 +- .../package.json | 2 +- .../package.json | 2 +- ...phql-conversation-transformer.test.ts.snap | 80 ++++++++++++---- ...y-graphql-conversation-transformer.test.ts | 5 + .../schemas/conversation-schema-types.graphql | 1 + .../src/graphql-types/message-model.ts | 4 +- ...assistant-mutation-resolver-fn.template.js | 49 ++++------ .../resolvers/assistant-mutation-resolver.ts | 11 ++- .../invoke-lambda-resolver-fn.template.js | 15 ++- .../src/resolvers/invoke-lambda-resolver.ts | 15 +++ ...list-messages-init-resolver-fn.template.js | 8 ++ .../resolvers/list-messages-init-resolver.ts | 16 ++++ .../message-history-resolver-fn.template.js | 40 -------- .../src/resolvers/message-history-resolver.ts | 16 ---- ...rify-session-owner-resolver-fn.template.js | 3 +- .../verify-session-owner-resolver.ts | 30 +++++- .../write-message-to-table-resolver.ts | 7 +- .../conversation-resolver-generator.ts | 96 ++++++++++++++----- yarn.lock | 8 +- 25 files changed, 262 insertions(+), 206 deletions(-) create mode 100644 packages/amplify-graphql-conversation-transformer/src/resolvers/list-messages-init-resolver-fn.template.js create mode 100644 packages/amplify-graphql-conversation-transformer/src/resolvers/list-messages-init-resolver.ts delete mode 100644 packages/amplify-graphql-conversation-transformer/src/resolvers/message-history-resolver-fn.template.js delete mode 100644 packages/amplify-graphql-conversation-transformer/src/resolvers/message-history-resolver.ts diff --git a/packages/amplify-data-construct/.jsii b/packages/amplify-data-construct/.jsii index 2db7108892..328098c1c2 100644 --- a/packages/amplify-data-construct/.jsii +++ b/packages/amplify-data-construct/.jsii @@ -6,7 +6,7 @@ ] }, "bundled": { - "@aws-amplify/ai-constructs": "^0.1.4", + "@aws-amplify/ai-constructs": "^0.2.0", "@aws-amplify/backend-output-schemas": "^1.0.0", "@aws-amplify/backend-output-storage": "^1.0.0", "@aws-amplify/graphql-auth-transformer": "4.1.2", @@ -4027,5 +4027,5 @@ }, "types": {}, "version": "1.10.2", - "fingerprint": "naLvPjdr9z9wuDIK2e15PtMBvodNvsprfPj2tyr4FaA=" + "fingerprint": "jyWXx9H104F/Z3YgGPhDKqRig51p9ImclAMN35IID8o=" } \ No newline at end of file diff --git a/packages/amplify-data-construct/package.json b/packages/amplify-data-construct/package.json index cdc8c53c16..e1a646b78f 100644 --- a/packages/amplify-data-construct/package.json +++ b/packages/amplify-data-construct/package.json @@ -157,7 +157,7 @@ "semver" ], "dependencies": { - "@aws-amplify/ai-constructs": "^0.1.4", + "@aws-amplify/ai-constructs": "^0.2.0", "@aws-amplify/backend-output-schemas": "^1.0.0", "@aws-amplify/backend-output-storage": "^1.0.0", "@aws-amplify/graphql-api-construct": "1.14.0", diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/API.ts b/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/API.ts index ed262bb185..2ce7d3e80c 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/API.ts +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/API.ts @@ -5,7 +5,6 @@ export type ConversationMessagePirateChat = { __typename: 'ConversationMessagePirateChat'; aiContext?: string | null; - assistantContent?: Array | null; content?: Array | null; conversation?: ConversationPirateChat | null; conversationId: string; @@ -285,7 +284,6 @@ export type ModelConversationMessagePirateChatConditionInput = { export type CreateConversationMessagePirateChatInput = { aiContext?: string | null; - assistantContent?: Array | null; content?: Array | null; conversationId: string; id?: string | null; @@ -386,10 +384,6 @@ export type GetConversationMessagePirateChatQuery = { getConversationMessagePirateChat?: { __typename: 'ConversationMessagePirateChat'; aiContext?: string | null; - assistantContent?: Array<{ - __typename: 'ContentBlock'; - text?: string | null; - } | null> | null; content?: Array<{ __typename: 'ContentBlock'; text?: string | null; @@ -488,10 +482,6 @@ export type CreateAssistantResponsePirateChatMutation = { createAssistantResponsePirateChat?: { __typename: 'ConversationMessagePirateChat'; aiContext?: string | null; - assistantContent?: Array<{ - __typename: 'ContentBlock'; - text?: string | null; - } | null> | null; content?: Array<{ __typename: 'ContentBlock'; text?: string | null; @@ -526,10 +516,6 @@ export type CreateConversationMessagePirateChatMutation = { createConversationMessagePirateChat?: { __typename: 'ConversationMessagePirateChat'; aiContext?: string | null; - assistantContent?: Array<{ - __typename: 'ContentBlock'; - text?: string | null; - } | null> | null; content?: Array<{ __typename: 'ContentBlock'; text?: string | null; @@ -585,10 +571,6 @@ export type DeleteConversationMessagePirateChatMutation = { deleteConversationMessagePirateChat?: { __typename: 'ConversationMessagePirateChat'; aiContext?: string | null; - assistantContent?: Array<{ - __typename: 'ContentBlock'; - text?: string | null; - } | null> | null; content?: Array<{ __typename: 'ContentBlock'; text?: string | null; @@ -659,10 +641,6 @@ export type PirateChatMutation = { __typename: 'ToolConfiguration'; } | null; updatedAt?: string | null; - assistantContent?: Array<{ - __typename: 'ContentBlock'; - text?: string | null; - } | null> | null; conversation?: { __typename: 'ConversationPirateChat'; createdAt: string; @@ -683,10 +661,6 @@ export type OnCreateAssistantResponsePirateChatSubscription = { onCreateAssistantResponsePirateChat?: { __typename: 'ConversationMessagePirateChat'; aiContext?: string | null; - assistantContent?: Array<{ - __typename: 'ContentBlock'; - text?: string | null; - } | null> | null; content?: Array<{ __typename: 'ContentBlock'; text?: string | null; @@ -721,10 +695,6 @@ export type OnCreateConversationMessagePirateChatSubscription = { onCreateConversationMessagePirateChat?: { __typename: 'ConversationMessagePirateChat'; aiContext?: string | null; - assistantContent?: Array<{ - __typename: 'ContentBlock'; - text?: string | null; - } | null> | null; content?: Array<{ __typename: 'ContentBlock'; text?: string | null; diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/graphql/mutations.ts b/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/graphql/mutations.ts index 084cacf909..0370ddbb53 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/graphql/mutations.ts +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/graphql/mutations.ts @@ -13,10 +13,6 @@ export const createAssistantResponsePirateChat = /* GraphQL */ `mutation CreateA ) { createAssistantResponsePirateChat(input: $input) { aiContext - assistantContent { - text - __typename - } content { text __typename @@ -49,10 +45,6 @@ export const createConversationMessagePirateChat = /* GraphQL */ `mutation Creat ) { createConversationMessagePirateChat(condition: $condition, input: $input) { aiContext - assistantContent { - text - __typename - } content { text __typename @@ -104,10 +96,6 @@ export const deleteConversationMessagePirateChat = /* GraphQL */ `mutation Delet ) { deleteConversationMessagePirateChat(condition: $condition, input: $input) { aiContext - assistantContent { - text - __typename - } content { text __typename @@ -181,10 +169,6 @@ export const pirateChat = /* GraphQL */ `mutation PirateChat( updatedAt ... on ConversationMessagePirateChat { - assistantContent { - text - __typename - } conversation { createdAt id diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/graphql/queries.ts b/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/graphql/queries.ts index 59ddfd0114..3f63a5d645 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/graphql/queries.ts +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/graphql/queries.ts @@ -11,10 +11,6 @@ type GeneratedQuery = string & { export const getConversationMessagePirateChat = /* GraphQL */ `query GetConversationMessagePirateChat($id: ID!) { getConversationMessagePirateChat(id: $id) { aiContext - assistantContent { - text - __typename - } content { text __typename diff --git a/packages/amplify-graphql-api-construct/.jsii b/packages/amplify-graphql-api-construct/.jsii index e8c89a0f8a..d00875fe9b 100644 --- a/packages/amplify-graphql-api-construct/.jsii +++ b/packages/amplify-graphql-api-construct/.jsii @@ -6,7 +6,7 @@ ] }, "bundled": { - "@aws-amplify/ai-constructs": "^0.1.4", + "@aws-amplify/ai-constructs": "^0.2.0", "@aws-amplify/backend-output-schemas": "^1.0.0", "@aws-amplify/backend-output-storage": "^1.0.0", "@aws-amplify/graphql-auth-transformer": "4.1.2", @@ -8959,5 +8959,5 @@ } }, "version": "1.14.0", - "fingerprint": "u4jHwzwBKRAlcky7o7NnOthEdgdtKR1CtWBsGA5tXq4=" + "fingerprint": "BFQM638XQPsnf3jPECgT+5lULFJd0VyEXzkdPElCSAA=" } \ No newline at end of file diff --git a/packages/amplify-graphql-api-construct/package.json b/packages/amplify-graphql-api-construct/package.json index e8b51e2717..236e443518 100644 --- a/packages/amplify-graphql-api-construct/package.json +++ b/packages/amplify-graphql-api-construct/package.json @@ -158,7 +158,7 @@ "semver" ], "dependencies": { - "@aws-amplify/ai-constructs": "^0.1.4", + "@aws-amplify/ai-constructs": "^0.2.0", "@aws-amplify/backend-output-schemas": "^1.0.0", "@aws-amplify/backend-output-storage": "^1.0.0", "@aws-amplify/graphql-auth-transformer": "4.1.2", diff --git a/packages/amplify-graphql-conversation-transformer/package.json b/packages/amplify-graphql-conversation-transformer/package.json index 6d03d4a561..9ccd9f0871 100644 --- a/packages/amplify-graphql-conversation-transformer/package.json +++ b/packages/amplify-graphql-conversation-transformer/package.json @@ -24,7 +24,7 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/ai-constructs": "^0.1.4", + "@aws-amplify/ai-constructs": "^0.2.0", "@aws-amplify/graphql-directives": "2.2.0", "@aws-amplify/graphql-index-transformer": "3.0.4", "@aws-amplify/graphql-model-transformer": "3.0.4", diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap b/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap index 3ea92189bc..e60aed6ad1 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/__snapshots__/amplify-graphql-conversation-transformer.test.ts.snap @@ -85,11 +85,12 @@ export function response(ctx) { exports[`ConversationTransformer valid schemas should transform conversation route with inference configuration 3`] = ` "export function request(ctx) { const { authFilter } = ctx.stash; + const { conversationId } = ctx.args; const query = { expression: 'id = :id', expressionValues: util.dynamodb.toMapValues({ - ':id': ctx.args.conversationId, + ':id': conversationId, }), }; @@ -124,7 +125,7 @@ export function request(ctx) { const args = ctx.stash.transformedArgs ?? ctx.args; const defaultValues = ctx.stash.defaultValues ?? {}; const message = { - __typename: 'ConversationMessagepirateChat', + __typename: 'ConversationMessagePirateChat', role: 'user', ...args, ...defaultValues, @@ -148,13 +149,12 @@ exports[`ConversationTransformer valid schemas should transform conversation rou "import { util } from '@aws-appsync/utils'; export function request(ctx) { - const { args, request, prev } = ctx; + const { args, request } = ctx; const { graphqlApiEndpoint } = ctx.stash; const selectionSet = 'id conversationId content { image { format source { bytes }} text toolUse { toolUseId name input } toolResult { status toolUseId content { json text image { format source { bytes }} document { format name source { bytes }} }}} role owner createdAt updatedAt'; - const messages = prev.result.items; const responseMutation = { name: 'createAssistantResponsePirateChat', inputTypeName: 'CreateConversationMessagePirateChatAssistantInput', @@ -174,6 +174,14 @@ export function request(ctx) { clientTools }; + const messageHistoryQuery = { + getQueryName: 'getConversationMessagePirateChat', + getQueryInputTypeName: 'ID', + listQueryName: 'listConversationMessagePirateChats', + listQueryInputTypeName: 'ModelConversationMessagePirateChatFilterInput', + listQueryLimit: undefined, + }; + const authHeader = request.headers['authorization']; const payload = { conversationId: args.conversationId, @@ -182,7 +190,7 @@ export function request(ctx) { graphqlApiEndpoint, modelConfiguration, request: { headers: { authorization: authHeader } }, - messages, + messageHistoryQuery, toolsConfiguration, }; @@ -203,6 +211,8 @@ export function response(ctx) { conversationId: ctx.args.conversationId, role: 'user', content: ctx.args.content, + aiContext: ctx.args.aiContext, + toolConfiguration: ctx.args.toolConfiguration, createdAt: ctx.stash.defaultValues.createdAt, updatedAt: ctx.stash.defaultValues.updatedAt, }; @@ -296,11 +306,12 @@ export function response(ctx) { exports[`ConversationTransformer valid schemas should transform conversation route with model query tool 3`] = ` "export function request(ctx) { const { authFilter } = ctx.stash; + const { conversationId } = ctx.args; const query = { expression: 'id = :id', expressionValues: util.dynamodb.toMapValues({ - ':id': ctx.args.conversationId, + ':id': conversationId, }), }; @@ -335,7 +346,7 @@ export function request(ctx) { const args = ctx.stash.transformedArgs ?? ctx.args; const defaultValues = ctx.stash.defaultValues ?? {}; const message = { - __typename: 'ConversationMessagepirateChat', + __typename: 'ConversationMessagePirateChat', role: 'user', ...args, ...defaultValues, @@ -359,13 +370,12 @@ exports[`ConversationTransformer valid schemas should transform conversation rou "import { util } from '@aws-appsync/utils'; export function request(ctx) { - const { args, request, prev } = ctx; + const { args, request } = ctx; const { graphqlApiEndpoint } = ctx.stash; const toolDefinitions = {"tools":[{"name":"listTodos","description":"lists todos","inputSchema":{"json":{"type":"object","properties":{},"required":[]}},"graphqlRequestInputDescriptor":{"selectionSet":"items { content isDone id createdAt updatedAt owner } nextToken","propertyTypes":{},"queryName":"listTodos"}}]}; const selectionSet = 'id conversationId content { image { format source { bytes }} text toolUse { toolUseId name input } toolResult { status toolUseId content { json text image { format source { bytes }} document { format name source { bytes }} }}} role owner createdAt updatedAt'; - const messages = prev.result.items; const responseMutation = { name: 'createAssistantResponsePirateChat', inputTypeName: 'CreateConversationMessagePirateChatAssistantInput', @@ -387,6 +397,14 @@ export function request(ctx) { clientTools, }; + const messageHistoryQuery = { + getQueryName: 'getConversationMessagePirateChat', + getQueryInputTypeName: 'ID', + listQueryName: 'listConversationMessagePirateChats', + listQueryInputTypeName: 'ModelConversationMessagePirateChatFilterInput', + listQueryLimit: undefined, + }; + const authHeader = request.headers['authorization']; const payload = { conversationId: args.conversationId, @@ -395,7 +413,7 @@ export function request(ctx) { graphqlApiEndpoint, modelConfiguration, request: { headers: { authorization: authHeader } }, - messages, + messageHistoryQuery, toolsConfiguration, }; @@ -416,6 +434,8 @@ export function response(ctx) { conversationId: ctx.args.conversationId, role: 'user', content: ctx.args.content, + aiContext: ctx.args.aiContext, + toolConfiguration: ctx.args.toolConfiguration, createdAt: ctx.stash.defaultValues.createdAt, updatedAt: ctx.stash.defaultValues.updatedAt, }; @@ -509,11 +529,12 @@ export function response(ctx) { exports[`ConversationTransformer valid schemas should transform conversation route with model query tool including relationships 3`] = ` "export function request(ctx) { const { authFilter } = ctx.stash; + const { conversationId } = ctx.args; const query = { expression: 'id = :id', expressionValues: util.dynamodb.toMapValues({ - ':id': ctx.args.conversationId, + ':id': conversationId, }), }; @@ -548,7 +569,7 @@ export function request(ctx) { const args = ctx.stash.transformedArgs ?? ctx.args; const defaultValues = ctx.stash.defaultValues ?? {}; const message = { - __typename: 'ConversationMessagepirateChat', + __typename: 'ConversationMessagePirateChat', role: 'user', ...args, ...defaultValues, @@ -572,13 +593,12 @@ exports[`ConversationTransformer valid schemas should transform conversation rou "import { util } from '@aws-appsync/utils'; export function request(ctx) { - const { args, request, prev } = ctx; + const { args, request } = ctx; const { graphqlApiEndpoint } = ctx.stash; const toolDefinitions = {"tools":[{"name":"listCustomers","description":"Provides data about the customer sending a message","inputSchema":{"json":{"type":"object","properties":{},"required":[]}},"graphqlRequestInputDescriptor":{"selectionSet":"items { name email activeCart { products { name price } customerId id createdAt updatedAt owner } orderHistory { items { products { name price } customerId id createdAt updatedAt owner } nextToken } id createdAt updatedAt owner } nextToken","propertyTypes":{},"queryName":"listCustomers"}}]}; const selectionSet = 'id conversationId content { image { format source { bytes }} text toolUse { toolUseId name input } toolResult { status toolUseId content { json text image { format source { bytes }} document { format name source { bytes }} }}} role owner createdAt updatedAt'; - const messages = prev.result.items; const responseMutation = { name: 'createAssistantResponsePirateChat', inputTypeName: 'CreateConversationMessagePirateChatAssistantInput', @@ -600,6 +620,14 @@ export function request(ctx) { clientTools, }; + const messageHistoryQuery = { + getQueryName: 'getConversationMessagePirateChat', + getQueryInputTypeName: 'ID', + listQueryName: 'listConversationMessagePirateChats', + listQueryInputTypeName: 'ModelConversationMessagePirateChatFilterInput', + listQueryLimit: undefined, + }; + const authHeader = request.headers['authorization']; const payload = { conversationId: args.conversationId, @@ -608,7 +636,7 @@ export function request(ctx) { graphqlApiEndpoint, modelConfiguration, request: { headers: { authorization: authHeader } }, - messages, + messageHistoryQuery, toolsConfiguration, }; @@ -629,6 +657,8 @@ export function response(ctx) { conversationId: ctx.args.conversationId, role: 'user', content: ctx.args.content, + aiContext: ctx.args.aiContext, + toolConfiguration: ctx.args.toolConfiguration, createdAt: ctx.stash.defaultValues.createdAt, updatedAt: ctx.stash.defaultValues.updatedAt, }; @@ -722,11 +752,12 @@ export function response(ctx) { exports[`ConversationTransformer valid schemas should transform conversation route with query tools 3`] = ` "export function request(ctx) { const { authFilter } = ctx.stash; + const { conversationId } = ctx.args; const query = { expression: 'id = :id', expressionValues: util.dynamodb.toMapValues({ - ':id': ctx.args.conversationId, + ':id': conversationId, }), }; @@ -761,7 +792,7 @@ export function request(ctx) { const args = ctx.stash.transformedArgs ?? ctx.args; const defaultValues = ctx.stash.defaultValues ?? {}; const message = { - __typename: 'ConversationMessagepirateChat', + __typename: 'ConversationMessagePirateChat', role: 'user', ...args, ...defaultValues, @@ -785,13 +816,12 @@ exports[`ConversationTransformer valid schemas should transform conversation rou "import { util } from '@aws-appsync/utils'; export function request(ctx) { - const { args, request, prev } = ctx; + const { args, request } = ctx; const { graphqlApiEndpoint } = ctx.stash; const toolDefinitions = {"tools":[{"name":"getTemperature","description":"does a thing","inputSchema":{"json":{"type":"object","properties":{"city":{"type":"string","description":"A UTF-8 character sequence."}},"required":["city"]}},"graphqlRequestInputDescriptor":{"selectionSet":"value unit","propertyTypes":{"city":"String!"},"queryName":"getTemperature"}},{"name":"plus","description":"does a different thing","inputSchema":{"json":{"type":"object","properties":{"a":{"type":"number","description":"A signed 32-bit integer value."},"b":{"type":"number","description":"A signed 32-bit integer value."}},"required":[]}},"graphqlRequestInputDescriptor":{"selectionSet":"","propertyTypes":{"a":"Int","b":"Int"},"queryName":"plus"}}]}; const selectionSet = 'id conversationId content { image { format source { bytes }} text toolUse { toolUseId name input } toolResult { status toolUseId content { json text image { format source { bytes }} document { format name source { bytes }} }}} role owner createdAt updatedAt'; - const messages = prev.result.items; const responseMutation = { name: 'createAssistantResponsePirateChat', inputTypeName: 'CreateConversationMessagePirateChatAssistantInput', @@ -813,6 +843,14 @@ export function request(ctx) { clientTools, }; + const messageHistoryQuery = { + getQueryName: 'getConversationMessagePirateChat', + getQueryInputTypeName: 'ID', + listQueryName: 'listConversationMessagePirateChats', + listQueryInputTypeName: 'ModelConversationMessagePirateChatFilterInput', + listQueryLimit: undefined, + }; + const authHeader = request.headers['authorization']; const payload = { conversationId: args.conversationId, @@ -821,7 +859,7 @@ export function request(ctx) { graphqlApiEndpoint, modelConfiguration, request: { headers: { authorization: authHeader } }, - messages, + messageHistoryQuery, toolsConfiguration, }; @@ -842,6 +880,8 @@ export function response(ctx) { conversationId: ctx.args.conversationId, role: 'user', content: ctx.args.content, + aiContext: ctx.args.aiContext, + toolConfiguration: ctx.args.toolConfiguration, createdAt: ctx.stash.defaultValues.createdAt, updatedAt: ctx.stash.defaultValues.updatedAt, }; diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts b/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts index 16be984c44..83ee4b5f47 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/amplify-graphql-conversation-transformer.test.ts @@ -44,6 +44,11 @@ describe('ConversationTransformer', () => { const schema = parse(out.schema); validateModelSchema(schema); + + expect( + out.stacks.ConversationMessagePirateChat.Resources![`ListConversationMessage${toUpper(routeName)}Resolver`].Properties + .PipelineConfig.Functions, + ).toHaveLength(4); }); it('uses functionMap for custom handler', () => { diff --git a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-schema-types.graphql b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-schema-types.graphql index fac17f04e5..0bddee354a 100644 --- a/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-schema-types.graphql +++ b/packages/amplify-graphql-conversation-transformer/src/__tests__/schemas/conversation-schema-types.graphql @@ -10,6 +10,7 @@ interface ConversationMessage { content: [ContentBlock] context: AWSJSON toolConfiguration: ToolConfiguration + associatedUserMessageId: ID } input DocumentBlockSourceInput { diff --git a/packages/amplify-graphql-conversation-transformer/src/graphql-types/message-model.ts b/packages/amplify-graphql-conversation-transformer/src/graphql-types/message-model.ts index 75950edd72..12eab5bf46 100644 --- a/packages/amplify-graphql-conversation-transformer/src/graphql-types/message-model.ts +++ b/packages/amplify-graphql-conversation-transformer/src/graphql-types/message-model.ts @@ -227,12 +227,12 @@ const constructConversationMessageModel = ( const content = makeField('content', [], makeListType(makeNamedType('ContentBlock'))); const context = makeField('aiContext', [], makeNamedType('AWSJSON')); const uiComponents = makeField('toolConfiguration', [], makeNamedType('ToolConfiguration')); - const assistantContent = makeField('assistantContent', [], makeListType(makeNamedType('ContentBlock'))); + const associatedUserMessageId = makeField('associatedUserMessageId', [], makeNamedType('ID')); const object = { ...blankObject(modelName), interfaces: [conversationMessageInterface], - fields: [id, conversationId, sessionField, role, content, context, uiComponents, assistantContent], + fields: [id, conversationId, sessionField, role, content, context, uiComponents, associatedUserMessageId], directives: typeDirectives, }; diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/assistant-mutation-resolver-fn.template.js b/packages/amplify-graphql-conversation-transformer/src/resolvers/assistant-mutation-resolver-fn.template.js index d35ff74551..288b483083 100644 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/assistant-mutation-resolver-fn.template.js +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/assistant-mutation-resolver-fn.template.js @@ -1,4 +1,5 @@ import { util } from '@aws-appsync/utils'; +import * as ddb from '@aws-appsync/utils/dynamodb'; /** * Sends a request to the attached data source @@ -6,30 +7,23 @@ import { util } from '@aws-appsync/utils'; * @returns {*} the request */ export function request(ctx) { - const owner = ctx.identity['claims']['sub']; - ctx.stash.owner = owner; const { conversationId, content, associatedUserMessageId } = ctx.args.input; - const updatedAt = util.time.nowISO8601(); + const { owner } = ctx.args; + const defaultValues = ctx.stash.defaultValues ?? {}; + const id = defaultValues.id; - const expression = 'SET #assistantContent = :assistantContent, #updatedAt = :updatedAt'; - const expressionNames = { '#assistantContent': 'assistantContent', '#updatedAt': 'updatedAt' }; - const expressionValues = { ':assistantContent': content, ':updatedAt': updatedAt }; - const condition = JSON.parse( - util.transform.toDynamoDBConditionExpression({ - owner: { eq: owner }, - conversationId: { eq: conversationId }, - }), - ); - return { - operation: 'UpdateItem', - key: util.dynamodb.toMapValues({ id: associatedUserMessageId }), - condition, - update: { - expression, - expressionNames, - expressionValues: util.dynamodb.toMapValues(expressionValues), - }, + const message = { + __typename: '[[CONVERSATION_MESSAGE_TYPE_NAME]]', + id, + role: 'assistant', + content, + conversationId, + associatedUserMessageId, + owner, + ...defaultValues, }; + + return ddb.put({ key: { id }, item: message }); } /** @@ -43,16 +37,5 @@ export function response(ctx) { util.error(ctx.error.message, ctx.error.type); } - const { conversationId, content, associatedUserMessageId } = ctx.args.input; - const { createdAt, updatedAt } = ctx.result; - - return { - id: associatedUserMessageId, - content, - conversationId, - role: 'assistant', - owner: ctx.stash.owner, - createdAt, - updatedAt, - }; + return ctx.result; } diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/assistant-mutation-resolver.ts b/packages/amplify-graphql-conversation-transformer/src/resolvers/assistant-mutation-resolver.ts index ebfcf45d5e..0df9f8b473 100644 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/assistant-mutation-resolver.ts +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/assistant-mutation-resolver.ts @@ -3,6 +3,7 @@ import { MappingTemplateProvider } from '@aws-amplify/graphql-transformer-interf import fs from 'fs'; import path from 'path'; import { ConversationDirectiveConfiguration } from '../grapqhl-conversation-transformer'; +import { toUpper } from 'graphql-transformer-common'; /** * Creates and returns the mapping template for the assistant mutation resolver. @@ -11,7 +12,15 @@ import { ConversationDirectiveConfiguration } from '../grapqhl-conversation-tran * @returns {MappingTemplateProvider} An object containing request and response MappingTemplateProviders. */ export const assistantMutationResolver = (config: ConversationDirectiveConfiguration): MappingTemplateProvider => { - const resolver = fs.readFileSync(path.join(__dirname, 'assistant-mutation-resolver-fn.template.js'), 'utf8'); + let resolver = fs.readFileSync(path.join(__dirname, 'assistant-mutation-resolver-fn.template.js'), 'utf8'); + const fieldName = toUpper(config.field.name.value); + const substitutions = { + CONVERSATION_MESSAGE_TYPE_NAME: `ConversationMessage${fieldName}`, + }; + Object.entries(substitutions).forEach(([key, value]) => { + const replaced = resolver.replace(new RegExp(`\\[\\[${key}\\]\\]`, 'g'), value); + resolver = replaced; + }); const templateName = `Mutation.${config.field.name.value}.assistant-response.js`; return MappingTemplate.s3MappingFunctionCodeFromString(resolver, templateName); }; diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver-fn.template.js b/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver-fn.template.js index 00c8c6e19c..557bacbaf5 100644 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver-fn.template.js +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver-fn.template.js @@ -1,13 +1,12 @@ import { util } from '@aws-appsync/utils'; export function request(ctx) { - const { args, request, prev } = ctx; + const { args, request } = ctx; const { graphqlApiEndpoint } = ctx.stash; [[TOOL_DEFINITIONS_LINE]] const selectionSet = '[[SELECTION_SET]]'; - const messages = prev.result.items; const responseMutation = { name: '[[RESPONSE_MUTATION_NAME]]', inputTypeName: '[[RESPONSE_MUTATION_INPUT_TYPE_NAME]]', @@ -21,6 +20,14 @@ export function request(ctx) { }); [[TOOLS_CONFIGURATION_LINE]] + const messageHistoryQuery = { + getQueryName: '[[GET_QUERY_NAME]]', + getQueryInputTypeName: '[[GET_QUERY_INPUT_TYPE_NAME]]', + listQueryName: '[[LIST_QUERY_NAME]]', + listQueryInputTypeName: '[[LIST_QUERY_INPUT_TYPE_NAME]]', + listQueryLimit: [[LIST_QUERY_LIMIT]], + }; + const authHeader = request.headers['authorization']; const payload = { conversationId: args.conversationId, @@ -29,7 +36,7 @@ export function request(ctx) { graphqlApiEndpoint, modelConfiguration, request: { headers: { authorization: authHeader } }, - messages, + messageHistoryQuery, toolsConfiguration, }; @@ -50,6 +57,8 @@ export function response(ctx) { conversationId: ctx.args.conversationId, role: 'user', content: ctx.args.content, + aiContext: ctx.args.aiContext, + toolConfiguration: ctx.args.toolConfiguration, createdAt: ctx.stash.defaultValues.createdAt, updatedAt: ctx.stash.defaultValues.updatedAt, }; diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver.ts b/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver.ts index 0fd3e9d16d..b73c3b4325 100644 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver.ts +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/invoke-lambda-resolver.ts @@ -4,6 +4,8 @@ import { ConversationDirectiveConfiguration } from '../grapqhl-conversation-tran import fs from 'fs'; import path from 'path'; import dedent from 'ts-dedent'; +import { toUpper } from 'graphql-transformer-common'; +import pluralize from 'pluralize'; /** * Creates a mapping template for invoking a Lambda function in the context of a GraphQL conversation. @@ -20,6 +22,14 @@ export const invokeLambdaMappingTemplate = (config: ConversationDirectiveConfigu const RESPONSE_MUTATION_INPUT_TYPE_NAME = config.responseMutationInputTypeName; const MESSAGE_MODEL_NAME = config.messageModel.messageModel.name.value; + // TODO: Create and add these values to `ConversationDirectiveConfiguration` in an earlier step and + // access them here. + const GET_QUERY_NAME = `getConversationMessage${toUpper(config.field.name.value)}`; + const GET_QUERY_INPUT_TYPE_NAME = 'ID'; + const LIST_QUERY_NAME = `listConversationMessage${toUpper(pluralize(config.field.name.value))}`; + const LIST_QUERY_INPUT_TYPE_NAME = `ModelConversationMessage${toUpper(config.field.name.value)}FilterInput`; + const LIST_QUERY_LIMIT = 'undefined'; + const substitutions = { TOOL_DEFINITIONS_LINE, TOOLS_CONFIGURATION_LINE, @@ -28,6 +38,11 @@ export const invokeLambdaMappingTemplate = (config: ConversationDirectiveConfigu RESPONSE_MUTATION_NAME, RESPONSE_MUTATION_INPUT_TYPE_NAME, MESSAGE_MODEL_NAME, + GET_QUERY_NAME, + GET_QUERY_INPUT_TYPE_NAME, + LIST_QUERY_NAME, + LIST_QUERY_INPUT_TYPE_NAME, + LIST_QUERY_LIMIT, }; let resolver = fs.readFileSync(path.join(__dirname, 'invoke-lambda-resolver-fn.template.js'), 'utf8'); diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/list-messages-init-resolver-fn.template.js b/packages/amplify-graphql-conversation-transformer/src/resolvers/list-messages-init-resolver-fn.template.js new file mode 100644 index 0000000000..71a6777a2f --- /dev/null +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/list-messages-init-resolver-fn.template.js @@ -0,0 +1,8 @@ +export function request(ctx) { + ctx.stash.metadata.index = 'gsi-ConversationMessage.conversationId.createdAt'; + return {}; +} + +export function response(ctx) { + return {}; +} diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/list-messages-init-resolver.ts b/packages/amplify-graphql-conversation-transformer/src/resolvers/list-messages-init-resolver.ts new file mode 100644 index 0000000000..de1b608354 --- /dev/null +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/list-messages-init-resolver.ts @@ -0,0 +1,16 @@ +import { MappingTemplate } from '@aws-amplify/graphql-transformer-core'; +import { MappingTemplateProvider } from '@aws-amplify/graphql-transformer-interfaces'; +import fs from 'fs'; +import path from 'path'; +import { ConversationDirectiveConfiguration } from '../grapqhl-conversation-transformer'; + +/** + * Creates and returns the function code for the list messages resolver init slot. + * + * @returns {MappingTemplateProvider} + */ +export const listMessageInitMappingTemplate = (config: ConversationDirectiveConfiguration): MappingTemplateProvider => { + const resolver = fs.readFileSync(path.join(__dirname, 'list-messages-init-resolver-fn.template.js'), 'utf8'); + const templateName = `Query.${config.field.name.value}.list-messages-init.js`; + return MappingTemplate.s3MappingFunctionCodeFromString(resolver, templateName); +}; diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/message-history-resolver-fn.template.js b/packages/amplify-graphql-conversation-transformer/src/resolvers/message-history-resolver-fn.template.js deleted file mode 100644 index 82e7666ded..0000000000 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/message-history-resolver-fn.template.js +++ /dev/null @@ -1,40 +0,0 @@ -export function request(ctx) { - const { conversationId } = ctx.args; - const { authFilter } = ctx.stash; - - const limit = 100; - const query = { - expression: 'conversationId = :conversationId', - expressionValues: util.dynamodb.toMapValues({ - ':conversationId': conversationId, - }), - }; - - const filter = JSON.parse(util.transform.toDynamoDBFilterExpression(authFilter)); - const index = 'gsi-ConversationMessage.conversationId.createdAt'; - - return { - operation: 'Query', - query, - filter, - index, - scanIndexForward: false, - }; -} - -export function response(ctx) { - if (ctx.error) { - util.error(ctx.error.message, ctx.error.type); - } - const messagesWithAssistantResponse = ctx.result.items - .filter((message) => message.assistantContent !== undefined) - .reduce((acc, current) => { - acc.push({ role: 'user', content: current.content }); - acc.push({ role: 'assistant', content: current.assistantContent }); - return acc; - }, []); - - const currentMessage = { role: 'user', content: ctx.prev.result.content }; - const items = [...messagesWithAssistantResponse, currentMessage]; - return { items }; -} diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/message-history-resolver.ts b/packages/amplify-graphql-conversation-transformer/src/resolvers/message-history-resolver.ts deleted file mode 100644 index ce56cfa558..0000000000 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/message-history-resolver.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { MappingTemplate } from '@aws-amplify/graphql-transformer-core'; -import { MappingTemplateProvider } from '@aws-amplify/graphql-transformer-interfaces'; -import fs from 'fs'; -import path from 'path'; -import { ConversationDirectiveConfiguration } from '../grapqhl-conversation-transformer'; - -/** - * Creates a mapping template for reading message history in a conversation. - * - * @returns {MappingTemplateProvider} An object containing request and response mapping functions. - */ -export const readHistoryMappingTemplate = (config: ConversationDirectiveConfiguration): MappingTemplateProvider => { - const resolver = fs.readFileSync(path.join(__dirname, 'message-history-resolver-fn.template.js'), 'utf8'); - const templateName = `Mutation.${config.field.name.value}.message-history.js`; - return MappingTemplate.s3MappingFunctionCodeFromString(resolver, templateName); -}; diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/verify-session-owner-resolver-fn.template.js b/packages/amplify-graphql-conversation-transformer/src/resolvers/verify-session-owner-resolver-fn.template.js index 7a05ff10b7..25ce987425 100644 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/verify-session-owner-resolver-fn.template.js +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/verify-session-owner-resolver-fn.template.js @@ -1,10 +1,11 @@ export function request(ctx) { const { authFilter } = ctx.stash; + const { conversationId } = [[CONVERSATION_ID_PARENT]]; const query = { expression: 'id = :id', expressionValues: util.dynamodb.toMapValues({ - ':id': ctx.args.conversationId, + ':id': conversationId, }), }; diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/verify-session-owner-resolver.ts b/packages/amplify-graphql-conversation-transformer/src/resolvers/verify-session-owner-resolver.ts index 0624e1c9d5..a718c580d8 100644 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/verify-session-owner-resolver.ts +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/verify-session-owner-resolver.ts @@ -9,8 +9,32 @@ import { ConversationDirectiveConfiguration } from '../grapqhl-conversation-tran * * @returns {MappingTemplateProvider} An object containing request and response MappingTemplateProviders. */ -export const verifySessionOwnerMappingTemplate = (config: ConversationDirectiveConfiguration): MappingTemplateProvider => { - const resolver = fs.readFileSync(path.join(__dirname, 'verify-session-owner-resolver-fn.template.js'), 'utf8'); +export const verifySessionOwnerSendMessageMappingTemplate = (config: ConversationDirectiveConfiguration): MappingTemplateProvider => { + const substitutions = { + CONVERSATION_ID_PARENT: 'ctx.args', + }; const templateName = `Mutation.${config.field.name.value}.verify-session-owner.js`; - return MappingTemplate.s3MappingFunctionCodeFromString(resolver, templateName); + return verifySessionOwnerMappingTemplate(templateName, substitutions); +}; + +/** + * Creates a mapping template for verifying the session owner in a conversation. + * + * @returns {MappingTemplateProvider} An object containing request and response MappingTemplateProviders. + */ +export const verifySessionOwnerAssistantResponseMappingTemplate = (config: ConversationDirectiveConfiguration): MappingTemplateProvider => { + const substitutions = { + CONVERSATION_ID_PARENT: 'ctx.args.input', + }; + const templateName = `Mutation.${config.field.name.value}AssistantResponse.verify-session-owner.js`; + return verifySessionOwnerMappingTemplate(templateName, substitutions); +}; + +const verifySessionOwnerMappingTemplate = (name: string, substitute: Record) => { + let resolver = fs.readFileSync(path.join(__dirname, 'verify-session-owner-resolver-fn.template.js'), 'utf8'); + Object.entries(substitute).forEach(([key, value]) => { + const replaced = resolver.replace(new RegExp(`\\[\\[${key}\\]\\]`, 'g'), value); + resolver = replaced; + }); + return MappingTemplate.s3MappingFunctionCodeFromString(resolver, name); }; diff --git a/packages/amplify-graphql-conversation-transformer/src/resolvers/write-message-to-table-resolver.ts b/packages/amplify-graphql-conversation-transformer/src/resolvers/write-message-to-table-resolver.ts index cf9848dc01..c7fc24b484 100644 --- a/packages/amplify-graphql-conversation-transformer/src/resolvers/write-message-to-table-resolver.ts +++ b/packages/amplify-graphql-conversation-transformer/src/resolvers/write-message-to-table-resolver.ts @@ -2,15 +2,18 @@ import { MappingTemplate } from '@aws-amplify/graphql-transformer-core'; import { MappingTemplateProvider } from '@aws-amplify/graphql-transformer-interfaces'; import fs from 'fs'; import path from 'path'; +import { ConversationDirectiveConfiguration } from '../grapqhl-conversation-transformer'; +import { toUpper } from 'graphql-transformer-common'; /** * Creates a mapping template for writing a message to a table in a conversation. * * @returns {MappingTemplateProvider} An object containing request and response MappingTemplateProviders. */ -export const writeMessageToTableMappingTemplate = (fieldName: string): MappingTemplateProvider => { +export const writeMessageToTableMappingTemplate = (config: ConversationDirectiveConfiguration): MappingTemplateProvider => { + const fieldName = config.field.name.value; const substitutions = { - CONVERSATION_MESSAGE_TYPE_NAME: `ConversationMessage${fieldName}`, + CONVERSATION_MESSAGE_TYPE_NAME: `ConversationMessage${toUpper(fieldName)}`, }; let resolver = fs.readFileSync(path.join(__dirname, 'write-message-to-table-resolver-fn.template.js'), 'utf8'); Object.entries(substitutions).forEach(([key, value]) => { diff --git a/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts b/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts index e6025fe373..0d34ddaaae 100644 --- a/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts +++ b/packages/amplify-graphql-conversation-transformer/src/transformer-steps/conversation-resolver-generator.ts @@ -9,13 +9,17 @@ import { IFunction } from 'aws-cdk-lib/aws-lambda'; import { getModelDataSourceNameForTypeName, getTable } from '@aws-amplify/graphql-transformer-core'; import { initMappingTemplate } from '../resolvers/init-resolver'; import { authMappingTemplate } from '../resolvers/auth-resolver'; -import { verifySessionOwnerMappingTemplate } from '../resolvers/verify-session-owner-resolver'; +import { + verifySessionOwnerSendMessageMappingTemplate, + verifySessionOwnerAssistantResponseMappingTemplate, +} from '../resolvers/verify-session-owner-resolver'; import { writeMessageToTableMappingTemplate } from '../resolvers/write-message-to-table-resolver'; -import { readHistoryMappingTemplate } from '../resolvers/message-history-resolver'; import { invokeLambdaMappingTemplate } from '../resolvers/invoke-lambda-resolver'; import { assistantMutationResolver } from '../resolvers/assistant-mutation-resolver'; import { conversationMessageSubscriptionMappingTamplate } from '../resolvers/assistant-messages-subscription-resolver'; import { overrideIndexAtCfnLevel } from '@aws-amplify/graphql-index-transformer'; +import pluralize from 'pluralize'; +import { listMessageInitMappingTemplate } from '../resolvers/list-messages-init-resolver'; type KeyAttributeDefinition = { name: string; @@ -30,6 +34,7 @@ export class ConversationResolverGenerator { for (const directive of directives) { this.processToolsForDirective(directive, ctx); this.generateResolversForDirective(directive, ctx); + this.addInitSlotToListMessagesPipeline(ctx, directive); } } @@ -48,14 +53,15 @@ export class ConversationResolverGenerator { const functionStack = this.createFunctionStack(ctx, capitalizedFieldName); const { functionDataSourceId, referencedFunction } = this.setupFunctionDataSource(directive, functionStack, capitalizedFieldName); - - this.createAssistantResponseResolver(ctx, directive, capitalizedFieldName); - this.createAssistantResponseSubscriptionResolver(ctx, directive, capitalizedFieldName); - const functionDataSource = this.addLambdaDataSource(ctx, functionDataSourceId, referencedFunction, capitalizedFieldName); const invokeLambdaFunction = invokeLambdaMappingTemplate(directive); this.setupMessageTableIndex(ctx, directive); + const initResolverFunction = initMappingTemplate(ctx); + const authResolverFunction = authMappingTemplate(directive); + const verifySessionOwnerSendMessageResolverFunction = verifySessionOwnerSendMessageMappingTemplate(directive); + const verifySessionOwnerAssistantResponseResolverFunction = verifySessionOwnerAssistantResponseMappingTemplate(directive); + const writeMessageToTableFunction = writeMessageToTableMappingTemplate(directive); this.createConversationPipelineResolver( ctx, @@ -64,8 +70,21 @@ export class ConversationResolverGenerator { capitalizedFieldName, functionDataSource, invokeLambdaFunction, + initResolverFunction, + authResolverFunction, + verifySessionOwnerSendMessageResolverFunction, + writeMessageToTableFunction, + ); + + this.createAssistantResponseResolver( + ctx, directive, + capitalizedFieldName, + initResolverFunction, + authResolverFunction, + verifySessionOwnerAssistantResponseResolverFunction, ); + this.createAssistantResponseSubscriptionResolver(ctx, directive, capitalizedFieldName); } /** @@ -161,7 +180,10 @@ export class ConversationResolverGenerator { capitalizedFieldName: string, functionDataSource: any, invokeLambdaFunction: MappingTemplateProvider, - directive: ConversationDirectiveConfiguration, + initResolverFunction: MappingTemplateProvider, + authResolverFunction: MappingTemplateProvider, + verifySessionOwnerResolverFunction: MappingTemplateProvider, + writeMessageToTableFunction: MappingTemplateProvider, ): void { const resolverResourceId = ResolverResourceIDs.ResolverResourceID(parentName, fieldName); const runtime = APPSYNC_JS_RUNTIME; @@ -170,13 +192,21 @@ export class ConversationResolverGenerator { fieldName, resolverResourceId, { codeMappingTemplate: invokeLambdaFunction }, - ['init', 'auth', 'verifySessionOwner', 'writeMessageToTable', 'retrieveMessageHistory'], + ['init', 'auth', 'verifySessionOwner', 'writeMessageToTable'], ['handleLambdaResponse', 'finish'], functionDataSource, runtime, ); - this.addPipelineResolverFunctions(ctx, conversationPipelineResolver, capitalizedFieldName, directive); + this.addPipelineResolverFunctions( + ctx, + conversationPipelineResolver, + capitalizedFieldName, + initResolverFunction, + authResolverFunction, + verifySessionOwnerResolverFunction, + writeMessageToTableFunction, + ); ctx.resolvers.addResolver(parentName, fieldName, conversationPipelineResolver); } @@ -192,33 +222,28 @@ export class ConversationResolverGenerator { ctx: TransformerContextProvider, resolver: TransformerResolver, capitalizedFieldName: string, - directive: ConversationDirectiveConfiguration, + initResolverFunction: MappingTemplateProvider, + authResolverFunction: MappingTemplateProvider, + verifySessionOwnerResolverFunction: MappingTemplateProvider, + writeMessageToTableFunction: MappingTemplateProvider, ): void { // Add init function - const initFunction = initMappingTemplate(ctx); - resolver.addJsFunctionToSlot('init', initFunction); + resolver.addJsFunctionToSlot('init', initResolverFunction); // Add auth function - const authFunction = authMappingTemplate(directive); - resolver.addJsFunctionToSlot('auth', authFunction); + resolver.addJsFunctionToSlot('auth', authResolverFunction); // Add verifySessionOwner function - const verifySessionOwnerFunction = verifySessionOwnerMappingTemplate(directive); const sessionModelName = `Conversation${capitalizedFieldName}`; const sessionModelDDBDataSourceName = getModelDataSourceNameForTypeName(ctx, sessionModelName); const conversationSessionDDBDataSource = ctx.api.host.getDataSource(sessionModelDDBDataSourceName); - resolver.addJsFunctionToSlot('verifySessionOwner', verifySessionOwnerFunction, conversationSessionDDBDataSource as any); + resolver.addJsFunctionToSlot('verifySessionOwner', verifySessionOwnerResolverFunction, conversationSessionDDBDataSource as any); // Add writeMessageToTable function - const writeMessageToTableFunction = writeMessageToTableMappingTemplate(directive.field.name.value); const messageModelName = `ConversationMessage${capitalizedFieldName}`; const messageModelDDBDataSourceName = getModelDataSourceNameForTypeName(ctx, messageModelName); const messageDDBDataSource = ctx.api.host.getDataSource(messageModelDDBDataSourceName); resolver.addJsFunctionToSlot('writeMessageToTable', writeMessageToTableFunction, messageDDBDataSource as any); - - // Add retrieveMessageHistory function - const retrieveMessageHistoryFunction = readHistoryMappingTemplate(directive); - resolver.addJsFunctionToSlot('retrieveMessageHistory', retrieveMessageHistoryFunction, messageDDBDataSource as any); } /** @@ -231,23 +256,38 @@ export class ConversationResolverGenerator { ctx: TransformerContextProvider, directive: ConversationDirectiveConfiguration, capitalizedFieldName: string, + initResolverFunction: MappingTemplateProvider, + authResolverFunction: MappingTemplateProvider, + verifySessionOwnerResolverFunction: MappingTemplateProvider, ): void { const assistantResponseResolverResourceId = ResolverResourceIDs.ResolverResourceID('Mutation', directive.responseMutationName); const assistantResponseResolverFunction = assistantMutationResolver(directive); const conversationMessageDataSourceName = getModelDataSourceNameForTypeName(ctx, `ConversationMessage${capitalizedFieldName}`); const conversationMessageDataSource = ctx.api.host.getDataSource(conversationMessageDataSourceName); - const assistantResponseResolver = new TransformerResolver( + const resolver = new TransformerResolver( 'Mutation', directive.responseMutationName, assistantResponseResolverResourceId, { codeMappingTemplate: assistantResponseResolverFunction }, - [], + ['init', 'auth', 'verifySessionOwner'], [], conversationMessageDataSource as any, APPSYNC_JS_RUNTIME, ); - ctx.resolvers.addResolver('Mutation', directive.responseMutationName, assistantResponseResolver); + // Add init function + resolver.addJsFunctionToSlot('init', initResolverFunction); + + // Add auth function + resolver.addJsFunctionToSlot('auth', authResolverFunction); + + // Add verifySessionOwner function + const sessionModelName = `Conversation${capitalizedFieldName}`; + const sessionModelDDBDataSourceName = getModelDataSourceNameForTypeName(ctx, sessionModelName); + const conversationSessionDDBDataSource = ctx.api.host.getDataSource(sessionModelDDBDataSourceName); + resolver.addJsFunctionToSlot('verifySessionOwner', verifySessionOwnerResolverFunction, conversationSessionDDBDataSource as any); + + ctx.resolvers.addResolver('Mutation', directive.responseMutationName, resolver); } /** @@ -305,6 +345,14 @@ export class ConversationResolverGenerator { return ctx.api.host.addLambdaDataSource(functionDataSourceId, referencedFunction, {}, functionDataSourceScope); } + private addInitSlotToListMessagesPipeline(ctx: TransformerContextProvider, directive: ConversationDirectiveConfiguration): void { + const messageModelName = directive.messageModel.messageModel.name.value; + const pluralized = pluralize(messageModelName); + const listMessagesResolver = ctx.resolvers.getResolver('Query', `list${pluralized}`) as TransformerResolver; + const initResolverFn = listMessageInitMappingTemplate(directive); + listMessagesResolver.addJsFunctionToSlot('init', initResolverFn); + } + /** * Sets up the message table index * @param ctx - The transformer context provider diff --git a/yarn.lock b/yarn.lock index ee3f6b4290..0a2743b9dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,10 +10,10 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@aws-amplify/ai-constructs@^0.1.4": - version "0.1.4" - resolved "https://registry.npmjs.org/@aws-amplify/ai-constructs/-/ai-constructs-0.1.4.tgz#043ca7793cb4a97ad7864797bd70dbfa323329f4" - integrity sha512-BGLBFs/pt6JrNgUo+QD0Szt/ssHMa6EyEE45yLoHemwPHRuJPpnFmxIbbxgxaqJP0mWK6QMs9Wh3IsdJ/6XhDA== +"@aws-amplify/ai-constructs@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@aws-amplify/ai-constructs/-/ai-constructs-0.2.0.tgz#91db9586d8e656a4ad7f2b0b539a2221a38124b2" + integrity sha512-aqmUrUvbWpebJcNCvoFywHLTQXNIlli8VE2i9+sSMlQXAG2zRiqcpDdRha+0NQnPNj09K2/DMLTe79ldwDaGkQ== dependencies: "@aws-amplify/plugin-types" "^1.0.1" "@aws-sdk/client-bedrock-runtime" "^3.622.0" From 22d197a32be92d832243de07ce5ccd9b48e4e504 Mon Sep 17 00:00:00 2001 From: "Peter V." <98245483+p5quared@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:00:21 -0700 Subject: [PATCH 12/15] fix: sql default value e2e failures (#2932) --- .../schemas/sql-models/schema.graphql | 2 +- .../src/sql-tests-common/sql-models.ts | 28 ------------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql index e5138d8a6a..b99fe7eeb8 100644 --- a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql @@ -1,6 +1,6 @@ type Todo @model @refersTo(name: "e2e_test_todos") { id: ID! @primaryKey - description: String! @default(value: "Lorem ipsum yadda yadda...") + description: String! } type Student @model @refersTo(name: "e2e_test_students") { studentId: Int! @primaryKey(sortKeyFields: ["classId"]) diff --git a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts index 0e58d04213..0411dd2290 100644 --- a/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts +++ b/packages/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models.ts @@ -73,34 +73,6 @@ export const testGraphQLAPI = ( deleteProjectDir(projRoot); }); - test(`check default value on todo table - ${engine}`, async () => { - const defaultTodoDescription = 'Lorem ipsum yadda yadda...'; - - // Create Todo Mutation - const createTodo1 = await toDoTableCRUDLHelper.create({}); - - expect(createTodo1).toBeDefined(); - expect(createTodo1.id).toBeDefined(); - expect(createTodo1.description).toEqual(defaultTodoDescription); - - // Get Todo Query - const getTodo1 = await toDoTableCRUDLHelper.getById(createTodo1.id); - expect(getTodo1.id).toEqual(createTodo1.id); - expect(getTodo1.description).toEqual(createTodo1.description); - - // Update Todo Mutation - const updateTodo1 = await toDoTableCRUDLHelper.update({ id: createTodo1.id, description: 'Updated Todo #1' }); - - expect(updateTodo1.id).toEqual(createTodo1.id); - expect(updateTodo1.description).toEqual('Updated Todo #1'); - - // Get Todo Query after update - const getUpdatedTodo1 = await toDoTableCRUDLHelper.getById(createTodo1.id); - - expect(getUpdatedTodo1.id).toEqual(createTodo1.id); - expect(getUpdatedTodo1.description).toEqual('Updated Todo #1'); - }); - test(`check CRUDL on todo table with default primary key - ${engine}`, async () => { // Create Todo Mutation const createTodo1 = await toDoTableCRUDLHelper.create({ description: 'Todo #1' }); From 9dde73c09dd4f42c666c256c8543a6b94dc4a084 Mon Sep 17 00:00:00 2001 From: Ian Saultz <52051793+atierian@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:15:26 -0400 Subject: [PATCH 13/15] fix(generation): remove trailing comma in inferenceConfig resolver code (#2933) --- .../generations/graphql/schema-generation.graphql | 7 ++++++- .../amplify-graphql-generation-transformer.test.ts.snap | 2 +- .../src/resolvers/invoke-bedrock.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/schema-generation.graphql b/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/schema-generation.graphql index 334ebec9c8..9ee59e600e 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/schema-generation.graphql +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/generations/graphql/schema-generation.graphql @@ -4,7 +4,12 @@ type Recipe { } type Query { - summarize(input: String): String @generation(aiModel: "anthropic.claude-3-haiku-20240307-v1:0", systemPrompt: "summarize the input.") + summarize(input: String): String + @generation( + aiModel: "anthropic.claude-3-haiku-20240307-v1:0" + systemPrompt: "summarize the input." + inferenceConfiguration: { temperature: 0.5 } + ) generateRecipe(description: String): Recipe @generation(aiModel: "anthropic.claude-3-haiku-20240307-v1:0", systemPrompt: "You are a 3 star michelin chef that generates recipes.") diff --git a/packages/amplify-graphql-generation-transformer/src/__tests__/__snapshots__/amplify-graphql-generation-transformer.test.ts.snap b/packages/amplify-graphql-generation-transformer/src/__tests__/__snapshots__/amplify-graphql-generation-transformer.test.ts.snap index 5af359d090..1686d54c9b 100644 --- a/packages/amplify-graphql-generation-transformer/src/__tests__/__snapshots__/amplify-graphql-generation-transformer.test.ts.snap +++ b/packages/amplify-graphql-generation-transformer/src/__tests__/__snapshots__/amplify-graphql-generation-transformer.test.ts.snap @@ -519,7 +519,7 @@ export function request(ctx) { const toolConfig = {"tools":[{"toolSpec":{"name":"responseType","description":"Generate a response type for the given field.","inputSchema":{"json":{"type":"object","properties":{"value":{"type":"string","description":"A UTF-8 character sequence."}},"required":["value"]}}}}],"toolChoice":{"tool":{"name":"responseType"}}}; const prompt = "Generate a string based on the description."; const args = JSON.stringify(ctx.args); - const inferenceConfig = { inferenceConfig: {"maxTokens":100,"temperature":0.7,"topP":0.9} },; + const inferenceConfig = { inferenceConfig: {"maxTokens":100,"temperature":0.7,"topP":0.9} }; return { resourcePath: '/model/anthropic.claude-3-haiku-20240307-v1:0/converse', diff --git a/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock.ts b/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock.ts index 1bb95f81b5..05713c3d4f 100644 --- a/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock.ts +++ b/packages/amplify-graphql-generation-transformer/src/resolvers/invoke-bedrock.ts @@ -55,7 +55,7 @@ const generateResolver = (fileName: string, values: Record): str */ const getInferenceConfigResolverDefinition = (inferenceConfiguration?: InferenceConfiguration): string => { return inferenceConfiguration && Object.keys(inferenceConfiguration).length > 0 - ? `{ inferenceConfig: ${JSON.stringify(inferenceConfiguration)} },` + ? `{ inferenceConfig: ${JSON.stringify(inferenceConfiguration)} }` : 'undefined'; }; From 5cb5a2bcacf30ca9ff0f29fe32be57d1ffb6c1cf Mon Sep 17 00:00:00 2001 From: Tim Schmelter Date: Thu, 10 Oct 2024 07:51:51 -0700 Subject: [PATCH 14/15] fix: add aws_iam to custom operations when enableIamAuthorization is enabled; fix graphql type utils (#2921) - test: Add additional tests to fix coverage metrics for unchanged files - test: Add implicit IAM auth support tests - Added a skipped test for custom type support, to be re-enabled once we figure out the right strategy for this. --- .../iam-custom-operations.test.ts | 240 ++++++++ .../src/__tests__/index-import.test.ts | 9 + .../sql-model-datasource-strategy.test.ts | 193 +++++++ .../amplify-graphql-auth-transformer/API.md | 2 + .../__tests__/iam-custom-operations.test.ts | 517 ++++++++++++++++++ .../src/graphql-auth-transformer.ts | 49 +- .../amplify-graphql-transformer-core/API.md | 14 +- .../src/utils/graphql-utils.ts | 8 +- 8 files changed, 1009 insertions(+), 23 deletions(-) create mode 100644 packages/amplify-graphql-api-construct/src/__tests__/__functional__/iam-custom-operations.test.ts create mode 100644 packages/amplify-graphql-api-construct/src/__tests__/index-import.test.ts create mode 100644 packages/amplify-graphql-api-construct/src/__tests__/sql-model-datasource-strategy.test.ts create mode 100644 packages/amplify-graphql-auth-transformer/src/__tests__/iam-custom-operations.test.ts diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/iam-custom-operations.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/iam-custom-operations.test.ts new file mode 100644 index 0000000000..603564a36a --- /dev/null +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/iam-custom-operations.test.ts @@ -0,0 +1,240 @@ +import * as cdk from 'aws-cdk-lib'; +import { Template } from 'aws-cdk-lib/assertions'; +import * as cognito from 'aws-cdk-lib/aws-cognito'; +import { AccountPrincipal, Effect, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; +import { AmplifyGraphqlApi } from '../../amplify-graphql-api'; +import { AmplifyGraphqlDefinition } from '../../amplify-graphql-definition'; + +/** + * This tests the CDK construct interface for enabling IAM authorization mode by default for Gen 2 and ensures that it properly applies to + * custom operations. Further, it asserts that the policies created by the transformers are properly scoped, and never include references to + * the custom operations. This is especially important for configurations that include a Cognito Identity Pool--the Identity Pool auth and + * unauth roles should not be granted access to the custom operations unless explicitly allowed. + */ +describe('Custom operations have @aws_iam directives when enableIamAuthorizationMode is true', () => { + it('Correctly scopes policies when a user pool is present', () => { + const stack = new cdk.Stack(); + + const userPool = cognito.UserPool.fromUserPoolId(stack, 'ImportedUserPool', 'ImportedUserPoolId'); + + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ provider: userPools, allow: owner }]) { + description: String! + } + type Query { + getFooCustom: String + } + type Mutation { + updateFooCustom: String + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) + } + `), + authorizationModes: { + defaultAuthorizationMode: 'AMAZON_COGNITO_USER_POOLS', + userPoolConfig: { userPool }, + iamConfig: { enableIamAuthorizationMode: true }, + }, + }); + + const template = Template.fromStack(stack); + + // We do not expect any policy statements relating to the GraphQL API, since no policy was created by the customer. We'll do a string + // match across all roles & policies as a brute-force way of ensuring that nothing is granting access to the custom operations. We + // search policies directly, and also roles to make sure we get inline policies attached to the roles. + const allRoles = template.findResources('AWS::IAM::Role'); + const allRolesString = JSON.stringify(allRoles); + expect(allRolesString).not.toContain('appsync:GraphQL'); + + const allPolicies = template.findResources('AWS::IAM::Policy'); + const allPoliciesString = JSON.stringify(allPolicies); + expect(allPoliciesString).not.toContain('appsync:GraphQL'); + + // There should be no managed policies at all since we didn't include an Identity Pool + const allManagedPolicies = template.findResources('AWS::IAM::ManagedPolicy'); + expect(allManagedPolicies).toStrictEqual({}); + }); + + it('Correctly handles no-model cases', () => { + const stack = new cdk.Stack(); + + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Query { + getFooCustom: String + } + type Mutation { + updateFooCustom: String + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) + } + `), + authorizationModes: { + iamConfig: { enableIamAuthorizationMode: true }, + }, + }); + + const template = Template.fromStack(stack); + + // We do not expect any policy statements relating to the GraphQL API, since no policy was created by the customer. We'll do a string + // match across all roles & policies as a brute-force way of ensuring that nothing is granting access to the custom operations. We + // search policies directly, and also roles to make sure we get inline policies attached to the roles. + const allRoles = template.findResources('AWS::IAM::Role'); + const allRolesString = JSON.stringify(allRoles); + expect(allRolesString).not.toContain('appsync:GraphQL'); + + const allPolicies = template.findResources('AWS::IAM::Policy'); + const allPoliciesString = JSON.stringify(allPolicies); + expect(allPoliciesString).not.toContain('appsync:GraphQL'); + + // There should be no managed policies at all since we didn't include an Identity Pool + const allManagedPolicies = template.findResources('AWS::IAM::ManagedPolicy'); + expect(allManagedPolicies).toStrictEqual({}); + }); + + it('Respects customer-provided access policies', () => { + const stack = new cdk.Stack(); + + const api = new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Query { + getFooCustom: String + } + type Mutation { + updateFooCustom: String + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) + } + `), + authorizationModes: { + iamConfig: { enableIamAuthorizationMode: true }, + }, + }); + + new Role(stack, 'TestRole', { + assumedBy: new AccountPrincipal('123456789012'), + roleName: 'TestCustomOperationAccessRole', + inlinePolicies: { + TestCustomOperationAccessPolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + actions: ['appsync:GraphQL'], + resources: [`${api.resources.graphqlApi.arn}/types/Mutation/fields/updateFooCustom`], + effect: Effect.ALLOW, + }), + ], + }), + }, + }); + + const template = Template.fromStack(stack); + + // Rather than a string match across all roles, we'll ensure that access to the custom mutation comes only in the customer-provided role + const allRoles = template.findResources('AWS::IAM::Role'); + + const customerRoleKeys = Object.keys(allRoles).filter((key) => key.startsWith('TestRole')); + expect(customerRoleKeys.length).toEqual(1); + + const customerRole = allRoles[customerRoleKeys[0]]; + const customerRoleString = JSON.stringify(customerRole); + + const allRolesExceptCustomer = JSON.parse(JSON.stringify(allRoles)); + delete allRolesExceptCustomer[customerRoleKeys[0]]; + + const allRolesExceptCustomerString = JSON.stringify(allRoles); + + // No role or policy should contain a permission for getFooCustom, since it wasn't explicitly granted + expect(allRolesExceptCustomerString).not.toContain('getFooCustom'); + expect(customerRoleString).not.toContain('getFooCustom'); + + // The customer role should contain a permission for updateFooCustom as specified in the inline policy above + expect(customerRoleString).toContain('appsync:GraphQL'); + expect(customerRoleString).toContain('/types/Mutation/fields/updateFooCustom'); + + // No standalone policy resource should contain a permission to the API + const allPolicies = template.findResources('AWS::IAM::Policy'); + const allPoliciesString = JSON.stringify(allPolicies); + expect(allPoliciesString).not.toContain('appsync:GraphQL'); + + // There should be no managed policies at all since we didn't include an Identity Pool + const allManagedPolicies = template.findResources('AWS::IAM::ManagedPolicy'); + expect(allManagedPolicies).toStrictEqual({}); + }); + + it('Correctly scopes when an identity pool is present', () => { + const stack = new cdk.Stack(); + + const userPool = cognito.UserPool.fromUserPoolId(stack, 'ImportedUserPool', 'ImportedUserPoolId'); + const userPoolClient = userPool.addClient('TestClient'); + + const identityPool = new cognito.CfnIdentityPool(stack, 'TestIdentityPool', { + allowUnauthenticatedIdentities: true, + cognitoIdentityProviders: [ + { + clientId: userPoolClient.userPoolClientId, + providerName: 'Amazon Cognito', + }, + ], + }); + const appsync = new ServicePrincipal('appsync.amazonaws.com'); + const authenticatedUserRole = new Role(stack, 'AuthRole', { assumedBy: appsync }); + const unauthenticatedUserRole = new Role(stack, 'UnauthRole', { assumedBy: appsync }); + + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ provider: identityPool, allow: private }]) { + description: String! + } + type Query { + getFooCustom: String + } + type Mutation { + updateFooCustom: String + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) + } + `), + authorizationModes: { + defaultAuthorizationMode: 'AMAZON_COGNITO_USER_POOLS', + userPoolConfig: { userPool }, + iamConfig: { enableIamAuthorizationMode: true }, + identityPoolConfig: { + identityPoolId: identityPool.attrId, + authenticatedUserRole, + unauthenticatedUserRole, + }, + }, + }); + + const template = Template.fromStack(stack); + + const allRoles = template.findResources('AWS::IAM::Role'); + const allRolesString = JSON.stringify(allRoles); + expect(allRolesString).not.toContain('appsync:GraphQL'); + + const allPolicies = template.findResources('AWS::IAM::Policy'); + const allPoliciesString = JSON.stringify(allPolicies); + expect(allPoliciesString).not.toContain('appsync:GraphQL'); + + // The managed policy for the identity pool should allow access to only the models, but not to the custom operations + const allManagedPolicies = template.findResources('AWS::IAM::ManagedPolicy'); + const allManagedPoliciesString = JSON.stringify(allManagedPolicies); + expect(allManagedPoliciesString).toContain('appsync:GraphQL'); + expect(allManagedPoliciesString).toContain('getTodo'); + expect(allManagedPoliciesString).toContain('listTodos'); + expect(allManagedPoliciesString).toContain('createTodo'); + expect(allManagedPoliciesString).toContain('updateTodo'); + expect(allManagedPoliciesString).toContain('deleteTodo'); + expect(allManagedPoliciesString).toContain('onCreateTodo'); + expect(allManagedPoliciesString).toContain('onUpdateTodo'); + expect(allManagedPoliciesString).toContain('onDeleteTodo'); + expect(allManagedPoliciesString).not.toContain('getFooCustom'); + expect(allManagedPoliciesString).not.toContain('updateFooCustom'); + expect(allManagedPoliciesString).not.toContain('onUpdateFooCustom'); + }); +}); diff --git a/packages/amplify-graphql-api-construct/src/__tests__/index-import.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/index-import.test.ts new file mode 100644 index 0000000000..550b94cf82 --- /dev/null +++ b/packages/amplify-graphql-api-construct/src/__tests__/index-import.test.ts @@ -0,0 +1,9 @@ +// This exists only to provide coverage metrics for various export files + +import * as src from '../index'; +import * as types from '../types'; + +test('Work around coverage metrics for export and type-only files', () => { + expect(src).toBeDefined(); + expect(types).toBeDefined(); +}); diff --git a/packages/amplify-graphql-api-construct/src/__tests__/sql-model-datasource-strategy.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/sql-model-datasource-strategy.test.ts new file mode 100644 index 0000000000..4465311d0c --- /dev/null +++ b/packages/amplify-graphql-api-construct/src/__tests__/sql-model-datasource-strategy.test.ts @@ -0,0 +1,193 @@ +/* eslint-disable max-classes-per-file */ +import { isSQLLambdaModelDataSourceStrategy, isSqlModelDataSourceDbConnectionConfig } from '../sql-model-datasource-strategy'; + +/** Mock to test that isSqlLambdaModelDataSourceStrategy recognizes function types */ +class MockFunctionStrategy { + dbType: string; + name: string; + dbConnectionConfig: any; + + constructor() { + this.dbType = 'MYSQL'; + this.name = 'test'; + this.dbConnectionConfig = new MockConnectionConfig('path'); + } +} + +class MockConnectionConfig { + connectionUriSsmPath: string | string[] | undefined; + constructor(path: string | string[] | undefined) { + this.connectionUriSsmPath = path; + } +} + +describe('sql-model-datasource-strategy utilities', () => { + describe('isSQLLambdaModelDataSourceStrategy', () => { + test.each([ + { + label: 'accepts an object type', + expected: true, + candidateObject: { + dbType: 'MYSQL', + name: 'test', + dbConnectionConfig: { + connectionUriSsmPath: '/path', + }, + }, + }, + + { + label: 'accepts a postgres database type', + expected: true, + candidateObject: { + dbType: 'POSTGRES', + name: 'test', + dbConnectionConfig: { + connectionUriSsmPath: '/path', + }, + }, + }, + + { + label: 'accepts a function type', + expected: true, + candidateObject: new MockFunctionStrategy(), + }, + + { + label: 'rejects an unknown db type', + expected: false, + candidateObject: { + dbType: 'ZZZZZ', + name: 'test', + dbConnectionConfig: { + connectionUriSsmPath: '/path', + }, + }, + }, + + { + label: 'rejects an unknown connection config', + expected: false, + candidateObject: { + dbType: 'POSTGRES', + name: 'test', + dbConnectionConfig: { + foo: false, + }, + }, + }, + + { + label: 'rejects a missing name', + expected: false, + candidateObject: { + dbType: 'POSTGRES', + dbConnectionConfig: { + connectionUriSsmPath: '/path', + }, + }, + }, + + { + label: 'rejects a non-object, non-function value', + expected: false, + candidateObject: 123, + }, + + { + label: 'rejects an undefined value', + expected: false, + candidateObject: 123, + }, + ])('$label', ({ candidateObject, expected }) => { + expect(isSQLLambdaModelDataSourceStrategy(candidateObject)).toEqual(expected); + }); + }); + + describe('isSqlModelDataSourceDbConnectionConfig', () => { + test.each([ + { + label: 'accepts an SSM connection URI config with a single value', + expected: true, + candidateObject: { + connectionUriSsmPath: '/path', + }, + }, + + { + label: 'accepts an SSM connection URI config with an array value', + expected: true, + candidateObject: { + connectionUriSsmPath: ['/path1', '/path2'], + }, + }, + + { + label: 'accepts a function with a single value', + expected: true, + candidateObject: new MockConnectionConfig('/path1'), + }, + + { + label: 'accepts a function with an array value', + expected: true, + candidateObject: new MockConnectionConfig(['/path1', '/path2']), + }, + + { + label: 'rejects a function with a missing value', + expected: false, + candidateObject: new MockConnectionConfig(undefined), + }, + + { + label: 'rejects a non-object, non-function value', + expected: false, + candidateObject: 123, + }, + + { + label: 'rejects an undefined value', + expected: false, + candidateObject: 123, + }, + + { + label: 'accepts an SSM individual parameter config', + expected: true, + candidateObject: { + hostnameSsmPath: '/hostnameSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + passwordSsmPath: '/passwordSsmPath', + databaseNameSsmPath: '/databaseNameSsmPath', + }, + }, + + { + label: 'rejects an SSM individual parameter config with a missing value', + expected: false, + candidateObject: { + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + passwordSsmPath: '/passwordSsmPath', + databaseNameSsmPath: '/databaseNameSsmPath', + }, + }, + + { + label: 'accepts an secrets manager config', + expected: true, + candidateObject: { + secretArn: 'arn:aws:secretsmanager:::secret', + port: 1234, + databaseName: 'databaseName', + hostname: 'hostname', + }, + }, + ])('$label', ({ candidateObject, expected }) => { + expect(isSqlModelDataSourceDbConnectionConfig(candidateObject)).toEqual(expected); + }); + }); +}); diff --git a/packages/amplify-graphql-auth-transformer/API.md b/packages/amplify-graphql-auth-transformer/API.md index 3958e675f5..755b401dd4 100644 --- a/packages/amplify-graphql-auth-transformer/API.md +++ b/packages/amplify-graphql-auth-transformer/API.md @@ -110,6 +110,8 @@ export class AuthTransformer extends TransformerAuthBase implements TransformerA // (undocumented) addAutoGeneratedRelationalFields: (ctx: TransformerContextProvider, def: ObjectTypeDefinitionNode, allowedFields: Set, fields: readonly string[]) => void; // (undocumented) + addCustomOperationFieldsToAuthNonModelConfig: (ctx: TransformerTransformSchemaStepContextProvider) => void; + // (undocumented) addFieldResolverForDynamicAuth: (ctx: TransformerContextProvider, def: ObjectTypeDefinitionNode, typeName: string, fieldName: string) => void; // (undocumented) addFieldsToObject: (ctx: TransformerTransformSchemaStepContextProvider, modelName: string, ownerFields: Array) => void; diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/iam-custom-operations.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/iam-custom-operations.test.ts new file mode 100644 index 0000000000..2d3d3864ec --- /dev/null +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/iam-custom-operations.test.ts @@ -0,0 +1,517 @@ +import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; +import { mockSqlDataSourceStrategy, testTransform } from '@aws-amplify/graphql-transformer-test-utils'; +import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; +import { SqlTransformer } from '@aws-amplify/graphql-sql-transformer'; +import { + constructDataSourceStrategies, + constructSqlDirectiveDataSourceStrategies, + DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY, + isSqlStrategy, +} from '@aws-amplify/graphql-transformer-core'; +import { + AppSyncAuthConfiguration, + ModelDataSourceStrategy, + SqlDirectiveDataSourceStrategy, + SynthParameters, + TransformerPluginProvider, +} from '@aws-amplify/graphql-transformer-interfaces'; +import { AuthTransformer } from '../graphql-auth-transformer'; + +const makeAuthConfig = (): AppSyncAuthConfiguration => ({ + defaultAuthentication: { + authenticationType: 'API_KEY', + }, + additionalAuthenticationProviders: [ + { + authenticationType: 'AWS_IAM', + }, + { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }, + ], +}); + +const makeSynthParameters = (): Partial => ({ + enableIamAccess: true, +}); + +const makeTransformers = (): TransformerPluginProvider[] => [ + new ModelTransformer(), + new AuthTransformer(), + new PrimaryKeyTransformer(), + new SqlTransformer(), +]; + +const makeSqlDirectiveDataSourceStrategies = (schema: string, strategy: ModelDataSourceStrategy): SqlDirectiveDataSourceStrategy[] => + isSqlStrategy(strategy) ? constructSqlDirectiveDataSourceStrategies(schema, strategy) : []; + +const strategyTypes = ['DDB', 'SQL'] as const; + +const makeStrategy = (strategyType: 'DDB' | 'SQL'): ModelDataSourceStrategy => + strategyType === 'SQL' ? mockSqlDataSourceStrategy() : DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY; + +/** + * Tests that custom operations always get an `@aws_iam` directive if `enableIamAuthorizationMode` (which maps to the `enableIamAccess` + * flag) is true. In Gen 2, IAM access is globally enabled, to allow console Admin use cases, but customers do not have a way to specify it, + * so it is expected that IAM auth is enabled by Amplify. Even if a custom operation has other auth modes, applied, `@aws_iam` should be + * added by the transformer. + * + * Note: + * - Every schema includes an explicit ID and `@primaryKey` directive, so the schema is suitable for both DDB & SQL strategies + * - We aren't exhaustively testing every combination of auth rule and `enableIamAuthorizationMode` since we have other tests that do that. + * This suite is intended to ensure that aws_iam gets applied. + */ +describe('Custom operations have @aws_iam directives when enableIamAuthorizationMode is true', () => { + describe.each(strategyTypes)('Using %s', (strategyType) => { + test('Model is not present', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Query { + getFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "SELECT 1")' : ''} + } + type Mutation { + updateFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "UPDATE FOO set content=1")' : ''} + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + expect(out.schema).toMatch(/getFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: String.*@aws_iam/); + }); + + test('Model is not present and custom operations include an explicit auth rule from Gen 2 schema builder', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Query { + getFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "SELECT 1")' : ''} @aws_cognito_user_pools + } + type Mutation { + updateFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "UPDATE FOO set content=1")' : ''} @aws_cognito_user_pools + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) @aws_cognito_user_pools + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + expect(out.schema).toMatch(/getFooCustom: String.*@aws_cognito_user_pools.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: String.*@aws_cognito_user_pools.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: String.*@aws_cognito_user_pools.*@aws_iam/); + }); + + test('Model is present', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Foo @model @auth(rules: [{ allow: public }]) { + id: ID! @primaryKey + content: String + } + type Query { + getFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "SELECT 1")' : ''} + } + type Mutation { + updateFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "UPDATE FOO set content=1")' : ''} + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + expect(out.schema).toMatch(/getFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: String.*@aws_iam/); + }); + + test('Model is present and custom operations include an explicit auth rule from Gen 2 schema builder', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Foo @model @auth(rules: [{ allow: public }]) { + id: ID! @primaryKey + content: String + } + type Query { + getFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "SELECT 1")' : ''} @aws_cognito_user_pools + } + type Mutation { + updateFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "UPDATE FOO set content=1")' : ''} @aws_cognito_user_pools + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) @aws_cognito_user_pools + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + expect(out.schema).toMatch(/getFooCustom: String.*@aws_cognito_user_pools.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: String.*@aws_cognito_user_pools.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: String.*@aws_cognito_user_pools.*@aws_iam/); + }); + + test('Model is present but queries are disabled', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Foo @model(queries: null) @auth(rules: [{ allow: public }]) { + id: ID! @primaryKey + content: String + } + type Query { + getFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "SELECT 1")' : ''} + } + type Mutation { + updateFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "UPDATE FOO set content=1")' : ''} + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + expect(out.schema).toMatch(/getFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: String.*@aws_iam/); + }); + + test('Model is present but mutations are disabled', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Foo @model(mutations: null) @auth(rules: [{ allow: public }]) { + id: ID! @primaryKey + content: String + } + type Query { + getFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "SELECT 1")' : ''} + } + type Mutation { + updateFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "UPDATE FOO set content=1")' : ''} + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + expect(out.schema).toMatch(/getFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: String.*@aws_iam/); + }); + + test('Model is present but subscriptions are disabled', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Foo @model(subscriptions: null) @auth(rules: [{ allow: public }]) { + id: ID! @primaryKey + content: String + } + type Query { + getFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "SELECT 1")' : ''} + } + type Mutation { + updateFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "UPDATE FOO set content=1")' : ''} + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + expect(out.schema).toMatch(/getFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: String.*@aws_iam/); + }); + + test('Does not add duplicate @aws_iam directive if already present', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Query { + getFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "SELECT 1")' : ''} @aws_iam + } + type Mutation { + updateFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "UPDATE FOO set content=1")' : ''} @aws_iam + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) @aws_iam + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + expect(out.schema).toMatch(/getFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: String.*@aws_iam/); + expect(out.schema).not.toMatch(/getFooCustom: String.*@aws_iam.*@aws_iam/); + expect(out.schema).not.toMatch(/updateFooCustom: String.*@aws_iam.*@aws_iam/); + expect(out.schema).not.toMatch(/onUpdateFooCustom: String.*@aws_iam.*@aws_iam/); + }); + + test('Adds @aws_iam directive if sandbox is enabled', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Query { + getFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "SELECT 1")' : ''} + } + type Mutation { + updateFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "UPDATE FOO set content=1")' : ''} + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + transformers: makeTransformers(), + transformParameters: { + sandboxModeEnabled: true, + }, + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + expect(out.schema).toMatch(/getFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: String.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: String.*@aws_iam/); + }); + + test('Does not add if neither sandbox nor enableIamAuthorizationMode is enabled', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Query { + getFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "SELECT 1")' : ''} + } + type Mutation { + updateFooCustom: String ${isSqlStrategy(strategy) ? '@sql(statement: "UPDATE FOO set content=1")' : ''} + } + type Subscription { + onUpdateFooCustom: String @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + expect(out.schema).not.toMatch(/getFooCustom: String.*@aws_iam/); + expect(out.schema).not.toMatch(/updateFooCustom: String.*@aws_iam/); + expect(out.schema).not.toMatch(/onUpdateFooCustom: String.*@aws_iam/); + }); + + // TODO: Enable this test once we fix https://github.com/aws-amplify/amplify-category-api/issues/2929 + test.skip('Adds @aws_iam to non-model custom types when there is no model', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Foo { + description: String + } + type Query { + getFooCustom: Foo + } + type Mutation { + updateFooCustom: Foo + } + type Subscription { + onUpdateFooCustom: Foo @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + // Expect the custom operations to be authorized + expect(out.schema).toMatch(/getFooCustom: Foo.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: Foo.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: Foo.*@aws_iam/); + + // Also expect the custom type referenced by the custom operation to be authorized + expect(out.schema).toMatch(/type Foo.*@aws_iam/); + }); + + // TODO: Enable this test once we fix https://github.com/aws-amplify/amplify-category-api/issues/2929 + test.skip('Adds @aws_iam to non-model custom types when there is a model', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Todo @model { + id: ID! @primaryKey + done: Boolean + } + type Foo { + description: String + } + type Query { + getFooCustom: Foo + } + type Mutation { + updateFooCustom: Foo + } + type Subscription { + onUpdateFooCustom: Foo @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + // Expect the custom operations to be authorized + expect(out.schema).toMatch(/getFooCustom: Foo.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: Foo.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: Foo.*@aws_iam/); + + // Also expect the custom type referenced by the custom operation to be authorized + expect(out.schema).toMatch(/type Foo.*@aws_iam/); + }); + + test('Adds @aws_iam to non-model custom types when there is some other auth directive on the field', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Foo { + description: String @auth(rules: [{ allow: groups, groups: ["ZZZ_DOES_NOT_EXIST"] }]) + } + type Query { + getFooCustom: Foo + } + type Mutation { + updateFooCustom: Foo + } + type Subscription { + onUpdateFooCustom: Foo @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + // Expect the custom operations to be authorized + expect(out.schema).toMatch(/getFooCustom: Foo.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: Foo.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: Foo.*@aws_iam/); + + // Also expect the custom type referenced by the custom operation to be authorized + expect(out.schema).toMatch(/description: String.*@aws_iam/); + }); + + test('Does not add duplicate @aws_iam directive to custom type if already present', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + type Foo @aws_iam { + description: String + } + type Query { + getFooCustom: Foo ${isSqlStrategy(strategy) ? '@sql(statement: "SELECT 1")' : ''} + } + type Mutation { + updateFooCustom: Foo ${isSqlStrategy(strategy) ? '@sql(statement: "UPDATE FOO set content=1")' : ''} + } + type Subscription { + onUpdateFooCustom: Foo @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + // Expect the custom operations to be authorized + expect(out.schema).toMatch(/getFooCustom: Foo.*@aws_iam/); + expect(out.schema).toMatch(/updateFooCustom: Foo.*@aws_iam/); + expect(out.schema).toMatch(/onUpdateFooCustom: Foo.*@aws_iam/); + + // Also expect the custom type referenced by the custom operation to be authorized + expect(out.schema).toMatch(/type Foo.*@aws_iam/); + expect(out.schema).not.toMatch(/type Foo.*@aws_iam.*@aws_iam/); + }); + }); +}); diff --git a/packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts b/packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts index adf021a188..4ac4990c89 100644 --- a/packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts +++ b/packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts @@ -1,21 +1,20 @@ import { DirectiveWrapper, - TransformerContractError, - TransformerAuthBase, - InvalidDirectiveError, - MappingTemplate, - TransformerResolver, - getSortKeyFieldNames, generateGetArgumentsInput, - isSqlModel, - getModelDataSourceNameForTypeName, - isModelType, - getFilterInputName, getConditionInputName, + getFilterInputName, + getModelDataSourceNameForTypeName, + getSortKeyFieldNames, getSubscriptionFilterInputName, - getConnectionName, - InputFieldWrapper, + InvalidDirectiveError, + isBuiltInGraphqlNode, isDynamoDbModel, + isModelType, + isSqlModel, + MappingTemplate, + TransformerAuthBase, + TransformerContractError, + TransformerResolver, } from '@aws-amplify/graphql-transformer-core'; import { DataSourceProvider, @@ -345,8 +344,34 @@ export class AuthTransformer extends TransformerAuthBase implements TransformerA } }; + /** + * Adds custom Queries, Mutations, and Subscriptions to the authNonModelConfig map to ensure they are included when adding implicit + * aws_iam auth directives. + */ + addCustomOperationFieldsToAuthNonModelConfig = (ctx: TransformerTransformSchemaStepContextProvider): void => { + if (!ctx.transformParameters.sandboxModeEnabled && !ctx.synthParameters.enableIamAccess) { + return; + } + + const hasAwsIamDirective = (field: FieldDefinitionNode): boolean => { + return field.directives?.some((dir) => dir.name.value === 'aws_iam'); + }; + + const allObjects = ctx.inputDocument.definitions.filter(isBuiltInGraphqlNode); + allObjects.forEach((object) => { + const typeName = object.name.value; + const fieldsWithoutIamDirective = object.fields.filter((field) => !hasAwsIamDirective(field)); + fieldsWithoutIamDirective.forEach((field) => { + addDirectivesToField(ctx, typeName, field.name.value, [makeDirective('aws_iam', [])]); + }); + }); + }; + transformSchema = (context: TransformerTransformSchemaStepContextProvider): void => { + this.addCustomOperationFieldsToAuthNonModelConfig(context); + const searchableAggregateServiceDirectives = new Set(); + const getOwnerFields = (acm: AccessControlMatrix): string[] => acm.getRoles().reduce((prev: string[], role: string) => { if (this.roleMap.get(role)!.strategy === 'owner') prev.push(this.roleMap.get(role)!.entity!); diff --git a/packages/amplify-graphql-transformer-core/API.md b/packages/amplify-graphql-transformer-core/API.md index f498d42d04..7a0dcb8d65 100644 --- a/packages/amplify-graphql-transformer-core/API.md +++ b/packages/amplify-graphql-transformer-core/API.md @@ -453,11 +453,11 @@ export class InvalidTransformerError extends Error { export const isAmplifyDynamoDbModelDataSourceStrategy: (strategy: ModelDataSourceStrategy) => strategy is AmplifyDynamoDbModelDataSourceStrategy; // @public (undocumented) -export const isBuiltInGraphqlNode: (obj: DefinitionNode) => obj is ObjectTypeDefinitionNode | (InterfaceTypeDefinitionNode & { +export const isBuiltInGraphqlNode: (obj: DefinitionNode) => obj is (ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode) & { name: { - value: 'Mutation' | 'Query'; + value: 'Mutation' | 'Query' | 'Subscription'; }; -}); +}; // @public (undocumented) export const isDefaultDynamoDbModelDataSourceStrategy: (strategy: ModelDataSourceStrategy) => strategy is DefaultDynamoDbModelDataSourceStrategy; @@ -478,11 +478,11 @@ function isLambdaSyncConfig(syncConfig: SyncConfig): syncConfig is SyncConfigLam export const isModelType: (ctx: DataSourceStrategiesProvider, typename: string) => boolean; // @public (undocumented) -export const isMutationNode: (obj: DefinitionNode) => obj is ObjectTypeDefinitionNode | (InterfaceTypeDefinitionNode & { +export const isMutationNode: (obj: DefinitionNode) => obj is (ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode) & { name: { value: 'Mutation'; }; -}); +}; // @public (undocumented) export const isObjectTypeDefinitionNode: (obj: DefinitionNode) => obj is ObjectTypeDefinitionNode; @@ -494,11 +494,11 @@ export const isPostgresDbType: (dbType: ModelDataSourceStrategyDbType) => dbType export const isPostgresModel: (ctx: DataSourceStrategiesProvider, typename: string) => boolean; // @public (undocumented) -export const isQueryNode: (obj: DefinitionNode) => obj is ObjectTypeDefinitionNode | (InterfaceTypeDefinitionNode & { +export const isQueryNode: (obj: DefinitionNode) => obj is (ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode) & { name: { value: 'Query'; }; -}); +}; // @public (undocumented) export const isQueryType: (typeName: string) => typeName is "Query"; diff --git a/packages/amplify-graphql-transformer-core/src/utils/graphql-utils.ts b/packages/amplify-graphql-transformer-core/src/utils/graphql-utils.ts index 9908147827..5de1ac9bbd 100644 --- a/packages/amplify-graphql-transformer-core/src/utils/graphql-utils.ts +++ b/packages/amplify-graphql-transformer-core/src/utils/graphql-utils.ts @@ -17,25 +17,25 @@ export const isBuiltInGraphqlType = (typeName: string): typeName is 'Mutation' | export const isMutationNode = ( obj: DefinitionNode, -): obj is ObjectTypeDefinitionNode | (InterfaceTypeDefinitionNode & { name: { value: 'Mutation' } }) => { +): obj is (ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode) & { name: { value: 'Mutation' } } => { return isObjectTypeDefinitionNode(obj) && isMutationType(obj.name.value); }; export const isQueryNode = ( obj: DefinitionNode, -): obj is ObjectTypeDefinitionNode | (InterfaceTypeDefinitionNode & { name: { value: 'Query' } }) => { +): obj is (ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode) & { name: { value: 'Query' } } => { return isObjectTypeDefinitionNode(obj) && isQueryType(obj.name.value); }; export const isSubscriptionNode = ( obj: DefinitionNode, -): obj is ObjectTypeDefinitionNode | (InterfaceTypeDefinitionNode & { name: { value: 'Subscription' } }) => { +): obj is (ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode) & { name: { value: 'Subscription' } } => { return isObjectTypeDefinitionNode(obj) && isSubscriptionType(obj.name.value); }; export const isBuiltInGraphqlNode = ( obj: DefinitionNode, -): obj is ObjectTypeDefinitionNode | (InterfaceTypeDefinitionNode & { name: { value: 'Mutation' | 'Query' } }) => { +): obj is (ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode) & { name: { value: 'Mutation' | 'Query' | 'Subscription' } } => { return isMutationNode(obj) || isQueryNode(obj) || isSubscriptionNode(obj); }; From d690bfa7764bddb60db5dcb66bb85027e6a39598 Mon Sep 17 00:00:00 2001 From: Kevin Shan Date: Thu, 10 Oct 2024 08:25:12 -0700 Subject: [PATCH 15/15] fix: appsync ttl correct duration time unit in ms (#2928) Signed-off-by: Kevin Shan --- .../src/internal/authorization-modes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/amplify-graphql-api-construct/src/internal/authorization-modes.ts b/packages/amplify-graphql-api-construct/src/internal/authorization-modes.ts index 27b142d3dd..20b5eb88cd 100644 --- a/packages/amplify-graphql-api-construct/src/internal/authorization-modes.ts +++ b/packages/amplify-graphql-api-construct/src/internal/authorization-modes.ts @@ -91,8 +91,8 @@ const convertAuthModeToAuthProvider = (authMode: AuthorizationConfigMode): AppSy name: authMode.oidcProviderName, issuerUrl: authMode.oidcIssuerUrl, clientId: authMode.clientId, - iatTTL: authMode.tokenExpiryFromIssue.toSeconds(), - authTTL: authMode.tokenExpiryFromAuth.toSeconds(), + iatTTL: authMode.tokenExpiryFromIssue.toMilliseconds(), + authTTL: authMode.tokenExpiryFromAuth.toMilliseconds(), }, }; case 'AWS_LAMBDA':