Skip to content

Commit

Permalink
fix(api-graphql): generate client.models on Hub core:configure event
Browse files Browse the repository at this point in the history
  • Loading branch information
HuiSF committed Feb 2, 2024
1 parent 7cb4e66 commit a620a2c
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 27 deletions.
61 changes: 50 additions & 11 deletions packages/api-graphql/__tests__/internals/generateClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as raw from '../../src';
import { Amplify, AmplifyClassV6 } from '@aws-amplify/core';
import { Amplify, AmplifyClassV6, Hub } from '@aws-amplify/core';
import { generateClient } from '../../src/internals';
import configFixture from '../fixtures/modeled/amplifyconfiguration';
import { Schema } from '../fixtures/modeled/schema';
Expand Down Expand Up @@ -53,7 +53,9 @@ function makeAppSyncStreams() {
);
if (matchedType) {
return new Observable(subscriber => {
streams[matchedType[1].toLowerCase()] = subscriber;
streams[
matchedType[1].toLowerCase() as 'create' | 'update' | 'delete'
] = subscriber;
});
}
});
Expand Down Expand Up @@ -103,15 +105,52 @@ const USER_AGENT_DETAILS = {
};

describe('generateClient', () => {
// test('raises clear error when API GraphQL isnt configured', () => {
// const getConfig = jest.fn().mockReturnValue({});
// const amplify = {
// getConfig,
// } as unknown as AmplifyClassV6;
// expect(() => generateClient({ amplify })).toThrow(
// 'The API configuration is missing. This is likely due to Amplify.configure() not being called prior to generateClient()'
// );
// });
describe('client `models` property', () => {
const expectedModelsProperties = [
'Todo',
'Note',
'TodoMetadata',
'ThingWithCustomerOwnerField',
'ThingWithOwnerFieldSpecifiedInModel',
'ThingWithAPIKeyAuth',
'ThingWithoutExplicitAuth',
'ThingWithCustomPk',
'CommunityPoll',
'CommunityPollAnswer',
'CommunityPollVote',
'CommunityPost',
];

it('generates `models` property when Amplify.getConfig() returns valid GraphQL provider config', () => {
Amplify.configure(configFixture); // clear the resource config

const client = generateClient<Schema>({ amplify: Amplify });

expect(Object.keys(client.models)).toEqual(expectedModelsProperties);
});

it('generates `models` property when Amplify.configure() is called later with a valid GraphQL provider config', async () => {
Amplify.configure({}); // clear the ResourceConfig mimic Amplify.configure has not been called
const client = generateClient<Schema>({ amplify: Amplify });

expect(Object.keys(client.models)).toHaveLength(0);

Amplify.configure(configFixture);

expect(Object.keys(client.models)).toEqual(expectedModelsProperties);
});

it('generates `models` property throwing error when there is no valid GraphQL provider config can be resolved', () => {
Amplify.configure({}); // clear the ResourceConfig mimic Amplify.configure has not been called
const client = generateClient<Schema>({ amplify: Amplify });

expect(() => {
client.models.Todo.create({ name: 'todo' });
}).toThrow(
'Could not generate client. This is likely due to Amplify.configure() not being called prior to generateClient().',
);
});
});

test('can produce a client bound to an arbitrary amplify object for getConfig()', async () => {
// TS lies: We don't care what `amplify` is or does. We want want to make sure
Expand Down
47 changes: 46 additions & 1 deletion packages/api-graphql/src/internals/generateClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
__headers,
} from '../types';
import { ClientGenerationParams } from './types';
import { ModelTypes } from '@aws-amplify/data-schema-types';
import { Hub, ResourcesConfig } from '@aws-amplify/core';

/**
* @private
Expand All @@ -34,7 +36,50 @@ export function generateClient<T extends Record<any, any> = never>(
models: {},
} as any;

client.models = generateModelsProperty<T>(client, params);
const config = params.amplify.getConfig();

if (!config.API?.GraphQL) {
client.models = emptyModels as ModelTypes<never>;
generateModelsPropertyOnAmplifyConfigure<T>(client);
} else {
client.models = generateModelsProperty<T>(
client,
config.API?.GraphQL,
);
}

return client as V6Client<T>;
}

const generateModelsPropertyOnAmplifyConfigure = <
T extends Record<any, any> = never,
>(clientRef: any) => {
Hub.listen('core', coreEvent => {
const { event, data } = coreEvent.payload;

if (event !== 'configure') {
return;
}

// data is guaranteed to be `ResourcesConfig` when the event is `configure`
const resourceConfig = data as ResourcesConfig;

if (resourceConfig.API?.GraphQL) {
clientRef.models = generateModelsProperty<T>(
clientRef,
resourceConfig.API?.GraphQL,
);
}
});
};

const emptyModels = new Proxy(
{},
{
get() {
throw new Error(
'Could not generate client. This is likely due to Amplify.configure() not being called prior to generateClient().',
);
},
},
);
17 changes: 3 additions & 14 deletions packages/api-graphql/src/internals/generateModelsProperty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,16 @@ import { getFactory } from './operations/get';
import { subscriptionFactory } from './operations/subscription';
import { observeQueryFactory } from './operations/observeQuery';
import { ModelIntrospectionSchema } from '@aws-amplify/core/internals/utils';
import { GraphQLProviderConfig } from '@aws-amplify/core';

export function generateModelsProperty<T extends Record<any, any> = never>(
client: V6Client<Record<string, any>>,
params: ClientGenerationParams
graphqlConfig: GraphQLProviderConfig['GraphQL'],
): ModelTypes<T> {
const models = {} as any;
const config = params.amplify.getConfig();

if (!config.API?.GraphQL) {
// breaks compatibility with certain bundler, e.g. Vite where component files are evaluated before
// the entry point causing false positive errors. Revisit how to better handle this post-launch

// throw new Error(
// 'The API configuration is missing. This is likely due to Amplify.configure() not being called
// prior to generateClient().'
// );
return {} as ModelTypes<never>;
}

const modelIntrospection: ModelIntrospectionSchema | undefined =
config.API.GraphQL.modelIntrospection;
graphqlConfig.modelIntrospection;

if (!modelIntrospection) {
return {} as ModelTypes<never>;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export {
AuthUserPoolConfig,
AuthUserPoolAndIdentityPoolConfig,
APIConfig,
GraphQLProviderConfig,
PredictionsConfig,
StorageAccessLevel,
StorageConfig,
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/singleton/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { APIConfig, LibraryAPIOptions } from './API/types';
import {
APIConfig,
LibraryAPIOptions,
GraphQLProviderConfig,
} from './API/types';
import { AnalyticsConfig } from './Analytics/types';
import {
AuthConfig,
Expand Down Expand Up @@ -57,6 +61,7 @@ export {
AuthIdentityPoolConfig,
AuthUserPoolAndIdentityPoolConfig,
GetCredentialsOptions,
GraphQLProviderConfig,
InteractionsConfig,
PredictionsConfig,
StorageAccessLevel,
Expand Down

0 comments on commit a620a2c

Please sign in to comment.