Skip to content

Commit

Permalink
feat(api-graphql): add .enums property to the GQL client
Browse files Browse the repository at this point in the history
  • Loading branch information
HuiSF committed Feb 8, 2024
1 parent ba181c8 commit 22e249d
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 54 deletions.
38 changes: 36 additions & 2 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, Hub } from '@aws-amplify/core';
import { Amplify, AmplifyClassV6 } 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 @@ -147,7 +147,41 @@ describe('generateClient', () => {
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().',
'Client is not generate. This is likely due to `Amplify.configure()` not being called prior to `generateClient()` or because the configuration passed to `Amplify.configure()` is missing GraphQL provider configuration.',
);
});
});

describe('client `enums` property', () => {
const expectedEnumsProperties = ['Status'];

it('generates `enums` 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.enums)).toEqual(expectedEnumsProperties);
});

it('generates `enums` 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.enums)).toHaveLength(0);

Amplify.configure(configFixture);

expect(Object.keys(client.enums)).toEqual(expectedEnumsProperties);
});

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.enums.Status.values()
}).toThrow(
'Client is not generate. This is likely due to `Amplify.configure()` not being called prior to `generateClient()` or because the configuration passed to `Amplify.configure()` is missing GraphQL provider configuration.',
);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { GraphQLProviderConfig } from '@aws-amplify/core/internals/utils';
import { generateEnumsProperty } from '../../../src/internals/utils/generateEnumsProperty';

