Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add angular codegen v6 support #799

Merged
merged 9 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/amplify-codegen/src/commands/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ async function add(context, apiId = null, region = 'us-east-1') {
apiId,
...(withoutInit ? { frontend } : {}),
...(withoutInit && frontend === 'javascript' ? { framework } : {}),
// The default Amplify JS lib version is set for 6 for angular codegen
...(answer.target === 'angular' ? { amplifyJsLibraryVersion: 6 } : {}),
},
};

Expand Down
16 changes: 16 additions & 0 deletions packages/amplify-codegen/src/commands/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ async function generateTypes(context, forceDownloadSchema, withoutInit = false,
}
const target = cfg.amplifyExtension.codeGenTarget;

let amplifyJsLibraryVersion = cfg.amplifyExtension.amplifyJsLibraryVersion;

/**
* amplifyJsLibraryVersion config is currently used for angular codegen
* The supported value is 5 for JS v5 and 6 for JS v6
* When the value is undefined, it will stay at codegen for JS v5 for non-breaking change for existing users
* For the other values set, a warning message will be sent in the console and the codegen will use the v6 codegen
*/
if (target === 'angular' && amplifyJsLibraryVersion && amplifyJsLibraryVersion !== 6 && amplifyJsLibraryVersion !== 5) {
context.print.warning(
`Amplify JS library version ${amplifyJsLibraryVersion} is not supported. The current support JS library version is [5, 6]. Codegen will be executed for JS v6 instead.`,
);
amplifyJsLibraryVersion = 6
}

const excludes = cfg.excludes.map(pattern => `!${pattern}`);
const normalizedPatterns = [...includeFiles, ...excludes].map((path) => normalizePathForGlobPattern(path));
const queryFilePaths = globby.sync(normalizedPatterns, {
Expand Down Expand Up @@ -114,6 +129,7 @@ async function generateTypes(context, forceDownloadSchema, withoutInit = false,
target,
introspection,
multipleSwiftFiles,
amplifyJsLibraryVersion,
});
const outputs = Object.entries(output);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ async function askCodeGenTargetLanguage(context, target, withoutInit = false, de
default: target || null,
},
]);

return answer.target;
}

Expand Down
35 changes: 35 additions & 0 deletions packages/amplify-codegen/tests/commands/types.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const { ensureIntrospectionSchema, getFrontEndHandler, getAppSyncAPIDetails } =
const MOCK_CONTEXT = {
print: {
info: jest.fn(),
warning: jest.fn(),
},
amplify: {
getEnvInfo: jest.fn(),
Expand Down Expand Up @@ -163,4 +164,38 @@ describe('command - types', () => {
expect(generateTypesHelper).not.toHaveBeenCalled();
expect(globby.sync).not.toHaveBeenCalled();
});

it('should show a warning if the amplifyJsLibraryVersion is invalid', async () => {
const MOCK_ANGULAR_PROJECT = {
excludes: [MOCK_EXCLUDE_PATH],
includes: [MOCK_INCLUDE_PATH],
schema: MOCK_SCHEMA,
amplifyExtension: {
generatedFileName: MOCK_GENERATED_FILE_NAME,
codeGenTarget: 'angular',
graphQLApiId: MOCK_API_ID,
region: MOCK_REGION,
amplifyJsLibraryVersion: 7,
},
};
fs.readFileSync
.mockReturnValueOnce('query 1')
.mockReturnValueOnce('query 2')
.mockReturnValueOnce('schema');
loadConfig.mockReturnValue({
getProjects: jest.fn().mockReturnValue([MOCK_ANGULAR_PROJECT]),
});
await generateTypes(MOCK_CONTEXT, false);
expect(MOCK_CONTEXT.print.warning).toHaveBeenCalledWith(
'Amplify JS library version 7 is not supported. The current support JS library version is [5, 6]. Codegen will be executed for JS v6 instead.'
);
expect(generateTypesHelper).toHaveBeenCalledWith({
queries: [new Source('query 1', 'q1.gql'), new Source('query 2', 'q2.gql')],
schema: 'schema',
target: 'angular',
introspection: false,
multipleSwiftFiles: false,
amplifyJsLibraryVersion: 6,
});
});
});
1 change: 1 addition & 0 deletions packages/graphql-generator/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export type GenerateTypesOptions = {
queries: string | Source[];
introspection?: boolean;
multipleSwiftFiles?: boolean;
amplifyJsLibraryVersion?: number;
};

// @public (undocumented)
Expand Down
3 changes: 2 additions & 1 deletion packages/graphql-generator/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { generate, generateFromString } from '@aws-amplify/graphql-types-generat
import { GenerateTypesOptions, GeneratedOutput } from './typescript';

