diff --git a/.changeset/support-apollo-angular-v12.md b/.changeset/support-apollo-angular-v12.md new file mode 100644 index 0000000000..08324211a1 --- /dev/null +++ b/.changeset/support-apollo-angular-v12.md @@ -0,0 +1,7 @@ +--- +"@graphql-codegen/typescript-apollo-angular": patch +--- + +Support apollo-angular v12+ combined parameter syntax + +For `apolloAngularVersion >= 12`, the generated SDK methods now internally combine variables into the options object when calling apollo-angular methods, matching the v12+ API requirements. The external SDK method signatures remain unchanged for backward compatibility. diff --git a/packages/plugins/typescript/apollo-angular/src/visitor.ts b/packages/plugins/typescript/apollo-angular/src/visitor.ts index ba266c0a4d..2e886e69cb 100644 --- a/packages/plugins/typescript/apollo-angular/src/visitor.ts +++ b/packages/plugins/typescript/apollo-angular/src/visitor.ts @@ -348,6 +348,8 @@ export class ApolloAngularVisitor extends ClientSideBaseVisitor< ); const hasQueries = this._operationsToInclude.find(o => o.operationType === 'Query'); + const isV12Plus = this.config.apolloAngularVersion >= 12; + const allPossibleActions = this._operationsToInclude .map(o => { const operationResultType = this._externalImportPrefix + o.operationResultType; @@ -360,27 +362,40 @@ export class ApolloAngularVisitor extends ClientSideBaseVisitor< v => v.type.kind !== Kind.NON_NULL_TYPE || !!v.defaultValue, ); - const options = - o.operationType === 'Mutation' + // For v12+, all OptionsAlone types have signature for proper type inference + const options = isV12Plus + ? `${o.operationType}OptionsAlone<${operationResultType}, ${operationVariablesTypes}>` + : o.operationType === 'Mutation' ? `${o.operationType}OptionsAlone<${operationResultType}, ${operationVariablesTypes}>` : `${o.operationType}OptionsAlone<${operationVariablesTypes}>`; + // For v12+, combine variables into options when calling apollo-angular + const callArgs = isV12Plus ? '{ ...options, variables }' : 'variables, options'; + const method = ` ${camelCase(o.node.name.value)}(variables${ optionalVariables ? '?' : '' }: ${operationVariablesTypes}, options?: ${options}) { - return this.${camelCase(o.serviceName)}.${actionType(o.operationType)}(variables, options) + return this.${camelCase(o.serviceName)}.${actionType(o.operationType)}(${callArgs}) }`; let watchMethod: string; if (o.operationType === 'Query') { + // For v12+, add explicit return type to ensure proper type inference + const watchOptions = isV12Plus + ? `WatchQueryOptionsAlone<${operationResultType}, ${operationVariablesTypes}>` + : `WatchQueryOptionsAlone<${operationVariablesTypes}>`; + const returnType = isV12Plus + ? `: Apollo.QueryRef<${operationResultType}, ${operationVariablesTypes}>` + : ''; + watchMethod = ` ${camelCase(o.node.name.value)}Watch(variables${ optionalVariables ? '?' : '' - }: ${operationVariablesTypes}, options?: WatchQueryOptionsAlone<${operationVariablesTypes}>) { - return this.${camelCase(o.serviceName)}.watch(variables, options) + }: ${operationVariablesTypes}, options?: ${watchOptions})${returnType} { + return this.${camelCase(o.serviceName)}.watch(${callArgs}) }`; } return [method, watchMethod].join(''); @@ -407,17 +422,29 @@ ${camelCase(o.node.name.value)}Watch(variables${ hasQueries || hasMutations || hasSubscriptions ? `type Omit = Pick>;` : ''; + + // For apollo-angular v12+ with Apollo Client 4, use type aliases with Apollo.Apollo namespace types + // (Apollo.Apollo accesses the class namespace from the module import) + // Apollo namespace uses order const watchType = hasQueries - ? `interface WatchQueryOptionsAlone extends Omit, 'query' | 'variables'> {}` + ? isV12Plus + ? `type WatchQueryOptionsAlone = Omit, 'query' | 'variables'>` + : `interface WatchQueryOptionsAlone extends Omit, 'query' | 'variables'> {}` : ''; const queryType = hasQueries - ? `interface QueryOptionsAlone extends Omit, 'query' | 'variables'> {}` + ? isV12Plus + ? `type QueryOptionsAlone = Omit, 'query' | 'variables'>` + : `interface QueryOptionsAlone extends Omit, 'query' | 'variables'> {}` : ''; const mutationType = hasMutations - ? `interface MutationOptionsAlone extends Omit, 'mutation' | 'variables'> {}` + ? isV12Plus + ? `type MutationOptionsAlone = Omit, 'mutation' | 'variables'>` + : `interface MutationOptionsAlone extends Omit, 'mutation' | 'variables'> {}` : ''; const subscriptionType = hasSubscriptions - ? `interface SubscriptionOptionsAlone extends Omit, 'query' | 'variables'> {}` + ? isV12Plus + ? `type SubscriptionOptionsAlone = Omit, 'query' | 'variables'>` + : `interface SubscriptionOptionsAlone extends Omit, 'query' | 'variables'> {}` : ''; const types = [omitType, watchType, queryType, mutationType, subscriptionType] diff --git a/packages/plugins/typescript/apollo-angular/tests/apollo-angular.spec.ts b/packages/plugins/typescript/apollo-angular/tests/apollo-angular.spec.ts index 581e3d7808..1dd99972cb 100644 --- a/packages/plugins/typescript/apollo-angular/tests/apollo-angular.spec.ts +++ b/packages/plugins/typescript/apollo-angular/tests/apollo-angular.spec.ts @@ -736,6 +736,152 @@ describe('Apollo Angular', () => { export class ApolloAngularSDK { `); }); + + it('should combine variables into options for apolloAngularVersion 12+', async () => { + const modifiedSchema = extendSchema(schema, addToSchema); + const myFeed = gql(` + query MyFeed { + feed { + id + } + } + `); + const docs = [{ location: '', document: myFeed }]; + const content = (await plugin( + modifiedSchema, + docs, + { + sdkClass: true, + apolloAngularVersion: 12, + }, + { + outputFile: 'graphql.ts', + }, + )) as Types.ComplexPluginOutput; + + // For v12+, use type aliases with Apollo.Apollo namespace types + expect(content.content).toBeSimilarStringTo(` + type Omit = Pick>; + + type WatchQueryOptionsAlone = Omit, 'query' | 'variables'> + + type QueryOptionsAlone = Omit, 'query' | 'variables'>`); + + // For v12+, SDK methods should keep same signature but combine variables into options internally + // Watch method should have explicit return type for proper type inference + expect(content.content).toBeSimilarStringTo(` + myFeed(variables?: MyFeedQueryVariables, options?: QueryOptionsAlone) { + return this.myFeedGql.fetch({ ...options, variables }) + } + + myFeedWatch(variables?: MyFeedQueryVariables, options?: WatchQueryOptionsAlone): Apollo.QueryRef { + return this.myFeedGql.watch({ ...options, variables }) + } + `); + }); + + it('should combine variables into options for mutations with apolloAngularVersion 12+', async () => { + const modifiedSchema = extendSchema(schema, addToSchema); + const myMutation = gql(` + mutation Update($arg: Int) { + update(arg: $arg) { + id + } + } + `); + const docs = [{ location: '', document: myMutation }]; + const content = (await plugin( + modifiedSchema, + docs, + { + sdkClass: true, + apolloAngularVersion: 12, + }, + { + outputFile: 'graphql.ts', + }, + )) as Types.ComplexPluginOutput; + + // For v12+, use type alias with Apollo.Apollo namespace + expect(content.content).toBeSimilarStringTo(` + type MutationOptionsAlone = Omit, 'mutation' | 'variables'>`); + + // For v12+, SDK methods should combine variables into options internally + expect(content.content).toBeSimilarStringTo(` + update(variables?: UpdateMutationVariables, options?: MutationOptionsAlone) { + return this.updateGql.mutate({ ...options, variables }) + } + `); + }); + + it('should combine variables into options for subscriptions with apolloAngularVersion 12+', async () => { + const modifiedSchema = extendSchema(schema, addToSchema); + const mySubscription = gql(` + subscription MyFeed { + feed { + id + } + } + `); + const docs = [{ location: '', document: mySubscription }]; + const content = (await plugin( + modifiedSchema, + docs, + { + sdkClass: true, + apolloAngularVersion: 12, + }, + { + outputFile: 'graphql.ts', + }, + )) as Types.ComplexPluginOutput; + + // For v12+, use type alias with Apollo.Apollo namespace + expect(content.content).toBeSimilarStringTo(` + type SubscriptionOptionsAlone = Omit, 'query' | 'variables'>`); + + // For v12+, SDK methods should combine variables into options internally + expect(content.content).toBeSimilarStringTo(` + myFeed(variables?: MyFeedSubscriptionVariables, options?: SubscriptionOptionsAlone) { + return this.myFeedGql.subscribe({ ...options, variables }) + } + `); + }); + + it('should use legacy call syntax for apolloAngularVersion below 12', async () => { + const modifiedSchema = extendSchema(schema, addToSchema); + const myFeed = gql(` + query MyFeed { + feed { + id + } + } + `); + const docs = [{ location: '', document: myFeed }]; + const content = (await plugin( + modifiedSchema, + docs, + { + sdkClass: true, + apolloAngularVersion: 11, + }, + { + outputFile: 'graphql.ts', + }, + )) as Types.ComplexPluginOutput; + + // For v11 and below, interfaces should not have OperationVariables constraint + expect(content.content).toBeSimilarStringTo(` + interface WatchQueryOptionsAlone extends Omit, 'query' | 'variables'> {}`); + expect(content.content).not.toContain('OperationVariables'); + + // For v11 and below, SDK methods should pass variables and options separately + expect(content.content).toBeSimilarStringTo(` + myFeed(variables?: MyFeedQueryVariables, options?: QueryOptionsAlone) { + return this.myFeedGql.fetch(variables, options) + } + `); + }); }); describe('near-operation-file', () => {