describe('generateEnumsProperty()', () => {
it('returns an empty object when there is no valid `modelIntrospection`', () => {
const mockAPIGraphQLConfig: GraphQLProviderConfig['GraphQL'] = {
endpoint: 'endpoint',
defaultAuthMode: 'iam',
};
const result = generateEnumsProperty(mockAPIGraphQLConfig);

expect(Object.keys(result)).toHaveLength(0);
});

it('returns expected `enums` object', () => {
const mockAPIGraphQLConfig: GraphQLProviderConfig['GraphQL'] = {
endpoint: 'endpoint',
defaultAuthMode: 'iam',
modelIntrospection: {
version: 1,
models: {},
nonModels: {},
enums: {
TodoStatus: {
name: 'TodoStatus',
values: ['Planned', 'InProgress', 'Completed'],
},
SomeEnum: {
name: 'SomeEnum',
values: ['value1', 'value2'],
},
},
},
};

const result = generateEnumsProperty(mockAPIGraphQLConfig);

expect(Object.keys(result)).toEqual(['TodoStatus', 'SomeEnum']);
expect((result as any).TodoStatus.values()).toEqual([
'Planned',
'InProgress',
'Completed',
]);
expect((result as any).SomeEnum.values()).toEqual(['value1', 'value2']);
});
});
58 changes: 23 additions & 35 deletions packages/api-graphql/src/internals/generateClient.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 { Hub } from '@aws-amplify/core';
import { EnumTypes, ModelTypes } from '@aws-amplify/data-schema-types';
import { graphql, cancel, isCancelError } from './v6';
import { generateModelsProperty } from './generateModelsProperty';
import { generateEnumsProperty } from './utils/generateEnumsProperty';
import { generateModelsProperty } from './utils/generateModelsProperty';
import { isApiGraphQLConfig } from './utils/isApiGraphQLProviderConfig';
import {
V6Client,
__amplify,
Expand All @@ -10,8 +14,7 @@ import {
__headers,
} from '../types';
import { ClientGenerationParams } from './types';
import { ModelTypes } from '@aws-amplify/data-schema-types';
import { Hub, HubCapsule, ResourcesConfig } from '@aws-amplify/core';
import { isConfigureEventWithResourceConfig } from './utils/isConfigureEventWithResourceConfig';

/**
* @private
Expand All @@ -23,7 +26,7 @@ import { Hub, HubCapsule, ResourcesConfig } from '@aws-amplify/core';
* @returns
*/
export function generateClient<T extends Record<any, any> = never>(
params: ClientGenerationParams
params: ClientGenerationParams,
): V6Client<T> {
const client = {
[__amplify]: params.amplify,
Expand All @@ -33,12 +36,16 @@ export function generateClient<T extends Record<any, any> = never>(
graphql,
cancel,
isCancelError,
models: {},
models: emptyProperty as ModelTypes<never>,
enums: emptyProperty as EnumTypes<never>,
} as any;

const config = params.amplify.getConfig();
const apiGraphqlConfig = params.amplify.getConfig().API?.GraphQL;

if (!config.API?.GraphQL) {
if (isApiGraphQLConfig(apiGraphqlConfig)) {
client.models = generateModelsProperty<T>(client, apiGraphqlConfig);
client.enums = generateEnumsProperty<T>(apiGraphqlConfig);
} else {
// This happens when the `Amplify.configure()` call gets evaluated after the `generateClient()` call.
//
// Cause: when the `generateClient()` and the `Amplify.configure()` calls are located in
Expand All @@ -51,52 +58,33 @@ export function generateClient<T extends Record<any, any> = never>(
//
// TODO: revisit, and reverify this approach when enabling multiple clients for multi-endpoints
// configuration.
client.models = emptyModels as ModelTypes<never>;
generateModelsPropertyOnAmplifyConfigure<T>(client);
} else {
client.models = generateModelsProperty<T>(
client,
config.API?.GraphQL,
);
generateModelsPropertyOnAmplifyConfigure(client);
}

return client as V6Client<T>;
}

const generateModelsPropertyOnAmplifyConfigure = <
T extends Record<any, any> = never,
>(clientRef: any) => {
const generateModelsPropertyOnAmplifyConfigure = (clientRef: any) => {
Hub.listen('core', coreEvent => {
if (!isConfigureEvent(coreEvent.payload)) {
if (!isConfigureEventWithResourceConfig(coreEvent.payload)) {
return;
}

const { data: resourceConfig } = coreEvent.payload;
const apiGraphQLConfig = coreEvent.payload.data.API?.GraphQL;

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

function isConfigureEvent(
payload: HubCapsule<'core', { event: string; data?: unknown }>['payload'],
): payload is {
event: 'configure';
data: ResourcesConfig;
} {
return payload.event === 'configure';
}

const emptyModels = new Proxy(
const emptyProperty = new Proxy(
{},
{
get() {
throw new Error(
'Could not generate client. This is likely due to Amplify.configure() not being called prior to generateClient().',
'Client is not generate. This is likely due to `Amplify.configure()` not being called prior to `generateClient()` or because the configuration passed to `Amplify.configure()` is missing GraphQL provider configuration.',
);
},
},
Expand Down
30 changes: 30 additions & 0 deletions packages/api-graphql/src/internals/utils/generateEnumsProperty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { ModelIntrospectionSchema, GraphQLProviderConfig } from '@aws-amplify/core/internals/utils';
import { EnumTypes } from '@aws-amplify/data-schema-types';

export const generateEnumsProperty = <T extends Record<any, any> = never>(
graphqlConfig: GraphQLProviderConfig['GraphQL'],
): EnumTypes<T> => {
const modelIntrospection: ModelIntrospectionSchema | undefined =
graphqlConfig.modelIntrospection;

if (!modelIntrospection) {
return {} as EnumTypes<never>;
}

const enums: {
[EnumName in keyof typeof modelIntrospection.enums]: {
values: () => string[];
};
} = {};

for (const [_, enumData] of Object.entries(modelIntrospection.enums)) {
enums[enumData.name] = {
values: () => enumData.values,
};
}

return enums as EnumTypes<T>;
};
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
// 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 { ClientGenerationParams } from './types';
import { V6Client, __authMode, __authToken } from '../types';
import { graphQLOperationsInfo, ModelOperation } from '../APIClient';
import { V6Client, __authMode, __authToken } from '../../types';

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';
import { GraphQLProviderConfig } from '@aws-amplify/core';
import { listFactory } from '../operations/list';
import { getFactory } from '../operations/get';
import { subscriptionFactory } from '../operations/subscription';
import { observeQueryFactory } from '../operations/observeQuery';
import {
GraphQLProviderConfig,
ModelIntrospectionSchema,
} from '@aws-amplify/core/internals/utils';

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

const modelIntrospection: ModelIntrospectionSchema | undefined =
graphqlConfig.modelIntrospection;
apiGraphQLConfig.modelIntrospection;

if (!modelIntrospection) {
return {} as ModelTypes<never>;
Expand All @@ -40,14 +41,14 @@ export function generateModelsProperty<T extends Record<any, any> = never>(
models[name][operationPrefix] = listFactory(
client,
modelIntrospection,
model
model,
);
} else if (SUBSCRIPTION_OPS.includes(operation)) {
models[name][operationPrefix] = subscriptionFactory(
client,
modelIntrospection,
model,
operation
operation,
);
} else if (operation === 'OBSERVE_QUERY') {
models[name][operationPrefix] = observeQueryFactory(models, model);
Expand All @@ -56,10 +57,10 @@ export function generateModelsProperty<T extends Record<any, any> = never>(
client,
modelIntrospection,
model,
operation
operation,
);
}
}
},
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { GraphQLProviderConfig } from '@aws-amplify/core/internals/utils';

export function isApiGraphQLConfig(
apiGraphQLConfig: GraphQLProviderConfig['GraphQL'] | undefined,
): apiGraphQLConfig is GraphQLProviderConfig['GraphQL'] {
return apiGraphQLConfig !== undefined;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { HubCapsule, ResourcesConfig } from '@aws-amplify/core';

export function isConfigureEventWithResourceConfig(
payload: HubCapsule<'core', { event: string; data?: unknown }>['payload'],
): payload is {
event: 'configure';
data: ResourcesConfig;
} {
return payload.event === 'configure';
}
3 changes: 2 additions & 1 deletion packages/api-graphql/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { AmplifyClassV6, ResourcesConfig } from '@aws-amplify/core';
import { ModelTypes, CustomHeaders } from '@aws-amplify/data-schema-types';
import { ModelTypes, CustomHeaders, EnumTypes } from '@aws-amplify/data-schema-types';
import { Source, DocumentNode, GraphQLError } from 'graphql';
export { OperationTypeNode } from 'graphql';
import { Observable } from 'rxjs';
Expand Down Expand Up @@ -382,6 +382,7 @@ export type V6Client<T extends Record<any, any> = never> = ExcludeNeverFields<{
cancel: (promise: Promise<any>, message?: string) => boolean;
isCancelError: (error: any) => boolean;
models: ModelTypes<T>;
enums: EnumTypes<T>;
}>;

export type V6ClientSSRRequest<T extends Record<any, any> = never> =
Expand Down
6 changes: 5 additions & 1 deletion packages/api-graphql/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"strictNullChecks": true
"strictNullChecks": true,
"baseUrl": ".",
"paths": {
"@aws-amplify/data-schema-types": ["../../node_modules/@aws-amplify/data-schema-types"]
}
},
"include": ["./src", "__tests__"]
}

0 comments on commit 22e249d

Please sign in to comment.