export async function generateTypes(options: GenerateTypesOptions): Promise<GeneratedOutput> {
const { schema, target, queries, multipleSwiftFiles = false, introspection = false } = options;
const { schema, target, queries, multipleSwiftFiles = false, introspection = false, amplifyJsLibraryVersion } = options;

const generatedOutput = await generateFromString(schema, introspection, queries, target, multipleSwiftFiles, {
addTypename: true,
complexObjectSupport: 'auto',
amplifyJsLibraryVersion,
});

return Object.fromEntries(Object.entries(generatedOutput).map(([filepath, contents]) => [path.basename(filepath), contents]));
Expand Down
1 change: 1 addition & 0 deletions packages/graphql-generator/src/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type GenerateTypesOptions = {
queries: string | Source[];
introspection?: boolean;
multipleSwiftFiles?: boolean; // only used when target is swift
amplifyJsLibraryVersion?: number; // only used when target is angular
};

export type GenerateModelsOptions = {
Expand Down
92 changes: 72 additions & 20 deletions packages/graphql-types-generator/src/angular/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,46 @@ import { Property, interfaceDeclaration } from '../typescript/language';
import { isList } from '../utilities/graphql';
import { propertyDeclarations } from '../flow/codeGeneration';

export function generateSource(context: LegacyCompilerContext) {
export function generateSource(context: LegacyCompilerContext, options?: { isAngularV6: boolean }) {
const isAngularV6: boolean = options?.isAngularV6 ?? false;
const importApiStatement = isAngularV6
? `import { Client, generateClient, GraphQLResult } from 'aws-amplify/api';`
: `import API, { graphqlOperation, GraphQLResult } from '@aws-amplify/api-graphql';`
const importObservable = isAngularV6
? `import { Observable } from 'rxjs';`
: `import { Observable } from 'zen-observable-ts';`;

const generator = new CodeGenerator<LegacyCompilerContext>(context);

generator.printOnNewline('/* tslint:disable */');
generator.printOnNewline('/* eslint-disable */');
generator.printOnNewline('// This file was automatically generated and should not be edited.');

generator.printOnNewline(`import { Injectable } from '@angular/core';`);
generator.printOnNewline(`import API, { graphqlOperation, GraphQLResult } from '@aws-amplify/api-graphql';`);
generator.printOnNewline(importApiStatement);

generator.printOnNewline(`import { Observable } from 'zen-observable-ts';`);
generator.printOnNewline(importObservable);
generator.printNewline();

generateTypes(generator, context);
generateTypes(generator, context, { isAngularV6 });
generator.printNewline();

generateAngularService(generator, context);
generateAngularService(generator, context, { isAngularV6 });
return prettier.format(generator.output, { parser: 'typescript' });
}

function generateTypes(generator: CodeGenerator, context: LegacyCompilerContext) {
function generateTypes(generator: CodeGenerator, context: LegacyCompilerContext, options?: { isAngularV6: boolean }) {
const isAngularV6: boolean = options?.isAngularV6 ?? false;
// if subscription operations exist create subscriptionResponse interface
// https://github.com/aws-amplify/amplify-cli/issues/5284
if (context.schema.getSubscriptionType()) {
generateSubscriptionResponseWrapper(generator);
if (!isAngularV6) {
/**
* V6 does not need to generate the response wrapper.
* The access pattern for V5 is `event.value.data` and `event.data` for V6
*/
generateSubscriptionResponseWrapper(generator);
}
generateSubscriptionOperationTypes(generator, context);
}
context.typesUsed.forEach(type => typeDeclarationForGraphQLType(generator, type));
Expand Down Expand Up @@ -152,57 +167,90 @@ function getReturnTypeName(generator: CodeGenerator, op: LegacyOperation): Strin
}
}

function generateAngularService(generator: CodeGenerator, context: LegacyCompilerContext) {
function generateAngularService(generator: CodeGenerator, context: LegacyCompilerContext, options?: { isAngularV6: boolean }) {
const isAngularV6: boolean = options?.isAngularV6 ?? false;
const operations = context.operations;
generator.printOnNewline(`@Injectable({
providedIn: 'root'
})`);
generator.printOnNewline(`export class APIService {`);

generator.withIndent(() => {
if (isAngularV6) {
generator.printOnNewline('public client: Client;');
generateServiceConstructor(generator);
}
Object.values(operations).forEach((op: LegacyOperation) => {
if (op.operationType === 'subscription') {
return generateSubscriptionOperation(generator, op);
return generateSubscriptionOperation(generator, op, { isAngularV6 });
}
if (op.operationType === 'query' || op.operationType === 'mutation') {
return generateQueryOrMutationOperation(generator, op);
return generateQueryOrMutationOperation(generator, op, { isAngularV6 });
}
});
generator.printOnNewline('}');
});
}

function generateSubscriptionOperation(generator: CodeGenerator, op: LegacyOperation) {
function generateServiceConstructor(generator: CodeGenerator) {
generator.printOnNewline();
generator.print(`constructor() {`);
generator.withIndent(() => {
generator.print(`this.client = generateClient();`);
});
generator.printOnNewline('}');
}

function generateSubscriptionOperation(generator: CodeGenerator, op: LegacyOperation, options?: { isAngularV6: boolean }) {
const isAngularV6: boolean = options?.isAngularV6 ?? false;
const statement = formatTemplateString(generator, op.source);
const { operationName } = op;
const vars = variablesFromField(generator.context, op.variables);
const returnType = getReturnTypeName(generator, op);
generator.printNewline();
const subscriptionName = `${operationName}Listener`;
if (!vars.length) {
generator.print(
`${subscriptionName}: Observable<SubscriptionResponse<${returnType}>> = API.graphql(graphqlOperation(\n\`${statement}\`)) as Observable<SubscriptionResponse<${returnType}>>`,
);
if (isAngularV6) {
generator.print(
`${subscriptionName}(): Observable<GraphQLResult<${returnType}>> { return this.client.graphql({ query: \n\`${statement}\` }) as any; }`,
);
} else {
generator.print(
`${subscriptionName}: Observable<SubscriptionResponse<${returnType}>> = API.graphql(graphqlOperation(\n\`${statement}\`)) as Observable<SubscriptionResponse<${returnType}>>`,
);
}
} else {
generator.print(`${subscriptionName}(`);
variableDeclaration(generator, vars);
generator.print(`) : Observable<SubscriptionResponse<${returnType}>> {`);
if (isAngularV6) {
generator.print(`) : Observable<GraphQLResult<${returnType}>> {`);
} else {
generator.print(`) : Observable<SubscriptionResponse<${returnType}>> {`);
}
generator.withIndent(() => {
generator.printNewlineIfNeeded();
generator.print(`const statement = \`${statement}\``);
const params = ['statement'];
variableAssignmentToInput(generator, vars);
params.push('gqlAPIServiceArguments');
generator.printOnNewline(
`return API.graphql(graphqlOperation(${params.join(', ')})) as Observable<SubscriptionResponse<${returnType}>>;`,
);
if (isAngularV6) {
generator.printOnNewline(
`return this.client.graphql({ query: statement, variables: gqlAPIServiceArguments }) as any;`,
);
} else {
generator.printOnNewline(
`return API.graphql(graphqlOperation(${params.join(', ')})) as Observable<SubscriptionResponse<${returnType}>>;`,
);
}

generator.printOnNewline('}');
});
}
generator.printNewline();
}

function generateQueryOrMutationOperation(generator: CodeGenerator, op: LegacyOperation) {
function generateQueryOrMutationOperation(generator: CodeGenerator, op: LegacyOperation, options?: { isAngularV6: boolean }) {
const isAngularV6: boolean = options?.isAngularV6 ?? false;
const statement = formatTemplateString(generator, op.source);
const vars = variablesFromField(generator.context, op.variables);
const returnType = getReturnTypeName(generator, op);
Expand All @@ -221,7 +269,11 @@ function generateQueryOrMutationOperation(generator: CodeGenerator, op: LegacyOp
variableAssignmentToInput(generator, vars);
params.push('gqlAPIServiceArguments');
}
generator.printOnNewline(`const response = await API.graphql(graphqlOperation(${params.join(', ')})) as any;`);
if (isAngularV6) {
generator.printOnNewline(`const response = await this.client.graphql({ query: statement, ${op.variables.length ? `variables: gqlAPIServiceArguments, `: ''}}) as any;`);
} else {
generator.printOnNewline(`const response = await API.graphql(graphqlOperation(${params.join(', ')})) as any;`);
}
generator.printOnNewline(`return (<${returnType}>response.data${resultProp})`);
});
generator.printOnNewline('}');
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql-types-generator/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function generateForTarget(
case 'scala':
return generateScalaSource(context, options);
case 'angular':
return generateAngularSource(context);
return generateAngularSource(context, { isAngularV6: options.amplifyJsLibraryVersion === 6 });
default:
throw new Error(`${target} is not supported.`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const extensionMap: { 'typescript': string, 'flow': string, 'angular': string, '
swift: 'swift',
};

const folderMap: { 'typescript': string, 'flow': string, 'angular': string, 'swift': string } = {
const folderMap: { 'typescript': string, 'flow': string, 'angular': string, 'swift': string, } = {
typescript: 'src',
flow: 'src',
angular: 'src/app',
Expand Down
Loading
Loading