From 1c37267c89b84c1c28aca56448e98da6132077a0 Mon Sep 17 00:00:00 2001 From: Jon Wire Date: Wed, 12 Jul 2023 17:21:47 -0500 Subject: [PATCH] wip: alt type tagged not inferring from fieldName --- .../src/commands/statements.js | 1 + .../src/utils/GraphQLStatementsFormatter.js | 35 +++++--- .../utils/GraphQLStatementsFormatter.test.js | 16 +++- .../GraphQLStatementsFormatter.test.js.snap | 11 ++- packages/graphql-docs-generator/src/index.ts | 80 +++++++++++++++---- .../src/typescript/codeGeneration.ts | 6 +- 6 files changed, 117 insertions(+), 32 deletions(-) diff --git a/packages/amplify-codegen/src/commands/statements.js b/packages/amplify-codegen/src/commands/statements.js index 4fc34319..7366647f 100644 --- a/packages/amplify-codegen/src/commands/statements.js +++ b/packages/amplify-codegen/src/commands/statements.js @@ -72,6 +72,7 @@ async function generateStatements(context, forceDownloadSchema, maxDepth, withou // default typenameIntrospection to true when not set typenameIntrospection: cfg.amplifyExtension.typenameIntrospection === undefined ? true : !!cfg.amplifyExtension.typenameIntrospection, + includeMetaData: true }); if (!generatedOps) { context.print.warning('No GraphQL statements are generated. Check if the introspection schema has GraphQL operations defined.'); diff --git a/packages/amplify-codegen/src/utils/GraphQLStatementsFormatter.js b/packages/amplify-codegen/src/utils/GraphQLStatementsFormatter.js index ebfda889..775dd4c3 100644 --- a/packages/amplify-codegen/src/utils/GraphQLStatementsFormatter.js +++ b/packages/amplify-codegen/src/utils/GraphQLStatementsFormatter.js @@ -1,4 +1,8 @@ const prettier = require('prettier'); +const { + interfaceNameFromOperation, + interfaceVariablesNameFromOperation +} = require('@aws-amplify/graphql-types-generator/lib/typescript/codeGeneration'); const CODEGEN_WARNING = 'this is an auto generated file. This will be overwritten'; const LINE_DELIMITOR = '\n'; @@ -52,7 +56,7 @@ class GraphQLStatementsFormatter { formatGraphQL(statements) { const headerBuffer = this.headerComments.map(comment => `# ${comment}`).join(LINE_DELIMITOR); - const statementsBuffer = statements ? [...statements.values()].join(LINE_DELIMITOR) : ''; + const statementsBuffer = statements ? [...statements.values()].map(s => s.graphql).join(LINE_DELIMITOR) : ''; const formattedOutput = [headerBuffer, LINE_DELIMITOR, statementsBuffer].join(LINE_DELIMITOR); return formattedOutput; } @@ -62,9 +66,10 @@ class GraphQLStatementsFormatter { const headerBuffer = this.headerComments.map(comment => `// ${comment}`).join(LINE_DELIMITOR); const formattedStatements = []; if (statements) { - for (const [key, value] of statements) { - const typeTag = this.buildTypeTag(key); - formattedStatements.push(`export const ${key} = /* GraphQL */ \`${value}\`${typeTag}`); + console.log('STATEMENTS', { statements }); + for (const [key, {graphql, operationName, operationType}] of statements) { + const typeTag = this.buildTypeTag(operationName, operationType); + formattedStatements.push(`export const ${key} = /* GraphQL */ \`${graphql}\`${typeTag}`); } } const formattedOutput = [lintOverridesBuffer, headerBuffer, LINE_DELIMITOR, this.typeDefs, LINE_DELIMITOR, ...formattedStatements].join( @@ -73,13 +78,25 @@ class GraphQLStatementsFormatter { return formattedOutput; } - buildTypeTag(name) { - if (!this.opTypeName) return ''; + buildTypeTag(operationName, operationType) { if (this.language !== 'typescript') return ''; + if (!operationType) return ''; - const titleCasedName = `${name[0].toUpperCase()}${name.slice(1)}`; - const variablesTypeName = `APITypes.${titleCasedName}${this.opTypeName}Variables`; - const resultTypeName = `APITypes.${titleCasedName}${this.opTypeName}`; + // const titleCasedName = `${name[0].toUpperCase()}${name.slice(1)}`; + + const resultTypeName = `APITypes.${interfaceNameFromOperation({ + operationName, + operationType, + // operationName: titleCasedName, + // operationType: this.opTypeName, + })}`; + + const variablesTypeName = `APITypes.${interfaceVariablesNameFromOperation({ + operationName, + operationType, + // operationName: titleCasedName, + // operationType: this.opTypeName, + })}`; return ` as Generated${this.opTypeName}< ${variablesTypeName}, diff --git a/packages/amplify-codegen/tests/utils/GraphQLStatementsFormatter.test.js b/packages/amplify-codegen/tests/utils/GraphQLStatementsFormatter.test.js index faae69b6..c79b2807 100644 --- a/packages/amplify-codegen/tests/utils/GraphQLStatementsFormatter.test.js +++ b/packages/amplify-codegen/tests/utils/GraphQLStatementsFormatter.test.js @@ -2,9 +2,8 @@ const { GraphQLStatementsFormatter } = require('../../src/utils'); describe('GraphQL statements Formatter', () => { const statements = new Map(); - statements.set( - 'getTodo', - ` + + const graphql = ` query GetProject($id: ID!) { getProject(id: $id) { id @@ -13,7 +12,16 @@ describe('GraphQL statements Formatter', () => { updatedAt } } - `, + `; + + statements.set( + 'getProject', + { + graphql, + operationName: 'GetProject', + operationType: 'query', + fieldName: 'getProject' + }, ); it('Generates formatted output for JS frontend', () => { diff --git a/packages/amplify-codegen/tests/utils/__snapshots__/GraphQLStatementsFormatter.test.js.snap b/packages/amplify-codegen/tests/utils/__snapshots__/GraphQLStatementsFormatter.test.js.snap index da126f95..a3ef8ea0 100644 --- a/packages/amplify-codegen/tests/utils/__snapshots__/GraphQLStatementsFormatter.test.js.snap +++ b/packages/amplify-codegen/tests/utils/__snapshots__/GraphQLStatementsFormatter.test.js.snap @@ -18,7 +18,7 @@ exports[`GraphQL statements Formatter Generates formatted output for Flow fronte "// @flow // this is an auto generated file. This will be overwritten -export const getTodo = /* GraphQL */ \` +export const getProject = /* GraphQL */ \` query GetProject($id: ID!) { getProject(id: $id) { id @@ -49,7 +49,7 @@ exports[`GraphQL statements Formatter Generates formatted output for JS frontend "/* eslint-disable */ // this is an auto generated file. This will be overwritten -export const getTodo = /* GraphQL */ \` +export const getProject = /* GraphQL */ \` query GetProject($id: ID!) { getProject(id: $id) { id @@ -73,7 +73,7 @@ type GeneratedQuery = string & { __generatedQueryOutput: OutputType; }; -export const getTodo = /* GraphQL */ \` +export const getProject = /* GraphQL */ \` query GetProject($id: ID!) { getProject(id: $id) { id @@ -82,6 +82,9 @@ export const getTodo = /* GraphQL */ \` updatedAt } } - \` as GeneratedQuery; + \` as GeneratedQuery< + APITypes.GetProjectQueryVariables, + APITypes.GetProjectQuery +>; " `; diff --git a/packages/graphql-docs-generator/src/index.ts b/packages/graphql-docs-generator/src/index.ts index fccf44ac..37bfeecb 100644 --- a/packages/graphql-docs-generator/src/index.ts +++ b/packages/graphql-docs-generator/src/index.ts @@ -4,10 +4,15 @@ import { buildSchema } from './generator/utils/loading'; import { getTemplatePartials, getOperationPartial, getExternalFragmentPartial } from './generator/utils/templates'; export { buildSchema } from './generator/utils/loading'; -export function generateGraphQLDocuments( +export function generateGraphQLDocuments( schema: string, - options: { maxDepth?: number; useExternalFragmentForS3Object?: boolean; typenameIntrospection?: boolean }, -): GeneratedOperations { + options: { + maxDepth?: number; + useExternalFragmentForS3Object?: boolean; + typenameIntrospection?: boolean; + includeMetaData?: INCLUDE_META; + }, +): GeneratedOperations> { const opts = { maxDepth: 2, useExternalFragmentForS3Object: true, @@ -25,16 +30,17 @@ export function generateGraphQLDocuments( registerHelpers(); const allOperations = { - queries: new Map(), - mutations: new Map(), - subscriptions: new Map(), + queries: new Map>(), + mutations: new Map>(), + subscriptions: new Map>(), fragments: new Map(), }; ['queries', 'mutations', 'subscriptions'].forEach(op => { const ops = gqlOperations[op]; + console.log({ ops }); if (ops.length) { - const renderedOperations = renderOperations(gqlOperations[op]); + const renderedOperations = renderOperations(gqlOperations[op], options.includeMetaData); allOperations[op] = renderedOperations; } }); @@ -47,26 +53,72 @@ export function generateGraphQLDocuments( return allOperations; } -type GeneratedOperations = { - queries: Map; - mutations: Map; - subscriptions: Map; +type GraphQLWithMeta = { + /** + * The generated graphql string. + */ + graphql: string; + + /** + * E.g., `GetMyModel` or `ListMyModels`. + * + * This is used for generating type names. + * + * `undefined` for fragments. + */ + operationName: string | undefined; + + /** + * `undefined` for fragments. + */ + operationType: "query" | "mutation" | "subscription" | undefined; + + /** + * E.g., `getMyModel` or `listMyModels`. + * + * It's the name of the operation that lives under Queries, Mutations, or Subscriptions + * in the schema and is generally used as the key + variable name referring to the query. + */ + fieldName: string; +} + +type GeneratedOperations = { + queries: Map; + mutations: Map; + subscriptions: Map; fragments: Map; }; -function renderOperations(operations: Array): Map { - const renderedOperations = new Map(); +type MapValueType = INCLUDE_META extends true ? GraphQLWithMeta : string; + +function renderOperations< + INCLUDE_META extends boolean, +>(operations: Array, includeMetaData: INCLUDE_META): Map> { + const renderedOperations = new Map>(); if (operations?.length) { operations.forEach(op => { const name = op.fieldName || op.name; const gql = renderOperation(op); - renderedOperations.set(name, gql); + if (includeMetaData) { + renderedOperations.set(name, { + graphql: gql, + operationName: op.name, + operationType: op.type, + fieldName: op.fieldName + } as any); + } else { + renderedOperations.set(name, gql as any); + } }); } return renderedOperations; } +function isMetaIncluded(includeMetaData: boolean, operationsMap: any): operationsMap is GraphQLWithMeta { + return includeMetaData === true; +} + function renderOperation(operation: GQLTemplateOp): string { // TODO: cleanup // console.log('rendering operation', operation); diff --git a/packages/graphql-types-generator/src/typescript/codeGeneration.ts b/packages/graphql-types-generator/src/typescript/codeGeneration.ts index c6a751e9..050576b3 100644 --- a/packages/graphql-types-generator/src/typescript/codeGeneration.ts +++ b/packages/graphql-types-generator/src/typescript/codeGeneration.ts @@ -165,6 +165,10 @@ export function interfaceNameFromOperation({ operationName, operationType }: { o } } +export function interfaceVariablesNameFromOperation({ operationName, operationType }: { operationName: string; operationType: string }) { + return `${interfaceNameFromOperation({ operationName, operationType })}Variables`; +} + export function interfaceVariablesDeclarationForOperation( generator: CodeGenerator, { operationName, operationType, variables }: LegacyOperation, @@ -172,7 +176,7 @@ export function interfaceVariablesDeclarationForOperation( if (!variables || variables.length < 1) { return; } - const interfaceName = `${interfaceNameFromOperation({ operationName, operationType })}Variables`; + const interfaceName = interfaceVariablesNameFromOperation({operationName, operationType}); interfaceDeclaration( generator,