From 414da5a749699f9081fb81ff414373d7bf4959e4 Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Date: Fri, 10 Nov 2023 12:28:50 -0800 Subject: [PATCH 1/5] chore(actions): add ci job for branch protection (#12524) * chore(actions): add ci job for branch protection * Update .github/workflows/pr.yml --------- Co-authored-by: Ashwin Kumar Co-authored-by: Jim Blanchard --- .github/workflows/pr.yml | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a9b133e35a6..f0db4a8c55d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -45,9 +45,29 @@ jobs: - github-actions-test - tsc-compliance-test runs-on: ubuntu-latest - # This is a bit of a hack - Branch protections depend upon selected - # workflows that run on hardware, not parents of many callable workflows. - # Adding this so that unit and bundle checks can be a single protection line. + if: success() # only run when all checks have passed + # store success output flag for ci job + outputs: + success: ${{ steps.setoutput.outputs.success }} steps: - - name: All tests passed - run: echo "All tests passed" + - id: setoutput + run: echo "::set-output name=success::true" + ci: + runs-on: ubuntu-latest + if: always() # always run, so we never skip the check + name: ci - Unit and Bundle tests have passed + needs: all-unit-tests-pass + env: + PASSED: ${{ needs.all-unit-tests-pass.outputs.success }} + steps: + # this job passes only when output of all-unit-tests-pass job is set + # in case at least one of the checks fails, all-unit-tests-pass is skipped + # and the output will not be set, which will then cause the ci job to fail + - run: | + if [[ $PASSED == "true" ]]; then + echo "All checks have passed" + exit 0 + else + echo "One or more checks have failed" + exit 1 + fi From 86ab927bfb425085294d78d6a956338b055070ad Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Date: Fri, 10 Nov 2023 12:57:56 -0800 Subject: [PATCH 2/5] chore(auth): add AtLeastOne type (#12558) * chore(auth): add AtLeastOne type * fix: linter issue * Update packages/core/src/singleton/Auth/types.ts Co-authored-by: Francisco Rodriguez --------- Co-authored-by: Ashwin Kumar Co-authored-by: Jim Blanchard Co-authored-by: Francisco Rodriguez --- packages/core/src/singleton/Auth/types.ts | 8 ++++++-- packages/core/src/singleton/Auth/utils/index.ts | 12 +++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/core/src/singleton/Auth/types.ts b/packages/core/src/singleton/Auth/types.ts index 69d0e8fff7e..9ef1f8f26b7 100644 --- a/packages/core/src/singleton/Auth/types.ts +++ b/packages/core/src/singleton/Auth/types.ts @@ -1,8 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 + +import { AtLeastOne } from '../types'; + // From https://github.com/awslabs/aws-jwt-verify/blob/main/src/safe-json-parse.ts // From https://github.com/awslabs/aws-jwt-verify/blob/main/src/jwt-model.ts - interface JwtPayloadStandardFields { exp?: number; // expires: https://tools.ietf.org/html/rfc7519#section-4.1.4 iss?: string; // issuer: https://tools.ietf.org/html/rfc7519#section-4.1.1 @@ -97,7 +99,9 @@ export type AuthConfigUserAttributes = Partial< Record >; -export type AuthConfig = StrictUnion< +export type AuthConfig = AtLeastOne; + +export type CognitoProviderConfig = StrictUnion< | AuthIdentityPoolConfig | AuthUserPoolConfig | AuthUserPoolAndIdentityPoolConfig diff --git a/packages/core/src/singleton/Auth/utils/index.ts b/packages/core/src/singleton/Auth/utils/index.ts index 7cc665e9d9e..43301206b44 100644 --- a/packages/core/src/singleton/Auth/utils/index.ts +++ b/packages/core/src/singleton/Auth/utils/index.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 import { AuthConfigurationErrorCode, assert } from './errorHelpers'; import { base64Decoder } from '../../../utils/convert'; - import { AuthConfig, JWT, @@ -12,6 +11,7 @@ import { CognitoUserPoolAndIdentityPoolConfig, CognitoIdentityPoolConfig, StrictUnion, + OAuthConfig, } from '../types'; export function assertTokenProviderConfig( @@ -38,20 +38,22 @@ export function assertTokenProviderConfig( } export function assertOAuthConfig( - cognitoConfig?: CognitoUserPoolConfig | CognitoUserPoolAndIdentityPoolConfig -): asserts cognitoConfig is CognitoUserPoolWithOAuthConfig { + cognitoConfig?: AuthConfig['Cognito'] +): asserts cognitoConfig is AuthConfig['Cognito'] & { + loginWith: { + oauth: OAuthConfig; + }; +} { const validOAuthConfig = !!cognitoConfig?.loginWith?.oauth?.domain && !!cognitoConfig?.loginWith?.oauth?.redirectSignOut && !!cognitoConfig?.loginWith?.oauth?.redirectSignIn && !!cognitoConfig?.loginWith?.oauth?.responseType; - return assert( validOAuthConfig, AuthConfigurationErrorCode.OAuthNotConfigureException ); } - export function assertIdentityPoolIdConfig( cognitoConfig?: StrictUnion< | CognitoUserPoolConfig From 3cfd338680440686b590b713388a5f0984d0feee Mon Sep 17 00:00:00 2001 From: ManojNB Date: Fri, 10 Nov 2023 13:16:47 -0800 Subject: [PATCH 3/5] chore(auth): additional granular unit tests for identityIdStore and tokenOrchestrator (#12527) --- .../providers/cognito/identityIdStore.test.ts | 96 +++++++++++++++++++ .../cognito/testUtils/authApiTestParams.ts | 29 ++++-- .../cognito/tokenOrchestrator.test.ts | 77 +++++++++++++++ 3 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 packages/auth/__tests__/providers/cognito/identityIdStore.test.ts create mode 100644 packages/auth/__tests__/providers/cognito/tokenOrchestrator.test.ts diff --git a/packages/auth/__tests__/providers/cognito/identityIdStore.test.ts b/packages/auth/__tests__/providers/cognito/identityIdStore.test.ts new file mode 100644 index 00000000000..3a9fa1ab049 --- /dev/null +++ b/packages/auth/__tests__/providers/cognito/identityIdStore.test.ts @@ -0,0 +1,96 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { DefaultIdentityIdStore } from '../../../src/providers/cognito'; +import { Identity, ResourcesConfig } from '@aws-amplify/core'; + +const mockKeyValueStorage = { + setItem: jest.fn(), + getItem: jest.fn(), + removeItem: jest.fn(), + clear: jest.fn(), +}; + +const validAuthConfig: ResourcesConfig = { + Auth: { + Cognito: { + userPoolId: 'us-east-1_test-id', + identityPoolId: 'us-east-1:test-id', + userPoolClientId: 'test-id', + allowGuestAccess: true, + }, + }, +}; +const validAuthKey = { + identityId: `com.amplify.Cognito.${ + validAuthConfig.Auth!.Cognito!.identityPoolId + }.identityId`, +}; +const validGuestIdentityId: Identity = { type: 'guest', id: 'guest-id' }; +const validPrimaryIdentityId: Identity = { type: 'primary', id: 'primary-id' }; + +const noIdentityPoolIdAuthConfig: ResourcesConfig = { + Auth: { + Cognito: { + userPoolId: 'us-east-1_test-id', + userPoolClientId: 'test-id', + }, + }, +}; + +describe('DefaultIdentityIdStore', () => { + const defaultIdStore = new DefaultIdentityIdStore(mockKeyValueStorage); + describe('Happy Path Cases:', () => { + beforeAll(() => { + defaultIdStore.setAuthConfig(validAuthConfig.Auth!); + }); + it('Should set the Auth config required to form the storage keys', async () => { + expect(defaultIdStore._authKeys).toEqual(validAuthKey); + }); + it('Should store guest identityId in keyValueStorage', async () => { + defaultIdStore.storeIdentityId(validGuestIdentityId); + expect(mockKeyValueStorage.setItem).toBeCalledWith( + validAuthKey.identityId, + validGuestIdentityId.id + ); + expect(defaultIdStore._primaryIdentityId).toBeUndefined(); + }); + it('Should load guest identityId from keyValueStorage', async () => { + mockKeyValueStorage.getItem.mockReturnValue(validGuestIdentityId.id); + + expect(await defaultIdStore.loadIdentityId()).toEqual( + validGuestIdentityId + ); + }); + it('Should store primary identityId in keyValueStorage', async () => { + defaultIdStore.storeIdentityId(validPrimaryIdentityId); + expect(mockKeyValueStorage.removeItem).toBeCalledWith( + validAuthKey.identityId + ); + expect(defaultIdStore._primaryIdentityId).toEqual( + validPrimaryIdentityId.id + ); + }); + it('Should load primary identityId from keyValueStorage', async () => { + expect(await defaultIdStore.loadIdentityId()).toEqual( + validPrimaryIdentityId + ); + }); + it('Should clear the cached identityId', async () => { + defaultIdStore.clearIdentityId(); + expect(mockKeyValueStorage.removeItem).toBeCalledWith( + validAuthKey.identityId + ); + expect(defaultIdStore._primaryIdentityId).toBeUndefined(); + }); + }); + describe('Error Path Cases:', () => { + it('Should assert when identityPoolId is not present while setting the auth config', async () => { + try { + defaultIdStore.setAuthConfig(noIdentityPoolIdAuthConfig.Auth!); + } catch (e) { + expect(e.name).toEqual('InvalidIdentityPoolIdException'); + } + }); + }); +}); diff --git a/packages/auth/__tests__/providers/cognito/testUtils/authApiTestParams.ts b/packages/auth/__tests__/providers/cognito/testUtils/authApiTestParams.ts index bd91ce15a63..f59dcf04153 100644 --- a/packages/auth/__tests__/providers/cognito/testUtils/authApiTestParams.ts +++ b/packages/auth/__tests__/providers/cognito/testUtils/authApiTestParams.ts @@ -148,25 +148,34 @@ export const authAPITestParams = { // Test values ValidAuthTokens: { idToken: decodeJWT( - 'eyJraWQiOiIyd1FTbElUQ2N0bWVMdTYwY3hzRFJPOW9DXC93eDZDdVMzT2lQbHRJRldYVT0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIzOGEwODU1Ny1hMTFkLTQzYjEtYjc5Yi03ZTNjNDE2YWUzYzciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tXC91cy1lYXN0LTJfUTRpaTdlZFRJIiwiY29nbml0bzp1c2VybmFtZSI6InRlc3QyIiwib3JpZ2luX2p0aSI6ImRiM2QxOGE1LTViZTAtNDVmOS05Y2RjLTI3OWQyMmJmNzgxZCIsImF1ZCI6IjZ1bG1sYTc0Y245cDlhdmEwZmcxYnV1cjhxIiwiZXZlbnRfaWQiOiJhZjRjMmM5NC04ZTY0LTRkYWYtYjc5ZS02NTE0NTEyMjE3OTAiLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTY5MDkzMjM0MCwiZXhwIjoxNjkwOTM1OTQwLCJpYXQiOjE2OTA5MzIzNDAsImp0aSI6ImVhM2JmNmNlLWEyZWUtNGJiMC05MjdkLWNjMzRjYzRhMWVjMiIsImVtYWlsIjoiYW16bm1hbm9qQGdtYWlsLmNvbSJ9.i71wkSBPZt8BlBFMZPILJ6RsfDaJx0xqriD9y6ly3LnNB2vNAIOZqPLcCKEi8u0obyoFIK_EY7jKVRva5wbDDcHGt5YrnjT3SsWc1FGVUhrPW6IzEwbfYkUsbVGYjfO1hqTMW7q3FHvJ4yFjLDIUHQe-1_NogYeuhjrNxEupOPmE5-52N4dRriZ0DlHD4fe7gqL8B6AJXr5np1XaxZySU4KpdePwIp1Nb2fkolMEGHvOANHqWdBe5I0vRhAh0MDJ6IxvEr65tnaJNgVQuQaZFR4kQlpjemvB7kaVQ-SpH-tV_zXzqpwr_OEH6dgGMcxIsFrBFC8AGQnGXlSsS-5ThQ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIHRoZSBzZWNvbmQiLCJpYXQiOjE1MTYyMzkwMjIsImlzcyI6Imh0dHBzOi8vdGVzdC5jb20iLCJleHAiOjE3MTAyOTMxMzB9.kpvsHfKH4JvCecECmb26Pl6HaedVX7PNiiF_8AlAbYc' ), accessToken: decodeJWT( - 'eyJraWQiOiJsUjZHYWlsakJyNVl6Z2tSakxoenNLR2IwUkFqd2FmbVg3RTlJOFRRdUE0PSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiIzOGEwODU1Ny1hMTFkLTQzYjEtYjc5Yi03ZTNjNDE2YWUzYzciLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0yLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMl9RNGlpN2VkVEkiLCJjbGllbnRfaWQiOiI2dWxtbGE3NGNuOXA5YXZhMGZnMWJ1dXI4cSIsIm9yaWdpbl9qdGkiOiJkYjNkMThhNS01YmUwLTQ1ZjktOWNkYy0yNzlkMjJiZjc4MWQiLCJldmVudF9pZCI6ImFmNGMyYzk0LThlNjQtNGRhZi1iNzllLTY1MTQ1MTIyMTc5MCIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiYXdzLmNvZ25pdG8uc2lnbmluLnVzZXIuYWRtaW4iLCJhdXRoX3RpbWUiOjE2OTA5MzIzNDAsImV4cCI6MTY5MDkzNTk0MCwiaWF0IjoxNjkwOTMyMzQwLCJqdGkiOiIzMjY2NWI4Zi04ZWFlLTQ5NzgtYjA1Ny00ODc1ZmFhNDBhMzUiLCJ1c2VybmFtZSI6InRlc3QyIn0.EHXtiMNrZQ0WzxWM8N15wXGVxLyxXkUaOzEf7Nj4yETpFsOQH1thufbxfu0e2Td0flDjiVTwTyeRD0Hue3_F4tC2o9_6kFlO9TBnQJnMI4mrSsbaTSTSgHJ8HS9YP7nDbcZ1QXFdWHlzPEoRSoJ9y_0oji8Bl3ZsyXIVCzSUfil_t0ZKhtprQnUakPDeqCunBT1oh-pqUsYC1g6lwS7vfucivJpuyxfnpcOEfQYY6VMlZxpDurEniOy7vgy6e8ElYpIdUzpBaRB_CvhDj6tYlnLRVTBOnKcRdckZMd69SJ8zTKtmxAsYbxF6DWZQTK6e82Rft1Uc5rLxKAD6VK92xA' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIHRoZSBzZWNvbmQiLCJpYXQiOjE1MTYyMzkwMjIsImlzcyI6Imh0dHBzOi8vdGVzdC5jb20iLCJleHAiOjE3MTAyOTMxMzB9.kpvsHfKH4JvCecECmb26Pl6HaedVX7PNiiF_8AlAbYc' + ), + accessTokenExpAt: Date.UTC(2023, 8, 24, 18, 55), + clockDrift: undefined, + metadata: undefined, + }, + ExpiredAuthTokens: { + idToken: decodeJWT( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTAyOTMxMzB9.1o9dQV9035dCO0nKDgZ-MwFf22Ptmysymt2ENyR5Mko' + ), + accessToken: decodeJWT( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTAyOTMxMzB9.1o9dQV9035dCO0nKDgZ-MwFf22Ptmysymt2ENyR5Mko' ), accessTokenExpAt: Date.UTC(2023, 8, 24, 18, 55), - clockDrift: undefined, metadata: undefined, }, NewValidAuthTokens: { idToken: decodeJWT( - 'yJraWQiOiIyd1FTbElUQ2N0bWVMdTYwY3hzRFJPOW9DXC93eDZDdVMzT2lQbHRJRldYVT0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIzOGEwODU1Ny1hMTFkLTQzYjEtYjc5Yi03ZTNjNDE2YWUzYzciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tXC91cy1lYXN0LTJfUTRpaTdlZFRJIiwiY29nbml0bzp1c2VybmFtZSI6InRlc3QyIiwib3JpZ2luX2p0aSI6ImRiM2QxOGE1LTViZTAtNDVmOS05Y2RjLTI3OWQyMmJmNzgxZCIsImF1ZCI6IjZ1bG1sYTc0Y245cDlhdmEwZmcxYnV1cjhxIiwiZXZlbnRfaWQiOiJhZjRjMmM5NC04ZTY0LTRkYWYtYjc5ZS02NTE0NTEyMjE3OTAiLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTY5MDkzMjM0MCwiZXhwIjoxNjkwOTM1OTQwLCJpYXQiOjE2OTA5MzIzNDAsImp0aSI6ImVhM2JmNmNlLWEyZWUtNGJiMC05MjdkLWNjMzRjYzRhMWVjMiIsImVtYWlsIjoiYW16bm1hbm9qQGdtYWlsLmNvbSJ9.i71wkSBPZt8BlBFMZPILJ6RsfDaJx0xqriD9y6ly3LnNB2vNAIOZqPLcCKEi8u0obyoFIK_EY7jKVRva5wbDDcHGt5YrnjT3SsWc1FGVUhrPW6IzEwbfYkUsbVGYjfO1hqTMW7q3FHvJ4yFjLDIUHQe-1_NogYeuhjrNxEupOPmE5-52N4dRriZ0DlHD4fe7gqL8B6AJXr5np1XaxZySU4KpdePwIp1Nb2fkolMEGHvOANHqWdBe5I0vRhAh0MDJ6IxvEr65tnaJNgVQuQaZFR4kQlpjemvB7kaVQ-SpH-tV_zXzqpwr_OEH6dgGMcxIsFrBFC8AGQnGXlSsS-5ThQ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIHRoZSBzZWNvbmQiLCJpYXQiOjE1MTYyMzkwMjIsImlzcyI6Imh0dHBzOi8vdGVzdC5jb20ifQ.5eGzqDYCAYmagLpVDc1kqRT1da1wPu0_1FAg6ZNAuj8' ), accessToken: decodeJWT( - 'eyJraWQiOiJsUjZHYWlsakJyNVl6Z2tSakxoenNLR2IwUkFqd2FmbVg3RTlJOFRRdUE0PSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiIzOGEwODU1Ny1hMTFkLTQzYjEtYjc5Yi03ZTNjNDE2YWUzYzciLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0yLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMl9RNGlpN2VkVEkiLCJjbGllbnRfaWQiOiI2dWxtbGE3NGNuOXA5YXZhMGZnMWJ1dXI4cSIsIm9yaWdpbl9qdGkiOiJkYjNkMThhNS01YmUwLTQ1ZjktOWNkYy0yNzlkMjJiZjc4MWQiLCJldmVudF9pZCI6ImFmNGMyYzk0LThlNjQtNGRhZi1iNzllLTY1MTQ1MTIyMTc5MCIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiYXdzLmNvZ25pdG8uc2lnbmluLnVzZXIuYWRtaW4iLCJhdXRoX3RpbWUiOjE2OTA5MzIzNDAsImV4cCI6MTY5MDkzNTk0MCwiaWF0IjoxNjkwOTMyMzQwLCJqdGkiOiIzMjY2NWI4Zi04ZWFlLTQ5NzgtYjA1Ny00ODc1ZmFhNDBhMzUiLCJ1c2VybmFtZSI6InRlc3QyIn0.EHXtiMNrZQ0WzxWM8N15wXGVxLyxXkUaOzEf7Nj4yETpFsOQH1thufbxfu0e2Td0flDjiVTwTyeRD0Hue3_F4tC2o9_6kFlO9TBnQJnMI4mrSsbaTSTSgHJ8HS9YP7nDbcZ1QXFdWHlzPEoRSoJ9y_0oji8Bl3ZsyXIVCzSUfil_t0ZKhtprQnUakPDeqCunBT1oh-pqUsYC1g6lwS7vfucivJpuyxfnpcOEfQYY6VMlZxpDurEniOy7vgy6e8ElYpIdUzpBaRB_CvhDj6tYlnLRVTBOnKcRdckZMd69SJ8zTKtmxAsYbxF6DWZQTK6e82Rft1Uc5rLxKAD6VK92xA' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIHRoZSBzZWNvbmQiLCJpYXQiOjE1MTYyMzkwMjIsImlzcyI6Imh0dHBzOi8vdGVzdC5jb20ifQ.5eGzqDYCAYmagLpVDc1kqRT1da1wPu0_1FAg6ZNAuj8' ), accessTokenExpAt: Date.UTC(2023, 8, 24, 18, 55), - clockDrift: undefined, metadata: undefined, }, @@ -175,15 +184,15 @@ export const authAPITestParams = { withValidAuthToken: { IdentityId: 'identity-id-test', Logins: { - 'cognito-idp.us-east-2.amazonaws.com/us-east-2_Q4ii7edTI': - 'eyJraWQiOiIyd1FTbElUQ2N0bWVMdTYwY3hzRFJPOW9DXC93eDZDdVMzT2lQbHRJRldYVT0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIzOGEwODU1Ny1hMTFkLTQzYjEtYjc5Yi03ZTNjNDE2YWUzYzciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tXC91cy1lYXN0LTJfUTRpaTdlZFRJIiwiY29nbml0bzp1c2VybmFtZSI6InRlc3QyIiwib3JpZ2luX2p0aSI6ImRiM2QxOGE1LTViZTAtNDVmOS05Y2RjLTI3OWQyMmJmNzgxZCIsImF1ZCI6IjZ1bG1sYTc0Y245cDlhdmEwZmcxYnV1cjhxIiwiZXZlbnRfaWQiOiJhZjRjMmM5NC04ZTY0LTRkYWYtYjc5ZS02NTE0NTEyMjE3OTAiLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTY5MDkzMjM0MCwiZXhwIjoxNjkwOTM1OTQwLCJpYXQiOjE2OTA5MzIzNDAsImp0aSI6ImVhM2JmNmNlLWEyZWUtNGJiMC05MjdkLWNjMzRjYzRhMWVjMiIsImVtYWlsIjoiYW16bm1hbm9qQGdtYWlsLmNvbSJ9.i71wkSBPZt8BlBFMZPILJ6RsfDaJx0xqriD9y6ly3LnNB2vNAIOZqPLcCKEi8u0obyoFIK_EY7jKVRva5wbDDcHGt5YrnjT3SsWc1FGVUhrPW6IzEwbfYkUsbVGYjfO1hqTMW7q3FHvJ4yFjLDIUHQe-1_NogYeuhjrNxEupOPmE5-52N4dRriZ0DlHD4fe7gqL8B6AJXr5np1XaxZySU4KpdePwIp1Nb2fkolMEGHvOANHqWdBe5I0vRhAh0MDJ6IxvEr65tnaJNgVQuQaZFR4kQlpjemvB7kaVQ-SpH-tV_zXzqpwr_OEH6dgGMcxIsFrBFC8AGQnGXlSsS-5ThQ', + 'test.com': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIHRoZSBzZWNvbmQiLCJpYXQiOjE1MTYyMzkwMjIsImlzcyI6Imh0dHBzOi8vdGVzdC5jb20iLCJleHAiOjE3MTAyOTMxMzB9.kpvsHfKH4JvCecECmb26Pl6HaedVX7PNiiF_8AlAbYc', }, }, withNewValidAuthToken: { IdentityId: 'identity-id-test', Logins: { - 'cognito-idp.us-east-2.amazonaws.com/us-east-2_Q4ii7edTI': - 'yJraWQiOiIyd1FTbElUQ2N0bWVMdTYwY3hzRFJPOW9DXC93eDZDdVMzT2lQbHRJRldYVT0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIzOGEwODU1Ny1hMTFkLTQzYjEtYjc5Yi03ZTNjNDE2YWUzYzciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tXC91cy1lYXN0LTJfUTRpaTdlZFRJIiwiY29nbml0bzp1c2VybmFtZSI6InRlc3QyIiwib3JpZ2luX2p0aSI6ImRiM2QxOGE1LTViZTAtNDVmOS05Y2RjLTI3OWQyMmJmNzgxZCIsImF1ZCI6IjZ1bG1sYTc0Y245cDlhdmEwZmcxYnV1cjhxIiwiZXZlbnRfaWQiOiJhZjRjMmM5NC04ZTY0LTRkYWYtYjc5ZS02NTE0NTEyMjE3OTAiLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTY5MDkzMjM0MCwiZXhwIjoxNjkwOTM1OTQwLCJpYXQiOjE2OTA5MzIzNDAsImp0aSI6ImVhM2JmNmNlLWEyZWUtNGJiMC05MjdkLWNjMzRjYzRhMWVjMiIsImVtYWlsIjoiYW16bm1hbm9qQGdtYWlsLmNvbSJ9.i71wkSBPZt8BlBFMZPILJ6RsfDaJx0xqriD9y6ly3LnNB2vNAIOZqPLcCKEi8u0obyoFIK_EY7jKVRva5wbDDcHGt5YrnjT3SsWc1FGVUhrPW6IzEwbfYkUsbVGYjfO1hqTMW7q3FHvJ4yFjLDIUHQe-1_NogYeuhjrNxEupOPmE5-52N4dRriZ0DlHD4fe7gqL8B6AJXr5np1XaxZySU4KpdePwIp1Nb2fkolMEGHvOANHqWdBe5I0vRhAh0MDJ6IxvEr65tnaJNgVQuQaZFR4kQlpjemvB7kaVQ-SpH-tV_zXzqpwr_OEH6dgGMcxIsFrBFC8AGQnGXlSsS-5ThQ', + 'test.com': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIHRoZSBzZWNvbmQiLCJpYXQiOjE1MTYyMzkwMjIsImlzcyI6Imh0dHBzOi8vdGVzdC5jb20iLCJleHAiOjE3MTAyOTMxMzB9.kpvsHfKH4JvCecECmb26Pl6HaedVX7PNiiF_8AlAbYc', }, }, }, diff --git a/packages/auth/__tests__/providers/cognito/tokenOrchestrator.test.ts b/packages/auth/__tests__/providers/cognito/tokenOrchestrator.test.ts new file mode 100644 index 00000000000..70d08deca59 --- /dev/null +++ b/packages/auth/__tests__/providers/cognito/tokenOrchestrator.test.ts @@ -0,0 +1,77 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { TokenOrchestrator } from '../../../src/providers/cognito'; +import { Hub, ResourcesConfig } from '@aws-amplify/core'; +import { authAPITestParams } from './testUtils/authApiTestParams'; +import { AMPLIFY_SYMBOL } from '@aws-amplify/core/internals/utils'; +jest.mock('@aws-amplify/core', () => ({ + ...jest.requireActual('@aws-amplify/core'), + Hub: { + dispatch: jest.fn(), + listen: jest.fn(), + }, +})); + +const mockAuthTokenStore = { + getLastAuthUser: jest.fn(), + loadTokens: jest.fn(), + storeTokens: jest.fn(), + clearTokens: jest.fn(), + setKeyValueStorage: jest.fn(), + getDeviceMetadata: jest.fn(), + clearDeviceMetadata: jest.fn(), +}; +const mockTokenRefresher = jest.fn(); +const validAuthConfig: ResourcesConfig = { + Auth: { + Cognito: { + userPoolId: 'us-east-1_test-id', + identityPoolId: 'us-east-1:test-id', + userPoolClientId: 'test-id', + allowGuestAccess: true, + }, + }, +}; + +describe('TokenOrchestrator', () => { + const tokenOrchestrator = new TokenOrchestrator(); + describe('Happy Path Cases:', () => { + beforeAll(() => { + tokenOrchestrator.setAuthConfig(validAuthConfig.Auth!); + tokenOrchestrator.setAuthTokenStore(mockAuthTokenStore); + tokenOrchestrator.setTokenRefresher(mockTokenRefresher); + mockAuthTokenStore.getLastAuthUser.mockResolvedValue('test-username'); + }); + it('Should get tokens', async () => { + mockAuthTokenStore.loadTokens.mockResolvedValue( + authAPITestParams.ValidAuthTokens + ); + + const tokensRes = await tokenOrchestrator.getTokens(); + expect(tokensRes).toEqual({ + accessToken: authAPITestParams.ValidAuthTokens.accessToken, + idToken: authAPITestParams.ValidAuthTokens.idToken, + signInDetails: undefined, + }); + }); + it('Should call tokenRefresher and return valid tokens', async () => { + mockAuthTokenStore.loadTokens.mockResolvedValue( + authAPITestParams.ExpiredAuthTokens + ); + mockTokenRefresher.mockResolvedValue(authAPITestParams.ValidAuthTokens); + const tokensRes = await tokenOrchestrator.getTokens(); + expect(tokensRes).toEqual({ + accessToken: authAPITestParams.ValidAuthTokens.accessToken, + idToken: authAPITestParams.ValidAuthTokens.idToken, + signInDetails: undefined, + }); + expect(Hub.dispatch).toHaveBeenCalledWith( + 'auth', + { event: 'tokenRefresh' }, + 'Auth', + AMPLIFY_SYMBOL + ); + }); + }); +}); From 92789fbb94f19d91f38fb1d90578160666901d0b Mon Sep 17 00:00:00 2001 From: Ivan Artemiev <29709626+iartemiev@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:39:54 -0500 Subject: [PATCH 4/5] fix: api/enable ts strict (#12560) Co-authored-by: Erin Beal Co-authored-by: Hui Zhao --- .vscode/launch.json | 2 +- .../api-graphql/__tests__/APIClient.test.ts | 10 +- .../api-graphql/__tests__/GraphQLAPI.test.ts | 6 +- .../__tests__/fixtures/modeled/schema.ts | 2 +- .../__tests__/resolveOwnerFields.test.ts | 9 +- .../__tests__/server/generateClient.test.ts | 8 +- packages/api-graphql/package.json | 14 +- packages/api-graphql/src/GraphQLAPI.ts | 4 +- .../AWSAppSyncRealTimeProvider/index.ts | 13 +- .../api-graphql/src/internals/APIClient.ts | 264 +++++++++++------- .../src/internals/InternalGraphQLAPI.ts | 50 ++-- .../src/internals/generateModelsProperty.ts | 13 +- .../src/internals/operations/get.ts | 78 +++--- .../src/internals/operations/list.ts | 55 ++-- .../src/internals/operations/observeQuery.ts | 24 +- .../src/internals/operations/subscription.ts | 20 +- .../src/internals/server/generateClient.ts | 2 +- .../server/generateModelsProperty.ts | 26 +- packages/api-graphql/src/types/index.ts | 14 + .../src/utils/ConnectionStateMonitor.ts | 4 +- .../src/utils/findIndexByFields.ts | 4 +- packages/api-graphql/tsconfig.json | 4 +- packages/api/package.json | 2 +- packages/api/src/internals/InternalAPI.ts | 16 +- packages/api/tsconfig.json | 4 +- packages/core/src/libraryUtils.ts | 12 +- packages/core/src/singleton/API/types.ts | 5 +- packages/datastore/__tests__/mutation.test.ts | 2 +- yarn.lock | 9 +- 29 files changed, 409 insertions(+), 267 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 116f5ef9480..377918c8061 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "--inspect-brk", "${workspaceRoot}/node_modules/.bin/jest", // Optionally specify a single test file to run/debug: - "generateClient.test.ts", + "GraphQLAPI.test.ts", "--runInBand", "--testTimeout", "600000", // 10 min timeout so jest doesn't error while we're stepping through code diff --git a/packages/api-graphql/__tests__/APIClient.test.ts b/packages/api-graphql/__tests__/APIClient.test.ts index 6fb84b1cabd..8c3b88bed58 100644 --- a/packages/api-graphql/__tests__/APIClient.test.ts +++ b/packages/api-graphql/__tests__/APIClient.test.ts @@ -1,3 +1,7 @@ +import { + SchemaModel, + ModelIntrospectionSchema, +} from '@aws-amplify/core/internals/utils'; import { normalizeMutationInput, flattenItems, @@ -6,8 +10,8 @@ import { } from '../src/internals/APIClient'; import config from './fixtures/modeled/amplifyconfiguration'; -const modelIntroSchema = config.modelIntrospection; -// +const modelIntroSchema = config.modelIntrospection as ModelIntrospectionSchema; + describe('APIClient', () => { describe('normalizeMutationInput', () => { // TODO: test all relationship combinations @@ -29,7 +33,7 @@ describe('APIClient', () => { todoNotesId: todo.id, }; - const noteModelDef = modelIntroSchema.models.Note; + const noteModelDef = modelIntroSchema.models.Note as SchemaModel; const normalized = normalizeMutationInput( note, diff --git a/packages/api-graphql/__tests__/GraphQLAPI.test.ts b/packages/api-graphql/__tests__/GraphQLAPI.test.ts index f6ee3655d50..cff9b2d3ee4 100644 --- a/packages/api-graphql/__tests__/GraphQLAPI.test.ts +++ b/packages/api-graphql/__tests__/GraphQLAPI.test.ts @@ -486,7 +486,7 @@ describe('API test', () => { url: new URL('https://localhost/graphql'), options: expect.objectContaining({ headers: expect.objectContaining({ 'X-Api-Key': 'FAKE-KEY' }), - signingServiceInfo: null, + signingServiceInfo: undefined, }), }); }); @@ -1066,7 +1066,7 @@ describe('API test', () => { someHeaderSetAtConfigThatWillBeOverridden: 'expectedValue', someOtherHeaderSetAtConfig: 'expectedValue', }), - signingServiceInfo: null, + signingServiceInfo: undefined, }), }); }); @@ -1146,7 +1146,7 @@ describe('API test', () => { url: new URL('https://localhost/graphql'), options: expect.objectContaining({ headers: expect.objectContaining({ 'X-Api-Key': 'FAKE-KEY' }), - signingServiceInfo: null, + signingServiceInfo: undefined, withCredentials: true, }), }); diff --git a/packages/api-graphql/__tests__/fixtures/modeled/schema.ts b/packages/api-graphql/__tests__/fixtures/modeled/schema.ts index a46d9f16330..abdd7fffedd 100644 --- a/packages/api-graphql/__tests__/fixtures/modeled/schema.ts +++ b/packages/api-graphql/__tests__/fixtures/modeled/schema.ts @@ -1,4 +1,4 @@ -import { type ClientSchema, a } from '@aws-amplify/amplify-api-next-alpha'; +import { type ClientSchema, a } from '@aws-amplify/data-schema'; const schema = a.schema({ Todo: a diff --git a/packages/api-graphql/__tests__/resolveOwnerFields.test.ts b/packages/api-graphql/__tests__/resolveOwnerFields.test.ts index 4af1e10b3f8..5feb87f37a5 100644 --- a/packages/api-graphql/__tests__/resolveOwnerFields.test.ts +++ b/packages/api-graphql/__tests__/resolveOwnerFields.test.ts @@ -1,3 +1,7 @@ +import { + ModelIntrospectionSchema, + SchemaModel, +} from '@aws-amplify/core/dist/esm/singleton/API/types'; import { resolveOwnerFields } from '../src/utils/resolveOwnerFields'; import configFixture from './fixtures/modeled/amplifyconfiguration'; @@ -14,7 +18,10 @@ describe('owner field resolution', () => { for (const [modelName, expected] of Object.entries(expectedResolutions)) { it(`identifes ${JSON.stringify(expected)} for ${modelName}`, () => { - const model = configFixture.modelIntrospection.models[modelName]; + const modelIntroSchema = + configFixture.modelIntrospection as ModelIntrospectionSchema; + const model: SchemaModel = modelIntroSchema.models[modelName]; + const resolvedField = resolveOwnerFields(model); expect(resolvedField).toEqual(expected); }); diff --git a/packages/api-graphql/__tests__/server/generateClient.test.ts b/packages/api-graphql/__tests__/server/generateClient.test.ts index 9f1a6369c16..830be38037b 100644 --- a/packages/api-graphql/__tests__/server/generateClient.test.ts +++ b/packages/api-graphql/__tests__/server/generateClient.test.ts @@ -47,7 +47,7 @@ function mockApiResponse(value: any) { describe('server generateClient', () => { describe('with cookies', () => { test('subscriptions are disabled', () => { - const getAmplify = async fn => await fn(Amplify); + const getAmplify = async (fn: any) => await fn(Amplify); const client = generateClient>({ amplify: getAmplify, @@ -79,7 +79,7 @@ describe('server generateClient', () => { }, }); - const getAmplify = async fn => await fn(Amplify); + const getAmplify = async (fn: any) => await fn(Amplify); const client = generateClient>({ amplify: getAmplify, @@ -154,7 +154,7 @@ describe('server generateClient', () => { }, }); - const getAmplify = async fn => await fn(Amplify); + const getAmplify = async (fn: any) => await fn(Amplify); const client = generateClient>({ amplify: getAmplify, @@ -220,7 +220,7 @@ describe('server generateClient', () => { }, }); - const getAmplify = async fn => await fn(Amplify); + const getAmplify = async (fn: any) => await fn(Amplify); const client = generateClient>({ amplify: getAmplify, diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json index 7bdbcdb45a1..76a7d5f236c 100644 --- a/packages/api-graphql/package.json +++ b/packages/api-graphql/package.json @@ -67,9 +67,10 @@ }, "homepage": "https://aws-amplify.github.io/", "devDependencies": { - "typescript": "5.1.6", + "typescript": "5.0.2", "@rollup/plugin-typescript": "11.1.5", - "rollup": "3.29.4" + "rollup": "3.29.4", + "@aws-amplify/data-schema": "^0.11.0" }, "files": [ "dist/cjs", @@ -98,16 +99,11 @@ "jest": { "globals": { "ts-jest": { - "diagnostics": { - "pathRegex": "(/__tests__/.*|\\.(test|spec))\\.(tsx?|jsx?)$" - }, + "diagnostics": false, "tsConfig": { "lib": [ - "es5", - "es2015", "dom", - "esnext.asynciterable", - "es2017.object" + "es2020" ], "allowJs": true, "noEmitOnError": false, diff --git a/packages/api-graphql/src/GraphQLAPI.ts b/packages/api-graphql/src/GraphQLAPI.ts index 858e95e6785..ab558dc8e20 100644 --- a/packages/api-graphql/src/GraphQLAPI.ts +++ b/packages/api-graphql/src/GraphQLAPI.ts @@ -7,7 +7,7 @@ import { InternalGraphQLAPIClass } from './internals/InternalGraphQLAPI'; import { Observable } from 'rxjs'; export const graphqlOperation = ( - query, + query: any, variables = {}, authToken?: string ) => ({ @@ -61,4 +61,4 @@ export class GraphQLAPIClass extends InternalGraphQLAPIClass { } } -export const GraphQLAPI = new GraphQLAPIClass(null); +export const GraphQLAPI = new GraphQLAPIClass(); diff --git a/packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts b/packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts index 739a021a24a..6cf0ded500d 100644 --- a/packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts +++ b/packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts @@ -2,7 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 import { Observable, SubscriptionLike } from 'rxjs'; import { GraphQLError } from 'graphql'; -import { Hub, fetchAuthSession, ConsoleLogger } from '@aws-amplify/core'; +import { + Hub, + fetchAuthSession, + ConsoleLogger, + HubPayload, +} from '@aws-amplify/core'; import { signRequest } from '@aws-amplify/core/internals/aws-client-utils'; import { base64Encoder, @@ -48,7 +53,7 @@ import { const logger = new ConsoleLogger('AWSAppSyncRealTimeProvider'); -const dispatchApiEvent = payload => { +const dispatchApiEvent = (payload: HubPayload) => { Hub.dispatch('api', payload, 'PubSub', AMPLIFY_SYMBOL); }; @@ -111,7 +116,7 @@ export class AWSAppSyncRealTimeProvider { private keepAliveAlertTimeoutId?: ReturnType; private subscriptionObserverMap: Map = new Map(); private promiseArray: Array<{ res: Function; rej: Function }> = []; - private connectionState: ConnectionState; + private connectionState: ConnectionState | undefined; private readonly connectionStateMonitor = new ConnectionStateMonitor(); private readonly reconnectionMonitor = new ReconnectionMonitor(); private connectionStateMonitorSubscription: SubscriptionLike; @@ -362,7 +367,7 @@ export class AWSAppSyncRealTimeProvider { region, additionalHeaders, }); - } catch (err) { + } catch (err: any) { this._logStartSubscriptionError(subscriptionId, observer, err); return; } diff --git a/packages/api-graphql/src/internals/APIClient.ts b/packages/api-graphql/src/internals/APIClient.ts index 74d4d460d89..83c80159c8a 100644 --- a/packages/api-graphql/src/internals/APIClient.ts +++ b/packages/api-graphql/src/internals/APIClient.ts @@ -1,12 +1,27 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { resolveOwnerFields } from '../utils/resolveOwnerFields'; -import { GraphQLAuthMode } from '@aws-amplify/core/internals/utils'; -import { V6Client, __authMode, __authToken } from '../types'; +import { + GraphQLAuthMode, + ModelIntrospectionSchema, + ModelFieldType, + SchemaModel, + SchemaModels, + AssociationHasOne, + AssociationBelongsTo, +} from '@aws-amplify/core/internals/utils'; +import { + AuthModeParams, + ClientWithModels, + ListArgs, + QueryArgs, + V6Client, + V6ClientSSRRequest, + __authMode, + __authToken, +} from '../types'; import { AmplifyServer } from '@aws-amplify/core/internals/adapter-core'; -type ListArgs = { selectionSet?: string[]; filter?: {} }; - type LazyLoadOptions = { authMode?: GraphQLAuthMode; authToken?: string | undefined; @@ -48,10 +63,10 @@ export const flattenItems = (obj: Record): Record => { // TODO: this should accept single result to support CRUD methods; create helper for array/list export function initializeModel( - client: any, + client: ClientWithModels, modelName: string, result: any[], - modelIntrospection: any, + modelIntrospection: ModelIntrospectionSchema, authMode: GraphQLAuthMode | undefined, authToken: string | undefined, context = false @@ -64,12 +79,14 @@ export function initializeModel( .map(([fieldName]) => fieldName); return result.map(record => { - const initializedRelationalFields = {}; + const initializedRelationalFields: Record = {}; - for (const field of modelFields) { - const relatedModelName = introModelFields[field].type.model; + for (const fieldName of modelFields) { + const modelField = introModelFields[fieldName]; + const modelFieldType = modelField?.type as ModelFieldType; - const relatedModel = modelIntrospection.models[relatedModelName]; + const relatedModelName = modelFieldType.model; + const relatedModel = modelIntrospection.models[relatedModelName!]; const relatedModelPKFieldName = relatedModel.primaryKeyInfo.primaryKeyFieldName; @@ -77,18 +94,26 @@ export function initializeModel( const relatedModelSKFieldNames = relatedModel.primaryKeyInfo.sortKeyFieldNames; - const relationType = introModelFields[field].association.connectionType; - const connectionFields = - introModelFields[field].association.associatedWith; + const relationType = modelField.association?.connectionType; - const targetNames = - introModelFields[field].association?.targetNames || []; + let connectionFields: string[] = []; + if ( + modelField.association && + 'associatedWith' in modelField.association + ) { + connectionFields = modelField.association.associatedWith; + } + + let targetNames: string[] = []; + if (modelField.association && 'targetNames' in modelField.association) { + targetNames = modelField.association.targetNames; + } switch (relationType) { case connectionType.HAS_ONE: case connectionType.BELONGS_TO: const sortKeyValues = relatedModelSKFieldNames.reduce( - (acc, curVal) => { + (acc: Record, curVal) => { if (record[curVal]) { return (acc[curVal] = record[curVal]); } @@ -97,12 +122,14 @@ export function initializeModel( ); if (context) { - initializedRelationalFields[field] = ( + initializedRelationalFields[fieldName] = ( contextSpec: AmplifyServer.ContextSpec, options?: LazyLoadOptions ) => { if (record[targetNames[0]]) { - return client.models[relatedModelName].get( + return ( + client as V6ClientSSRRequest> + ).models[relatedModelName].get( contextSpec, { [relatedModelPKFieldName]: record[targetNames[0]], @@ -117,11 +144,13 @@ export function initializeModel( return undefined; }; } else { - initializedRelationalFields[field] = ( + initializedRelationalFields[fieldName] = ( options?: LazyLoadOptions ) => { if (record[targetNames[0]]) { - return client.models[relatedModelName].get( + return (client as V6Client>).models[ + relatedModelName + ].get( { [relatedModelPKFieldName]: record[targetNames[0]], ...sortKeyValues, @@ -142,25 +171,37 @@ export function initializeModel( const parentSK = introModel.primaryKeyInfo.sortKeyFieldNames; // M:N check - TODO: refactor - if (relatedModel.fields[connectionFields[0]]?.type.model) { - const relatedTargetNames = - relatedModel.fields[connectionFields[0]].association.targetNames; + const relatedModelField = relatedModel.fields[connectionFields[0]]; + const relatedModelFieldType = + relatedModelField.type as ModelFieldType; + if (relatedModelFieldType.model) { + let relatedTargetNames: string[] = []; + if ( + relatedModelField.association && + 'targetNames' in relatedModelField.association + ) { + relatedTargetNames = relatedModelField.association?.targetNames; + } - const hasManyFilter = relatedTargetNames.map((field, idx) => { - if (idx === 0) { - return { [field]: { eq: record[parentPk] } }; - } + const hasManyFilter: Record = relatedTargetNames.map( + (field, idx) => { + if (idx === 0) { + return { [field]: { eq: record[parentPk] } }; + } - return { [field]: { eq: record[parentSK[idx - 1]] } }; - }); + return { [field]: { eq: record[parentSK[idx - 1]] } }; + } + ); if (context) { - initializedRelationalFields[field] = ( + initializedRelationalFields[fieldName] = ( contextSpec: AmplifyServer.ContextSpec, options?: LazyLoadOptions ) => { if (record[parentPk]) { - return client.models[relatedModelName].list(contextSpec, { + return ( + client as V6ClientSSRRequest> + ).models[relatedModelName].list(contextSpec, { filter: { and: hasManyFilter }, limit: options?.limit, nextToken: options?.nextToken, @@ -171,11 +212,13 @@ export function initializeModel( return []; }; } else { - initializedRelationalFields[field] = ( + initializedRelationalFields[fieldName] = ( options?: LazyLoadOptions ) => { if (record[parentPk]) { - return client.models[relatedModelName].list({ + return (client as V6Client>).models[ + relatedModelName + ].list({ filter: { and: hasManyFilter }, limit: options?.limit, nextToken: options?.nextToken, @@ -190,21 +233,25 @@ export function initializeModel( break; } - const hasManyFilter = connectionFields.map((field, idx) => { - if (idx === 0) { - return { [field]: { eq: record[parentPk] } }; - } + const hasManyFilter: Record = connectionFields.map( + (field, idx) => { + if (idx === 0) { + return { [field]: { eq: record[parentPk] } }; + } - return { [field]: { eq: record[parentSK[idx - 1]] } }; - }); + return { [field]: { eq: record[parentSK[idx - 1]] } }; + } + ); if (context) { - initializedRelationalFields[field] = ( + initializedRelationalFields[fieldName] = ( contextSpec: AmplifyServer.ContextSpec, options?: LazyLoadOptions ) => { if (record[parentPk]) { - return client.models[relatedModelName].list(contextSpec, { + return ( + client as V6ClientSSRRequest> + ).models[relatedModelName].list(contextSpec, { filter: { and: hasManyFilter }, limit: options?.limit, nextToken: options?.nextToken, @@ -215,11 +262,13 @@ export function initializeModel( return []; }; } else { - initializedRelationalFields[field] = ( + initializedRelationalFields[fieldName] = ( options?: LazyLoadOptions ) => { if (record[parentPk]) { - return client.models[relatedModelName].list({ + return (client as V6Client>).models[ + relatedModelName + ].list({ filter: { and: hasManyFilter }, limit: options?.limit, nextToken: options?.nextToken, @@ -260,7 +309,7 @@ type OperationPrefix = const graphQLDocumentsCache = new Map>(); const SELECTION_SET_WILDCARD = '*'; -function defaultSelectionSetForModel(modelDefinition: any): string[] { +function defaultSelectionSetForModel(modelDefinition: SchemaModel): string[] { // fields that are explicitly part of the graphql schema; not // inferred from owner auth rules. const { fields } = modelDefinition; @@ -280,7 +329,7 @@ const FIELD_IR = ''; /** * Generates nested Custom Selection Set IR from path * - * @param modelIntrospection + * @param modelDefinitions * @param modelName * @param selectionSet - array of object paths * @example @@ -297,31 +346,32 @@ const FIELD_IR = ''; * ``` */ export function customSelectionSetToIR( - modelIntrospection: any, + modelDefinitions: SchemaModels, modelName: string, selectionSet: string[] ): Record { - const modelDefinition = modelIntrospection[modelName]; + const modelDefinition = modelDefinitions[modelName]; const { fields: modelFields } = modelDefinition; - return selectionSet.reduce((resultObj, path) => { + return selectionSet.reduce((resultObj: Record, path) => { const [fieldName, nested, ...rest] = path.split('.'); if (nested) { - const relatedModel = modelFields[fieldName]?.type?.model; + const fieldType = modelFields[fieldName]?.type as ModelFieldType; + const relatedModel = fieldType.model; if (!relatedModel) { // TODO: may need to change this to support custom types throw Error(`${fieldName} is not a model field`); } - const relatedModelDefinition = modelIntrospection[relatedModel]; + const relatedModelDefinition = modelDefinitions[relatedModel]; const selectionSet = nested === SELECTION_SET_WILDCARD ? defaultSelectionSetIR(relatedModelDefinition) : // if we have a path like 'field.anotherField' recursively build up selection set IR - customSelectionSetToIR(modelIntrospection, relatedModel, [ + customSelectionSetToIR(modelDefinitions, relatedModel, [ [nested, ...rest].join('.'), ]); @@ -353,15 +403,18 @@ export function customSelectionSetToIR( }, {}); } -const defaultSelectionSetIR = relatedModelDefinition => { +const defaultSelectionSetIR = (relatedModelDefinition: SchemaModel) => { const defaultSelectionSet = defaultSelectionSetForModel( relatedModelDefinition ); - const reduced = defaultSelectionSet.reduce((acc, curVal) => { - acc[curVal] = FIELD_IR; - return acc; - }, {}); + const reduced = defaultSelectionSet.reduce( + (acc: Record, curVal) => { + acc[curVal] = FIELD_IR; + return acc; + }, + {} + ); return reduced; }; @@ -410,18 +463,18 @@ export function selectionSetIRToString( } export function generateSelectionSet( - modelIntrospection: any, + modelDefinitions: SchemaModels, modelName: string, selectionSet?: string[] ) { - const modelDefinition = modelIntrospection[modelName]; + const modelDefinition = modelDefinitions[modelName]; if (!selectionSet) { return defaultSelectionSetForModel(modelDefinition).join(' '); } const selSetIr = customSelectionSetToIR( - modelIntrospection, + modelDefinitions, modelName, selectionSet ); @@ -431,12 +484,12 @@ export function generateSelectionSet( } export function generateGraphQLDocument( - modelIntrospection: any, + modelDefinitions: SchemaModels, modelName: string, modelOperation: ModelOperation, listArgs?: ListArgs ): string { - const modelDefinition = modelIntrospection[modelName]; + const modelDefinition = modelDefinitions[modelName]; const { name, @@ -469,7 +522,7 @@ export function generateGraphQLDocument( let graphQLArguments: Record | undefined; const selectionSetFields = generateSelectionSet( - modelIntrospection, + modelDefinitions, modelName, selectionSet ); @@ -490,7 +543,7 @@ export function generateGraphQLDocument( graphQLArguments ?? (graphQLArguments = isCustomPrimaryKey ? [primaryKeyFieldName, ...sortKeyFieldNames].reduce( - (acc, fieldName) => { + (acc: Record, fieldName) => { acc[fieldName] = fields[fieldName].type; return acc; @@ -548,10 +601,10 @@ export function generateGraphQLDocument( } export function buildGraphQLVariables( - modelDefinition: any, + modelDefinition: SchemaModel, operation: ModelOperation, - arg: any, - modelIntrospection + arg: QueryArgs | undefined, + modelIntrospection: ModelIntrospectionSchema ): object { const { fields, @@ -568,36 +621,42 @@ export function buildGraphQLVariables( switch (operation) { case 'CREATE': variables = { - input: normalizeMutationInput(arg, modelDefinition, modelIntrospection), + input: arg + ? normalizeMutationInput(arg, modelDefinition, modelIntrospection) + : {}, }; break; case 'UPDATE': // readonly fields are not updated variables = { - input: Object.fromEntries( - Object.entries( - normalizeMutationInput(arg, modelDefinition, modelIntrospection) - ).filter(([fieldName]) => { - const { isReadOnly } = fields[fieldName]; - - return !isReadOnly; - }) - ), + input: arg + ? Object.fromEntries( + Object.entries( + normalizeMutationInput(arg, modelDefinition, modelIntrospection) + ).filter(([fieldName]) => { + const { isReadOnly } = fields[fieldName]; + + return !isReadOnly; + }) + ) + : {}, }; break; case 'READ': case 'DELETE': // only identifiers are sent - variables = isCustomPrimaryKey - ? [primaryKeyFieldName, ...sortKeyFieldNames].reduce( - (acc, fieldName) => { - acc[fieldName] = arg[fieldName]; + if (arg) { + variables = isCustomPrimaryKey + ? [primaryKeyFieldName, ...sortKeyFieldNames].reduce( + (acc: Record, fieldName) => { + acc[fieldName] = arg[fieldName]; - return acc; - }, - {} - ) - : { [primaryKeyFieldName]: arg[primaryKeyFieldName] }; + return acc; + }, + {} + ) + : { [primaryKeyFieldName]: arg[primaryKeyFieldName] }; + } if (operation === 'DELETE') { variables = { input: variables }; @@ -649,34 +708,36 @@ export function buildGraphQLVariables( * */ export function normalizeMutationInput( - mutationInput: any, - model: any, - modelDefinition: any -): Record { + mutationInput: QueryArgs, + model: SchemaModel, + modelIntrospection: ModelIntrospectionSchema +): QueryArgs { const { fields } = model; - const normalized = {}; + const normalized: Record = {}; Object.entries(mutationInput).forEach(([inputFieldName, inputValue]) => { - const relatedModelName: string | undefined = - fields[inputFieldName]?.type?.model; + const fieldType = fields[inputFieldName]?.type as ModelFieldType; + const relatedModelName = fieldType?.model; if (relatedModelName) { const association = fields[inputFieldName]?.association; - const relatedModelDef = modelDefinition.models[relatedModelName]; + const relatedModelDef = modelIntrospection.models[relatedModelName]; const relatedModelPkInfo = relatedModelDef.primaryKeyInfo; - if (association.connectionType === connectionType.HAS_ONE) { - association.targetNames.forEach((targetName, idx) => { - const associatedFieldName = association.associatedWith[idx]; + if (association?.connectionType === connectionType.HAS_ONE) { + const associationHasOne = association as AssociationHasOne; + associationHasOne.targetNames.forEach((targetName, idx) => { + const associatedFieldName = associationHasOne.associatedWith[idx]; normalized[targetName] = (inputValue as Record)[ associatedFieldName ]; }); } - if (association.connectionType === connectionType.BELONGS_TO) { - association.targetNames.forEach((targetName, idx) => { + if (association?.connectionType === connectionType.BELONGS_TO) { + const associationBelongsTo = association as AssociationBelongsTo; + associationBelongsTo.targetNames.forEach((targetName, idx) => { if (idx === 0) { const associatedFieldName = relatedModelPkInfo.primaryKeyFieldName; normalized[targetName] = (inputValue as Record)[ @@ -699,11 +760,6 @@ export function normalizeMutationInput( return normalized; } -type AuthModeParams = { - authMode?: GraphQLAuthMode; - authToken?: string; -}; - /** * Produces a parameter object that can contains auth mode/token overrides * only if present in either `options` (first) or configured on the `client` @@ -714,7 +770,7 @@ type AuthModeParams = { * @returns */ export function authModeParams( - client: V6Client, + client: ClientWithModels, options: AuthModeParams = {} ): AuthModeParams { return { diff --git a/packages/api-graphql/src/internals/InternalGraphQLAPI.ts b/packages/api-graphql/src/internals/InternalGraphQLAPI.ts index 2d2ed4d7754..7c46a449532 100644 --- a/packages/api-graphql/src/internals/InternalGraphQLAPI.ts +++ b/packages/api-graphql/src/internals/InternalGraphQLAPI.ts @@ -35,17 +35,11 @@ const USER_AGENT_HEADER = 'x-amz-user-agent'; const logger = new ConsoleLogger('GraphQLAPI'); -export const graphqlOperation = ( - query, - variables = {}, - authToken?: string -) => ({ - query, - variables, - authToken, -}); - -const isAmplifyInstance = (amplify): amplify is AmplifyClassV6 => { +const isAmplifyInstance = ( + amplify: + | AmplifyClassV6 + | ((fn: (amplify: any) => Promise) => Promise) +): amplify is AmplifyClassV6 => { return typeof amplify !== 'function'; }; @@ -56,8 +50,7 @@ export class InternalGraphQLAPIClass { /** * @private */ - private _options; - private appSyncRealTime: AWSAppSyncRealTimeProvider | null; + private appSyncRealTime = new AWSAppSyncRealTimeProvider(); private _api = { post, @@ -66,15 +59,6 @@ export class InternalGraphQLAPIClass { updateRequestToBeCancellable, }; - /** - * Initialize GraphQL API with AWS configuration - * @param {Object} options - Configuration object for API - */ - constructor(options) { - this._options = options; - logger.debug('API Options', this._options); - } - public getModuleName() { return 'InternalGraphQLAPI'; } @@ -208,7 +192,7 @@ export class InternalGraphQLAPIClass { customUserAgentDetails ); } else { - const wrapper = amplifyInstance => + const wrapper = (amplifyInstance: AmplifyClassV6) => this._graphql( amplifyInstance, { query, variables, authMode }, @@ -307,7 +291,7 @@ export class InternalGraphQLAPIClass { authMode !== 'iam' && authMode !== 'lambda') ) { - signingServiceInfo = null; + signingServiceInfo = undefined; } else { signingServiceInfo = { service: !customEndpointRegion ? 'appsync' : 'execute-api', @@ -326,7 +310,7 @@ export class InternalGraphQLAPIClass { }; } - let response; + let response: any; try { const { body: responseBody } = await this._api.post({ url: new AmplifyUrl(endpoint), @@ -352,7 +336,16 @@ export class InternalGraphQLAPIClass { response = { data: {}, - errors: [new GraphQLError(err.message, null, null, null, null, err)], + errors: [ + new GraphQLError( + (err as any).message, + null, + null, + null, + null, + err as any + ), + ], }; } @@ -391,9 +384,6 @@ export class InternalGraphQLAPIClass { ): Observable { const config = resolveConfig(amplify); - if (!this.appSyncRealTime) { - this.appSyncRealTime = new AWSAppSyncRealTimeProvider(); - } return this.appSyncRealTime.subscribe( { query: print(query as DocumentNode), @@ -409,4 +399,4 @@ export class InternalGraphQLAPIClass { } } -export const InternalGraphQLAPI = new InternalGraphQLAPIClass(null); +export const InternalGraphQLAPI = new InternalGraphQLAPIClass(); diff --git a/packages/api-graphql/src/internals/generateModelsProperty.ts b/packages/api-graphql/src/internals/generateModelsProperty.ts index 50c0997439d..966c293da04 100644 --- a/packages/api-graphql/src/internals/generateModelsProperty.ts +++ b/packages/api-graphql/src/internals/generateModelsProperty.ts @@ -9,9 +9,10 @@ import { listFactory } from './operations/list'; import { getFactory } from './operations/get'; import { subscriptionFactory } from './operations/subscription'; import { observeQueryFactory } from './operations/observeQuery'; +import { ModelIntrospectionSchema } from '@aws-amplify/core/internals/utils'; export function generateModelsProperty = never>( - client: V6Client, + client: V6Client>, params: ClientGenerationParams ): ModelTypes { const models = {} as any; @@ -23,17 +24,19 @@ export function generateModelsProperty = never>( ); } - const modelIntrospection = config.API?.GraphQL?.modelIntrospection; + const modelIntrospection: ModelIntrospectionSchema | undefined = + config.API.GraphQL.modelIntrospection; + if (!modelIntrospection) { - return {} as any; + return {} as ModelTypes; } const SUBSCRIPTION_OPS = ['ONCREATE', 'ONUPDATE', 'ONDELETE']; for (const model of Object.values(modelIntrospection.models)) { - const { name } = model as any; + const { name } = model; - models[name] = {} as any; + models[name] = {} as Record; Object.entries(graphQLOperationsInfo).forEach( ([key, { operationPrefix }]) => { diff --git a/packages/api-graphql/src/internals/operations/get.ts b/packages/api-graphql/src/internals/operations/get.ts index 6c6e4271b60..e5301b6180d 100644 --- a/packages/api-graphql/src/internals/operations/get.ts +++ b/packages/api-graphql/src/internals/operations/get.ts @@ -7,17 +7,32 @@ import { buildGraphQLVariables, flattenItems, authModeParams, + ModelOperation, } from '../APIClient'; +import { + AuthModeParams, + ClientWithModels, + GraphQLOptionsV6, + GraphQLResult, + ListArgs, + QueryArgs, + V6Client, + V6ClientSSRRequest, +} from '../../types'; +import { + ModelIntrospectionSchema, + SchemaModel, +} from '@aws-amplify/core/internals/utils'; export function getFactory( - client, - modelIntrospection, - model, - operation, - context = false + client: ClientWithModels, + modelIntrospection: ModelIntrospectionSchema, + model: SchemaModel, + operation: ModelOperation, + useContext = false ) { const getWithContext = async ( - contextSpec: AmplifyServer.ContextSpec, + contextSpec: AmplifyServer.ContextSpec & GraphQLOptionsV6, arg?: any, options?: any ) => { @@ -33,30 +48,22 @@ export function getFactory( }; const get = async (arg?: any, options?: any) => { - return _get( - client, - modelIntrospection, - model, - arg, - options, - operation, - context - ); + return _get(client, modelIntrospection, model, arg, options, operation); }; - return context ? getWithContext : get; + return useContext ? getWithContext : get; } async function _get( - client, - modelIntrospection, - model, - arg, - options, - operation, - context + client: ClientWithModels, + modelIntrospection: ModelIntrospectionSchema, + model: SchemaModel, + arg: QueryArgs, + options: AuthModeParams & ListArgs, + operation: ModelOperation, + context?: AmplifyServer.ContextSpec ) { - const { name } = model as any; + const { name } = model; const query = generateGraphQLDocument( modelIntrospection.models, @@ -74,19 +81,22 @@ async function _get( try { const auth = authModeParams(client, options); const { data, extensions } = context - ? ((await client.graphql(context, { - ...auth, - query, - variables, - })) as any) - : ((await client.graphql({ + ? ((await (client as V6ClientSSRRequest>).graphql( + context, + { + ...auth, + query, + variables, + } + )) as GraphQLResult) + : ((await (client as V6Client>).graphql({ ...auth, query, variables, - })) as any); + })) as GraphQLResult); // flatten response - if (data !== undefined) { + if (data) { const [key] = Object.keys(data); const flattenedResult = flattenItems(data)[key]; @@ -101,7 +111,7 @@ async function _get( modelIntrospection, auth.authMode, auth.authToken, - context + !!context ); return { data: initialized, extensions }; @@ -109,7 +119,7 @@ async function _get( } else { return { data: null, extensions }; } - } catch (error) { + } catch (error: any) { if (error.errors) { // graphql errors pass through return error as any; diff --git a/packages/api-graphql/src/internals/operations/list.ts b/packages/api-graphql/src/internals/operations/list.ts index 022074d5f79..f0fdcd898c8 100644 --- a/packages/api-graphql/src/internals/operations/list.ts +++ b/packages/api-graphql/src/internals/operations/list.ts @@ -8,29 +8,47 @@ import { flattenItems, authModeParams, } from '../APIClient'; +import { + AuthModeParams, + ClientWithModels, + ListArgs, + V6Client, + V6ClientSSRRequest, + GraphQLResult, +} from '../../types'; +import { + ModelIntrospectionSchema, + SchemaModel, +} from '@aws-amplify/core/internals/utils'; export function listFactory( - client, - modelIntrospection, - model, + client: ClientWithModels, + modelIntrospection: ModelIntrospectionSchema, + model: SchemaModel, context = false ) { const listWithContext = async ( contextSpec: AmplifyServer.ContextSpec, - args?: any + args?: ListArgs ) => { return _list(client, modelIntrospection, model, args, contextSpec); }; const list = async (args?: any) => { - return _list(client, modelIntrospection, model, args, context); + return _list(client, modelIntrospection, model, args); }; return context ? listWithContext : list; } -async function _list(client, modelIntrospection, model, args, context) { - const { name } = model as any; +async function _list( + client: ClientWithModels, + modelIntrospection: ModelIntrospectionSchema, + model: SchemaModel, + args?: ListArgs & AuthModeParams, + contextSpec?: AmplifyServer.ContextSpec +) { + const { name } = model; const query = generateGraphQLDocument( modelIntrospection.models, @@ -48,17 +66,20 @@ async function _list(client, modelIntrospection, model, args, context) { try { const auth = authModeParams(client, args); - const { data, extensions } = context - ? ((await client.graphql(context, { - ...auth, - query, - variables, - })) as any) - : ((await client.graphql({ + const { data, extensions } = !!contextSpec + ? ((await (client as V6ClientSSRRequest>).graphql( + contextSpec, + { + ...auth, + query, + variables, + } + )) as GraphQLResult) + : ((await (client as V6Client>).graphql({ ...auth, query, variables, - })) as any); + })) as GraphQLResult); // flatten response if (data !== undefined) { @@ -82,7 +103,7 @@ async function _list(client, modelIntrospection, model, args, context) { modelIntrospection, auth.authMode, auth.authToken, - context + !!contextSpec ); return { @@ -99,7 +120,7 @@ async function _list(client, modelIntrospection, model, args, context) { extensions, }; } - } catch (error) { + } catch (error: any) { if (error.errors) { // graphql errors pass through return error as any; diff --git a/packages/api-graphql/src/internals/operations/observeQuery.ts b/packages/api-graphql/src/internals/operations/observeQuery.ts index efe4095a8af..cf45c30bf06 100644 --- a/packages/api-graphql/src/internals/operations/observeQuery.ts +++ b/packages/api-graphql/src/internals/operations/observeQuery.ts @@ -1,11 +1,11 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 - -import { Observable, map } from 'rxjs'; +import { Observable } from 'rxjs'; import { findIndexByFields, resolvePKFields } from '../../utils'; +import { SchemaModel } from '@aws-amplify/core/internals/utils'; -export function observeQueryFactory(models, model) { - const { name } = model as any; +export function observeQueryFactory(models: any, model: SchemaModel) { + const { name } = model; const observeQuery = (arg?: any) => new Observable(subscriber => { @@ -28,26 +28,26 @@ export function observeQueryFactory(models, model) { // start subscriptions const onCreateSub = models[name].onCreate(arg).subscribe({ - next(item) { + next(item: object) { receiveMessages({ item, type: 'create' }); }, - error(error) { + error(error: any) { subscriber.error({ type: 'onCreate', error }); }, }); const onUpdateSub = models[name].onUpdate(arg).subscribe({ - next(item) { + next(item: object) { receiveMessages({ item, type: 'update' }); }, - error(error) { + error(error: any) { subscriber.error({ type: 'onUpdate', error }); }, }); const onDeleteSub = models[name].onDelete(arg).subscribe({ - next(item) { + next(item: object) { receiveMessages({ item, type: 'delete' }); }, - error(error) { + error(error: any) { subscriber.error({ type: 'onDelete', error }); }, }); @@ -89,6 +89,10 @@ export function observeQueryFactory(models, model) { data: page, errors, nextToken: _nextToken, + }: { + data: any; + errors: any; + nextToken: string | null; } = await models[name].list({ ...arg, nextToken }); nextToken = _nextToken; diff --git a/packages/api-graphql/src/internals/operations/subscription.ts b/packages/api-graphql/src/internals/operations/subscription.ts index 46dedc06261..ce7ff8dc860 100644 --- a/packages/api-graphql/src/internals/operations/subscription.ts +++ b/packages/api-graphql/src/internals/operations/subscription.ts @@ -2,19 +2,24 @@ // SPDX-License-Identifier: Apache-2.0 import { map } from 'rxjs'; -import { GraphqlSubscriptionResult } from '../../types'; +import { V6Client, GraphqlSubscriptionResult } from '../../types'; import { initializeModel, generateGraphQLDocument, buildGraphQLVariables, authModeParams, + ModelOperation, } from '../APIClient'; +import { + ModelIntrospectionSchema, + SchemaModel, +} from '@aws-amplify/core/internals/utils'; export function subscriptionFactory( - client, - modelIntrospection, - model, - operation + client: any, + modelIntrospection: ModelIntrospectionSchema, + model: SchemaModel, + operation: ModelOperation ) { const { name } = model as any; @@ -42,10 +47,11 @@ export function subscriptionFactory( return observable.pipe( map(value => { const [key] = Object.keys(value.data); + const data = (value.data as any)[key]; const [initialized] = initializeModel( - client, + client as V6Client>, name, - [value.data[key]], + [data], modelIntrospection, auth.authMode, auth.authToken diff --git a/packages/api-graphql/src/internals/server/generateClient.ts b/packages/api-graphql/src/internals/server/generateClient.ts index c55c9cf906e..c1860c703be 100644 --- a/packages/api-graphql/src/internals/server/generateClient.ts +++ b/packages/api-graphql/src/internals/server/generateClient.ts @@ -39,7 +39,7 @@ export function generateClient< isCancelError, } as any; - client.models = generateModelsProperty(client, params); + client.models = generateModelsProperty(client, params); return client as ClientType; } diff --git a/packages/api-graphql/src/internals/server/generateModelsProperty.ts b/packages/api-graphql/src/internals/server/generateModelsProperty.ts index 58f820366ad..211f8d29640 100644 --- a/packages/api-graphql/src/internals/server/generateModelsProperty.ts +++ b/packages/api-graphql/src/internals/server/generateModelsProperty.ts @@ -1,9 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 - +import { ModelTypes } from '@aws-amplify/data-schema-types'; import { graphQLOperationsInfo, ModelOperation } from '../APIClient'; import { ServerClientGenerationParams } from '../../types/'; import { V6ClientSSRRequest, V6ClientSSRCookies } from '../../types'; +import { ModelIntrospectionSchema } from '@aws-amplify/core/internals/utils'; import { listFactory } from '../operations/list'; import { getFactory } from '../operations/get'; @@ -11,21 +12,30 @@ import { getFactory } from '../operations/get'; export function generateModelsProperty< T extends Record = never, ClientType extends - | V6ClientSSRRequest - | V6ClientSSRCookies = V6ClientSSRCookies + | V6ClientSSRRequest> + | V6ClientSSRCookies> = V6ClientSSRCookies< + Record + > >(client: ClientType, params: ServerClientGenerationParams): ClientType { const models = {} as any; const config = params.config; const useContext = client === null; if (!config) { - // TODO: improve throw new Error('generateModelsProperty cannot retrieve Amplify config'); } - const modelIntrospection = config.API?.GraphQL?.modelIntrospection; + if (!config.API?.GraphQL) { + throw new Error( + 'The API configuration is missing. This is likely due to Amplify.configure() not being called prior to generateClient().' + ); + } + + const modelIntrospection: ModelIntrospectionSchema | undefined = + config.API.GraphQL.modelIntrospection; + if (!modelIntrospection) { - return {} as any; + return {} as ModelTypes; } const SSR_UNSUPORTED_OPS = [ @@ -36,8 +46,8 @@ export function generateModelsProperty< ]; for (const model of Object.values(modelIntrospection.models)) { - const { name } = model as any; - models[name] = {} as any; + const { name } = model; + models[name] = {} as Record; Object.entries(graphQLOperationsInfo).forEach( ([key, { operationPrefix }]) => { diff --git a/packages/api-graphql/src/types/index.ts b/packages/api-graphql/src/types/index.ts index c073a2b60ce..9ff85b9cbb8 100644 --- a/packages/api-graphql/src/types/index.ts +++ b/packages/api-graphql/src/types/index.ts @@ -364,6 +364,11 @@ export const __amplify = Symbol('amplify'); export const __authMode = Symbol('authMode'); export const __authToken = Symbol('authToken'); +export type ClientWithModels = + | V6Client> + | V6ClientSSRRequest> + | V6ClientSSRCookies>; + export type V6Client = never> = ExcludeNeverFields<{ [__amplify]: AmplifyClassV6; [__authMode]?: GraphQLAuthMode; @@ -434,3 +439,12 @@ export type ServerClientGenerationParams = { // global env-sourced config use for retrieving modelIntro config: ResourcesConfig; }; + +export type QueryArgs = Record; + +export type ListArgs = { selectionSet?: string[]; filter?: {} }; + +export type AuthModeParams = { + authMode?: GraphQLAuthMode; + authToken?: string; +}; diff --git a/packages/api-graphql/src/utils/ConnectionStateMonitor.ts b/packages/api-graphql/src/utils/ConnectionStateMonitor.ts index 03fb68a3744..fc26700978f 100644 --- a/packages/api-graphql/src/utils/ConnectionStateMonitor.ts +++ b/packages/api-graphql/src/utils/ConnectionStateMonitor.ts @@ -50,7 +50,9 @@ export class ConnectionStateMonitor { */ private _linkedConnectionState: LinkedConnectionStates; private _linkedConnectionStateObservable: Observable; - private _linkedConnectionStateObserver: Observer; + private _linkedConnectionStateObserver: + | Observer + | undefined; private _networkMonitoringSubscription?: SubscriptionLike; private _initialNetworkStateSubscription?: SubscriptionLike; diff --git a/packages/api-graphql/src/utils/findIndexByFields.ts b/packages/api-graphql/src/utils/findIndexByFields.ts index 125b53b2bd9..6d342be7530 100644 --- a/packages/api-graphql/src/utils/findIndexByFields.ts +++ b/packages/api-graphql/src/utils/findIndexByFields.ts @@ -19,7 +19,9 @@ export function findIndexByFields( for (let i = 0; i < haystack.length; i++) { if ( - Object.keys(searchObject).every(k => searchObject[k] === haystack[i][k]) + Object.keys(searchObject).every( + k => searchObject[k] === (haystack[i] as any)[k] + ) ) { return i; } diff --git a/packages/api-graphql/tsconfig.json b/packages/api-graphql/tsconfig.json index 791d50a260c..e477014534b 100644 --- a/packages/api-graphql/tsconfig.json +++ b/packages/api-graphql/tsconfig.json @@ -2,8 +2,8 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "importHelpers": true, - "strict": false, - "noImplicitAny": false, + "strict": true, + "noImplicitAny": true, "strictNullChecks": true, "skipLibCheck": true }, diff --git a/packages/api/package.json b/packages/api/package.json index 7914c7086e1..49259f799ac 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -70,7 +70,7 @@ "devDependencies": { "@rollup/plugin-typescript": "11.1.5", "rollup": "3.29.4", - "typescript": "5.1.6" + "typescript": "5.0.2" }, "files": [ "dist/cjs", diff --git a/packages/api/src/internals/InternalAPI.ts b/packages/api/src/internals/InternalAPI.ts index 644f107e259..f9cd7fa6a4d 100644 --- a/packages/api/src/internals/InternalAPI.ts +++ b/packages/api/src/internals/InternalAPI.ts @@ -34,23 +34,15 @@ const logger = new ConsoleLogger('API'); * Export Cloud Logic APIs */ export class InternalAPIClass { - /** - * Initialize API with AWS configuration - * @param {Object} options - Configuration object for API - */ - private _options; private _graphqlApi: InternalGraphQLAPIClass; Cache = Cache; /** - * Initialize API with AWS configuration - * @param {Object} options - Configuration object for API + * Initialize API */ - constructor(options) { - this._options = options; - this._graphqlApi = new InternalGraphQLAPIClass(options); - logger.debug('API Options', this._options); + constructor() { + this._graphqlApi = new InternalGraphQLAPIClass(); } public getModuleName() { @@ -104,4 +96,4 @@ export class InternalAPIClass { } } -export const InternalAPI = new InternalAPIClass(null); +export const InternalAPI = new InternalAPIClass(); diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 791d50a260c..e477014534b 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -2,8 +2,8 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "importHelpers": true, - "strict": false, - "noImplicitAny": false, + "strict": true, + "noImplicitAny": true, "strictNullChecks": true, "skipLibCheck": true }, diff --git a/packages/core/src/libraryUtils.ts b/packages/core/src/libraryUtils.ts index bbaa20143fe..1654bd7ced0 100644 --- a/packages/core/src/libraryUtils.ts +++ b/packages/core/src/libraryUtils.ts @@ -31,7 +31,17 @@ export { assertOAuthConfig, } from './singleton/Auth/utils'; export { isTokenExpired } from './singleton/Auth'; -export { GraphQLAuthMode, DocumentType } from './singleton/API/types'; +export { + AssociationBelongsTo, + AssociationHasMany, + AssociationHasOne, + DocumentType, + GraphQLAuthMode, + ModelFieldType, + ModelIntrospectionSchema, + SchemaModel, + SchemaModels, +} from './singleton/API/types'; export { Signer } from './Signer'; export { JWT, diff --git a/packages/core/src/singleton/API/types.ts b/packages/core/src/singleton/API/types.ts index b1f9d115c30..0a94301c5db 100644 --- a/packages/core/src/singleton/API/types.ts +++ b/packages/core/src/singleton/API/types.ts @@ -151,6 +151,9 @@ export type Field = { attributes?: FieldAttribute[]; association?: AssociationType; }; + +export type ModelFieldType = { model: string }; + export type FieldType = | 'ID' | 'String' @@ -167,7 +170,7 @@ export type FieldType = | 'AWSJSON' | 'AWSPhone' | { enum: string } - | { model: string } + | ModelFieldType | { nonModel: string }; export type FieldAttribute = ModelAttribute; diff --git a/packages/datastore/__tests__/mutation.test.ts b/packages/datastore/__tests__/mutation.test.ts index 4ba90bbe2e9..82150a08cac 100644 --- a/packages/datastore/__tests__/mutation.test.ts +++ b/packages/datastore/__tests__/mutation.test.ts @@ -200,7 +200,7 @@ describe('MutationProcessor', () => { datastoreUserAgentDetails ), }), - signingServiceInfo: null, + signingServiceInfo: undefined, withCredentials: undefined, }), }) diff --git a/yarn.lock b/yarn.lock index 8f36ffef5b9..93929afed77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,13 +10,20 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@aws-amplify/data-schema-types@^0.4.2": +"@aws-amplify/data-schema-types@*", "@aws-amplify/data-schema-types@^0.4.2": version "0.4.2" resolved "https://registry.yarnpkg.com/@aws-amplify/data-schema-types/-/data-schema-types-0.4.2.tgz#42a63fbc97922e5f74ff5b2780145d1bdc35d80e" integrity sha512-ip5U1FisKxCAgIupo5FxoJVsWedKOl1UIJrUOpMWBtSnhCeVDJerNyaH7Su5YxmOX4ZpG7SAMF9qwF5C1zIKgg== dependencies: rxjs "^7.8.1" +"@aws-amplify/data-schema@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@aws-amplify/data-schema/-/data-schema-0.11.0.tgz#3c214d8a90d49b3867619eaa7e80681fabb28d46" + integrity sha512-fNofD5n6p++fMjqwli9vyHlXc+SPVNXummtLnT7BnGn9YG8FypSLVuEO3ZifzKWyJsUKqkNn4rxHKCQBggK/cA== + dependencies: + "@aws-amplify/data-schema-types" "*" + "@aws-crypto/crc32@3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz#07300eca214409c33e3ff769cd5697b57fdd38fa" From a5eb33543190fc91fe840eb65e86eaf4c541c3c7 Mon Sep 17 00:00:00 2001 From: Jim Blanchard Date: Fri, 10 Nov 2023 17:59:08 -0600 Subject: [PATCH 5/5] chore: Flip on `noImplicitAny` on `adapter-nextjs` (#12562) * chore: Flip on `noImplicitAny` on `adapter-nextjs`. * Fix tests. --- packages/adapter-nextjs/__tests__/createServerRunner.test.ts | 2 +- packages/adapter-nextjs/package.json | 2 +- packages/adapter-nextjs/tsconfig.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/adapter-nextjs/__tests__/createServerRunner.test.ts b/packages/adapter-nextjs/__tests__/createServerRunner.test.ts index 7525b8968c0..56eee26f28e 100644 --- a/packages/adapter-nextjs/__tests__/createServerRunner.test.ts +++ b/packages/adapter-nextjs/__tests__/createServerRunner.test.ts @@ -28,7 +28,7 @@ jest.mock( ); describe('createServerRunner', () => { - let createServerRunner; + let createServerRunner: any; const mockParseAWSExports = jest.fn(); const mockCreateAWSCredentialsAndIdentityIdProvider = jest.fn(); diff --git a/packages/adapter-nextjs/package.json b/packages/adapter-nextjs/package.json index 4a0fb2130f6..a2d7fb162be 100644 --- a/packages/adapter-nextjs/package.json +++ b/packages/adapter-nextjs/package.json @@ -20,7 +20,7 @@ "jest-fetch-mock": "3.0.3", "next": ">= 13.5.0 < 15.0.0", "rollup": "3.29.4", - "typescript": "5.1.6" + "typescript": "5.0.2" }, "publishConfig": { "access": "public" diff --git a/packages/adapter-nextjs/tsconfig.json b/packages/adapter-nextjs/tsconfig.json index cf90b690b44..4cf76894da1 100755 --- a/packages/adapter-nextjs/tsconfig.json +++ b/packages/adapter-nextjs/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "allowSyntheticDefaultImports": true, "target": "es2020", - "noImplicitAny": false, + "noImplicitAny": true, "lib": ["dom", "es2019", "esnext.asynciterable"], "module": "commonjs", "moduleResolution": "node",