From ef1424a98c2b2679fc684ad10f7003ce23af81c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Jan 2026 10:48:32 +0000 Subject: [PATCH 1/5] fix(typescript-apollo-angular): add OperationVariables constraint for v12+ For apollo-angular v12 and above, add the `extends ApolloCore.OperationVariables` constraint to the generic type parameter V in the SDK type interfaces (WatchQueryOptionsAlone, QueryOptionsAlone, MutationOptionsAlone, SubscriptionOptionsAlone). This change is conditional and only applies when `apolloAngularVersion: 12` or higher is configured, maintaining backward compatibility with earlier versions. Fixes #1354 --- .../typescript/apollo-angular/src/visitor.ts | 17 ++- .../tests/apollo-angular.spec.ts | 109 ++++++++++++++++++ 2 files changed, 122 insertions(+), 4 deletions(-) diff --git a/packages/plugins/typescript/apollo-angular/src/visitor.ts b/packages/plugins/typescript/apollo-angular/src/visitor.ts index ba266c0a4d..9df7fc3c60 100644 --- a/packages/plugins/typescript/apollo-angular/src/visitor.ts +++ b/packages/plugins/typescript/apollo-angular/src/visitor.ts @@ -407,17 +407,26 @@ ${camelCase(o.node.name.value)}Watch(variables${ hasQueries || hasMutations || hasSubscriptions ? `type Omit = Pick>;` : ''; + + // For apollo-angular v12+, add OperationVariables constraint to fix type compatibility + const vConstraint = + this.config.apolloAngularVersion >= 12 ? '' : ''; + const tvConstraint = + this.config.apolloAngularVersion >= 12 + ? '' + : ''; + const watchType = hasQueries - ? `interface WatchQueryOptionsAlone extends Omit, 'query' | 'variables'> {}` + ? `interface WatchQueryOptionsAlone${vConstraint} extends Omit, 'query' | 'variables'> {}` : ''; const queryType = hasQueries - ? `interface QueryOptionsAlone extends Omit, 'query' | 'variables'> {}` + ? `interface QueryOptionsAlone${vConstraint} extends Omit, 'query' | 'variables'> {}` : ''; const mutationType = hasMutations - ? `interface MutationOptionsAlone extends Omit, 'mutation' | 'variables'> {}` + ? `interface MutationOptionsAlone${tvConstraint} extends Omit, 'mutation' | 'variables'> {}` : ''; const subscriptionType = hasSubscriptions - ? `interface SubscriptionOptionsAlone extends Omit, 'query' | 'variables'> {}` + ? `interface SubscriptionOptionsAlone${vConstraint} 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..e5fa725fac 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,115 @@ describe('Apollo Angular', () => { export class ApolloAngularSDK { `); }); + + it('should add OperationVariables constraint 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; + + expect(content.content).toBeSimilarStringTo(` + type Omit = Pick>; + + interface WatchQueryOptionsAlone extends Omit, 'query' | 'variables'> {} + + interface QueryOptionsAlone extends Omit, 'query' | 'variables'> {}`); + }); + + it('should add OperationVariables constraint 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; + + expect(content.content).toBeSimilarStringTo(` + interface MutationOptionsAlone extends Omit, 'mutation' | 'variables'> {}`); + }); + + it('should add OperationVariables constraint 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; + + expect(content.content).toBeSimilarStringTo(` + interface SubscriptionOptionsAlone extends Omit, 'query' | 'variables'> {}`); + }); + + it('should NOT add OperationVariables constraint 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; + + expect(content.content).toBeSimilarStringTo(` + interface WatchQueryOptionsAlone extends Omit, 'query' | 'variables'> {}`); + expect(content.content).not.toContain('OperationVariables'); + }); }); describe('near-operation-file', () => { From d8e3de1e0983cb0fd7570a5e7e723ddce0a216e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Jan 2026 10:53:55 +0000 Subject: [PATCH 2/5] feat(typescript-apollo-angular): support combined parameter syntax for v12+ Apollo-angular v12 introduced a breaking change where all method parameters must be combined into a single options object, with variables nested within the `variables` property. For apolloAngularVersion >= 12: - SDK methods now take a single options parameter instead of separate variables and options parameters - Interface types only omit 'query'/'mutation' (not 'variables') since variables are now part of the options object Example change for generated SDK methods: - Old: myQuery.fetch(variables, options) - New: myQuery.fetch(options) where options.variables contains the variables Fixes #1354 --- .../typescript/apollo-angular/src/visitor.ts | 52 ++++++++---- .../tests/apollo-angular.spec.ts | 82 +++++++++++++++++-- 2 files changed, 109 insertions(+), 25 deletions(-) diff --git a/packages/plugins/typescript/apollo-angular/src/visitor.ts b/packages/plugins/typescript/apollo-angular/src/visitor.ts index 9df7fc3c60..b8b90b6e27 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; @@ -365,23 +367,40 @@ export class ApolloAngularVisitor extends ClientSideBaseVisitor< ? `${o.operationType}OptionsAlone<${operationResultType}, ${operationVariablesTypes}>` : `${o.operationType}OptionsAlone<${operationVariablesTypes}>`; - const method = ` + // For v12+, use combined parameter syntax where variables are nested in options + let method: string; + if (isV12Plus) { + method = ` +${camelCase(o.node.name.value)}(options${optionalVariables ? '?' : ''}: ${options}) { + return this.${camelCase(o.serviceName)}.${actionType(o.operationType)}(options) +}`; + } else { + method = ` ${camelCase(o.node.name.value)}(variables${ - optionalVariables ? '?' : '' - }: ${operationVariablesTypes}, options?: ${options}) { + optionalVariables ? '?' : '' + }: ${operationVariablesTypes}, options?: ${options}) { return this.${camelCase(o.serviceName)}.${actionType(o.operationType)}(variables, options) }`; + } let watchMethod: string; if (o.operationType === 'Query') { - watchMethod = ` + if (isV12Plus) { + watchMethod = ` + +${camelCase(o.node.name.value)}Watch(options${optionalVariables ? '?' : ''}: WatchQueryOptionsAlone<${operationVariablesTypes}>) { + return this.${camelCase(o.serviceName)}.watch(options) +}`; + } else { + watchMethod = ` ${camelCase(o.node.name.value)}Watch(variables${ - optionalVariables ? '?' : '' - }: ${operationVariablesTypes}, options?: WatchQueryOptionsAlone<${operationVariablesTypes}>) { + optionalVariables ? '?' : '' + }: ${operationVariablesTypes}, options?: WatchQueryOptionsAlone<${operationVariablesTypes}>) { return this.${camelCase(o.serviceName)}.watch(variables, options) }`; + } } return [method, watchMethod].join(''); }) @@ -408,25 +427,24 @@ ${camelCase(o.node.name.value)}Watch(variables${ ? `type Omit = Pick>;` : ''; - // For apollo-angular v12+, add OperationVariables constraint to fix type compatibility - const vConstraint = - this.config.apolloAngularVersion >= 12 ? '' : ''; - const tvConstraint = - this.config.apolloAngularVersion >= 12 - ? '' - : ''; + // For apollo-angular v12+, add OperationVariables constraint and only omit 'query'/'mutation' + // (not 'variables') since v12+ uses combined parameter syntax where variables are in options + const vConstraint = isV12Plus ? '' : ''; + const tvConstraint = isV12Plus ? '' : ''; + const omitFields = isV12Plus ? `'query'` : `'query' | 'variables'`; + const omitMutationFields = isV12Plus ? `'mutation'` : `'mutation' | 'variables'`; const watchType = hasQueries - ? `interface WatchQueryOptionsAlone${vConstraint} extends Omit, 'query' | 'variables'> {}` + ? `interface WatchQueryOptionsAlone${vConstraint} extends Omit, ${omitFields}> {}` : ''; const queryType = hasQueries - ? `interface QueryOptionsAlone${vConstraint} extends Omit, 'query' | 'variables'> {}` + ? `interface QueryOptionsAlone${vConstraint} extends Omit, ${omitFields}> {}` : ''; const mutationType = hasMutations - ? `interface MutationOptionsAlone${tvConstraint} extends Omit, 'mutation' | 'variables'> {}` + ? `interface MutationOptionsAlone${tvConstraint} extends Omit, ${omitMutationFields}> {}` : ''; const subscriptionType = hasSubscriptions - ? `interface SubscriptionOptionsAlone${vConstraint} extends Omit, 'query' | 'variables'> {}` + ? `interface SubscriptionOptionsAlone${vConstraint} extends Omit, ${omitFields}> {}` : ''; 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 e5fa725fac..32d98ae9dd 100644 --- a/packages/plugins/typescript/apollo-angular/tests/apollo-angular.spec.ts +++ b/packages/plugins/typescript/apollo-angular/tests/apollo-angular.spec.ts @@ -737,7 +737,7 @@ describe('Apollo Angular', () => { `); }); - it('should add OperationVariables constraint for apolloAngularVersion 12+', async () => { + it('should use combined parameter syntax for apolloAngularVersion 12+', async () => { const modifiedSchema = extendSchema(schema, addToSchema); const myFeed = gql(` query MyFeed { @@ -759,15 +759,27 @@ describe('Apollo Angular', () => { }, )) as Types.ComplexPluginOutput; + // For v12+, interfaces should only omit 'query' (not 'variables') expect(content.content).toBeSimilarStringTo(` type Omit = Pick>; - interface WatchQueryOptionsAlone extends Omit, 'query' | 'variables'> {} + interface WatchQueryOptionsAlone extends Omit, 'query'> {} - interface QueryOptionsAlone extends Omit, 'query' | 'variables'> {}`); + interface QueryOptionsAlone extends Omit, 'query'> {}`); + + // For v12+, SDK methods should use combined parameter syntax + expect(content.content).toBeSimilarStringTo(` + myFeed(options?: QueryOptionsAlone) { + return this.myFeedGql.fetch(options) + } + + myFeedWatch(options?: WatchQueryOptionsAlone) { + return this.myFeedGql.watch(options) + } + `); }); - it('should add OperationVariables constraint for mutations with apolloAngularVersion 12+', async () => { + it('should use combined parameter syntax for mutations with apolloAngularVersion 12+', async () => { const modifiedSchema = extendSchema(schema, addToSchema); const myMutation = gql(` mutation Update($arg: Int) { @@ -789,11 +801,19 @@ describe('Apollo Angular', () => { }, )) as Types.ComplexPluginOutput; + // For v12+, interface should only omit 'mutation' (not 'variables') expect(content.content).toBeSimilarStringTo(` - interface MutationOptionsAlone extends Omit, 'mutation' | 'variables'> {}`); + interface MutationOptionsAlone extends Omit, 'mutation'> {}`); + + // For v12+, SDK methods should use combined parameter syntax + expect(content.content).toBeSimilarStringTo(` + update(options?: MutationOptionsAlone) { + return this.updateGql.mutate(options) + } + `); }); - it('should add OperationVariables constraint for subscriptions with apolloAngularVersion 12+', async () => { + it('should use combined parameter syntax for subscriptions with apolloAngularVersion 12+', async () => { const modifiedSchema = extendSchema(schema, addToSchema); const mySubscription = gql(` subscription MyFeed { @@ -815,11 +835,19 @@ describe('Apollo Angular', () => { }, )) as Types.ComplexPluginOutput; + // For v12+, interface should only omit 'query' (not 'variables') + expect(content.content).toBeSimilarStringTo(` + interface SubscriptionOptionsAlone extends Omit, 'query'> {}`); + + // For v12+, SDK methods should use combined parameter syntax expect(content.content).toBeSimilarStringTo(` - interface SubscriptionOptionsAlone extends Omit, 'query' | 'variables'> {}`); + myFeed(options?: SubscriptionOptionsAlone) { + return this.myFeedGql.subscribe(options) + } + `); }); - it('should NOT add OperationVariables constraint for apolloAngularVersion below 12', async () => { + it('should use legacy two-parameter syntax for apolloAngularVersion below 12', async () => { const modifiedSchema = extendSchema(schema, addToSchema); const myFeed = gql(` query MyFeed { @@ -841,9 +869,47 @@ describe('Apollo Angular', () => { }, )) as Types.ComplexPluginOutput; + // For v11 and below, interfaces should omit both 'query' and 'variables' expect(content.content).toBeSimilarStringTo(` interface WatchQueryOptionsAlone extends Omit, 'query' | 'variables'> {}`); expect(content.content).not.toContain('OperationVariables'); + + // For v11 and below, SDK methods should use legacy two-parameter syntax + expect(content.content).toBeSimilarStringTo(` + myFeed(variables?: MyFeedQueryVariables, options?: QueryOptionsAlone) { + return this.myFeedGql.fetch(variables, options) + } + `); + }); + + it('should require options parameter for required variables with apolloAngularVersion 12+', async () => { + const modifiedSchema = extendSchema(schema, addToSchema); + const myFeed = gql(` + query MyFeed($type: FeedType!) { + feed(type: $type) { + id + } + } + `); + const docs = [{ location: '', document: myFeed }]; + const content = (await plugin( + modifiedSchema, + docs, + { + sdkClass: true, + apolloAngularVersion: 12, + }, + { + outputFile: 'graphql.ts', + }, + )) as Types.ComplexPluginOutput; + + // For v12+ with required variables, options parameter should be required (no ?) + expect(content.content).toBeSimilarStringTo(` + myFeed(options: QueryOptionsAlone) { + return this.myFeedGql.fetch(options) + } + `); }); }); From 54e6add4dd7cdbcda89b1fe473fbe4087db886bf Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Jan 2026 11:02:26 +0000 Subject: [PATCH 3/5] fix(typescript-apollo-angular): keep same API but combine args internally for v12+ Maintains backward-compatible SDK method signatures (variables, options) while internally combining them into a single options object when calling apollo-angular v12+ methods. For apolloAngularVersion >= 12: - SDK method signatures remain: myQuery(variables, options) - Internal call becomes: this.service.fetch({ ...options, variables }) For apolloAngularVersion < 12: - Everything remains unchanged: this.service.fetch(variables, options) This ensures no breaking changes to consumer code while supporting the apollo-angular v12+ combined parameter syntax requirement. Fixes #1354 --- .../typescript/apollo-angular/src/visitor.ts | 51 +++++------- .../tests/apollo-angular.spec.ts | 78 ++++++------------- 2 files changed, 42 insertions(+), 87 deletions(-) diff --git a/packages/plugins/typescript/apollo-angular/src/visitor.ts b/packages/plugins/typescript/apollo-angular/src/visitor.ts index b8b90b6e27..e23b5ae407 100644 --- a/packages/plugins/typescript/apollo-angular/src/visitor.ts +++ b/packages/plugins/typescript/apollo-angular/src/visitor.ts @@ -367,40 +367,28 @@ export class ApolloAngularVisitor extends ClientSideBaseVisitor< ? `${o.operationType}OptionsAlone<${operationResultType}, ${operationVariablesTypes}>` : `${o.operationType}OptionsAlone<${operationVariablesTypes}>`; - // For v12+, use combined parameter syntax where variables are nested in options - let method: string; - if (isV12Plus) { - method = ` -${camelCase(o.node.name.value)}(options${optionalVariables ? '?' : ''}: ${options}) { - return this.${camelCase(o.serviceName)}.${actionType(o.operationType)}(options) -}`; - } else { - method = ` + // For v12+, keep same signature but 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) + optionalVariables ? '?' : '' + }: ${operationVariablesTypes}, options?: ${options}) { + return this.${camelCase(o.serviceName)}.${actionType(o.operationType)}(${callArgs}) }`; - } let watchMethod: string; if (o.operationType === 'Query') { - if (isV12Plus) { - watchMethod = ` - -${camelCase(o.node.name.value)}Watch(options${optionalVariables ? '?' : ''}: WatchQueryOptionsAlone<${operationVariablesTypes}>) { - return this.${camelCase(o.serviceName)}.watch(options) -}`; - } else { - watchMethod = ` + watchMethod = ` ${camelCase(o.node.name.value)}Watch(variables${ - optionalVariables ? '?' : '' - }: ${operationVariablesTypes}, options?: WatchQueryOptionsAlone<${operationVariablesTypes}>) { - return this.${camelCase(o.serviceName)}.watch(variables, options) + optionalVariables ? '?' : '' + }: ${operationVariablesTypes}, options?: WatchQueryOptionsAlone<${operationVariablesTypes}>) { + return this.${camelCase(o.serviceName)}.watch(${callArgs}) }`; - } } return [method, watchMethod].join(''); }) @@ -427,24 +415,21 @@ ${camelCase(o.node.name.value)}Watch(variables${ ? `type Omit = Pick>;` : ''; - // For apollo-angular v12+, add OperationVariables constraint and only omit 'query'/'mutation' - // (not 'variables') since v12+ uses combined parameter syntax where variables are in options + // For apollo-angular v12+, add OperationVariables constraint for type compatibility const vConstraint = isV12Plus ? '' : ''; const tvConstraint = isV12Plus ? '' : ''; - const omitFields = isV12Plus ? `'query'` : `'query' | 'variables'`; - const omitMutationFields = isV12Plus ? `'mutation'` : `'mutation' | 'variables'`; const watchType = hasQueries - ? `interface WatchQueryOptionsAlone${vConstraint} extends Omit, ${omitFields}> {}` + ? `interface WatchQueryOptionsAlone${vConstraint} extends Omit, 'query' | 'variables'> {}` : ''; const queryType = hasQueries - ? `interface QueryOptionsAlone${vConstraint} extends Omit, ${omitFields}> {}` + ? `interface QueryOptionsAlone${vConstraint} extends Omit, 'query' | 'variables'> {}` : ''; const mutationType = hasMutations - ? `interface MutationOptionsAlone${tvConstraint} extends Omit, ${omitMutationFields}> {}` + ? `interface MutationOptionsAlone${tvConstraint} extends Omit, 'mutation' | 'variables'> {}` : ''; const subscriptionType = hasSubscriptions - ? `interface SubscriptionOptionsAlone${vConstraint} extends Omit, ${omitFields}> {}` + ? `interface SubscriptionOptionsAlone${vConstraint} 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 32d98ae9dd..75a70351c6 100644 --- a/packages/plugins/typescript/apollo-angular/tests/apollo-angular.spec.ts +++ b/packages/plugins/typescript/apollo-angular/tests/apollo-angular.spec.ts @@ -737,7 +737,7 @@ describe('Apollo Angular', () => { `); }); - it('should use combined parameter syntax for apolloAngularVersion 12+', async () => { + it('should combine variables into options for apolloAngularVersion 12+', async () => { const modifiedSchema = extendSchema(schema, addToSchema); const myFeed = gql(` query MyFeed { @@ -759,27 +759,27 @@ describe('Apollo Angular', () => { }, )) as Types.ComplexPluginOutput; - // For v12+, interfaces should only omit 'query' (not 'variables') + // For v12+, interfaces should have OperationVariables constraint expect(content.content).toBeSimilarStringTo(` type Omit = Pick>; - interface WatchQueryOptionsAlone extends Omit, 'query'> {} + interface WatchQueryOptionsAlone extends Omit, 'query' | 'variables'> {} - interface QueryOptionsAlone extends Omit, 'query'> {}`); + interface QueryOptionsAlone extends Omit, 'query' | 'variables'> {}`); - // For v12+, SDK methods should use combined parameter syntax + // For v12+, SDK methods should keep same signature but combine variables into options internally expect(content.content).toBeSimilarStringTo(` - myFeed(options?: QueryOptionsAlone) { - return this.myFeedGql.fetch(options) + myFeed(variables?: MyFeedQueryVariables, options?: QueryOptionsAlone) { + return this.myFeedGql.fetch({ ...options, variables }) } - myFeedWatch(options?: WatchQueryOptionsAlone) { - return this.myFeedGql.watch(options) + myFeedWatch(variables?: MyFeedQueryVariables, options?: WatchQueryOptionsAlone) { + return this.myFeedGql.watch({ ...options, variables }) } `); }); - it('should use combined parameter syntax for mutations with apolloAngularVersion 12+', async () => { + it('should combine variables into options for mutations with apolloAngularVersion 12+', async () => { const modifiedSchema = extendSchema(schema, addToSchema); const myMutation = gql(` mutation Update($arg: Int) { @@ -801,19 +801,19 @@ describe('Apollo Angular', () => { }, )) as Types.ComplexPluginOutput; - // For v12+, interface should only omit 'mutation' (not 'variables') + // For v12+, interface should have OperationVariables constraint expect(content.content).toBeSimilarStringTo(` - interface MutationOptionsAlone extends Omit, 'mutation'> {}`); + interface MutationOptionsAlone extends Omit, 'mutation' | 'variables'> {}`); - // For v12+, SDK methods should use combined parameter syntax + // For v12+, SDK methods should combine variables into options internally expect(content.content).toBeSimilarStringTo(` - update(options?: MutationOptionsAlone) { - return this.updateGql.mutate(options) + update(variables?: UpdateMutationVariables, options?: MutationOptionsAlone) { + return this.updateGql.mutate({ ...options, variables }) } `); }); - it('should use combined parameter syntax for subscriptions with apolloAngularVersion 12+', async () => { + it('should combine variables into options for subscriptions with apolloAngularVersion 12+', async () => { const modifiedSchema = extendSchema(schema, addToSchema); const mySubscription = gql(` subscription MyFeed { @@ -835,19 +835,19 @@ describe('Apollo Angular', () => { }, )) as Types.ComplexPluginOutput; - // For v12+, interface should only omit 'query' (not 'variables') + // For v12+, interface should have OperationVariables constraint expect(content.content).toBeSimilarStringTo(` - interface SubscriptionOptionsAlone extends Omit, 'query'> {}`); + interface SubscriptionOptionsAlone extends Omit, 'query' | 'variables'> {}`); - // For v12+, SDK methods should use combined parameter syntax + // For v12+, SDK methods should combine variables into options internally expect(content.content).toBeSimilarStringTo(` - myFeed(options?: SubscriptionOptionsAlone) { - return this.myFeedGql.subscribe(options) + myFeed(variables?: MyFeedSubscriptionVariables, options?: SubscriptionOptionsAlone) { + return this.myFeedGql.subscribe({ ...options, variables }) } `); }); - it('should use legacy two-parameter syntax for apolloAngularVersion below 12', async () => { + it('should use legacy call syntax for apolloAngularVersion below 12', async () => { const modifiedSchema = extendSchema(schema, addToSchema); const myFeed = gql(` query MyFeed { @@ -869,48 +869,18 @@ describe('Apollo Angular', () => { }, )) as Types.ComplexPluginOutput; - // For v11 and below, interfaces should omit both 'query' and 'variables' + // 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 use legacy two-parameter syntax + // 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) } `); }); - - it('should require options parameter for required variables with apolloAngularVersion 12+', async () => { - const modifiedSchema = extendSchema(schema, addToSchema); - const myFeed = gql(` - query MyFeed($type: FeedType!) { - feed(type: $type) { - id - } - } - `); - const docs = [{ location: '', document: myFeed }]; - const content = (await plugin( - modifiedSchema, - docs, - { - sdkClass: true, - apolloAngularVersion: 12, - }, - { - outputFile: 'graphql.ts', - }, - )) as Types.ComplexPluginOutput; - - // For v12+ with required variables, options parameter should be required (no ?) - expect(content.content).toBeSimilarStringTo(` - myFeed(options: QueryOptionsAlone) { - return this.myFeedGql.fetch(options) - } - `); - }); }); describe('near-operation-file', () => { From bc00d118fdd916eaa6b8bf4ba4353f738964b9aa Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Jan 2026 11:11:54 +0000 Subject: [PATCH 4/5] chore: add changeset for apollo-angular v12+ support --- .changeset/support-apollo-angular-v12.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/support-apollo-angular-v12.md 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. From 738863f8c7af6dcaceecfccd392c302a06f9d6c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Jan 2026 14:39:17 +0000 Subject: [PATCH 5/5] fix(typescript-apollo-angular): use Apollo.Apollo namespace types for v12+ For apollo-angular v12+ with Apollo Client 4: - Use type aliases with Apollo.Apollo namespace types instead of ApolloCore - All OptionsAlone types have signature for proper type inference - Watch methods have explicit return type (Apollo.QueryRef) Type mappings for v12+: - WatchQueryOptionsAlone -> Apollo.Apollo.WatchQueryOptions - QueryOptionsAlone -> Apollo.Apollo.QueryOptions - MutationOptionsAlone -> Apollo.Apollo.MutateOptions - SubscriptionOptionsAlone -> Apollo.Apollo.SubscribeOptions Fixes #1354 --- .../typescript/apollo-angular/src/visitor.ts | 45 ++++++++++++------- .../tests/apollo-angular.spec.ts | 21 ++++----- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/packages/plugins/typescript/apollo-angular/src/visitor.ts b/packages/plugins/typescript/apollo-angular/src/visitor.ts index e23b5ae407..2e886e69cb 100644 --- a/packages/plugins/typescript/apollo-angular/src/visitor.ts +++ b/packages/plugins/typescript/apollo-angular/src/visitor.ts @@ -362,15 +362,15 @@ 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+, keep same signature but combine variables into options when calling apollo-angular - const callArgs = isV12Plus - ? '{ ...options, variables }' - : 'variables, options'; + // 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${ @@ -382,11 +382,19 @@ ${camelCase(o.node.name.value)}(variables${ 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}>) { + }: ${operationVariablesTypes}, options?: ${watchOptions})${returnType} { return this.${camelCase(o.serviceName)}.watch(${callArgs}) }`; } @@ -415,21 +423,28 @@ ${camelCase(o.node.name.value)}Watch(variables${ ? `type Omit = Pick>;` : ''; - // For apollo-angular v12+, add OperationVariables constraint for type compatibility - const vConstraint = isV12Plus ? '' : ''; - const tvConstraint = isV12Plus ? '' : ''; - + // 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${vConstraint} extends Omit, 'query' | 'variables'> {}` + ? isV12Plus + ? `type WatchQueryOptionsAlone = Omit, 'query' | 'variables'>` + : `interface WatchQueryOptionsAlone extends Omit, 'query' | 'variables'> {}` : ''; const queryType = hasQueries - ? `interface QueryOptionsAlone${vConstraint} extends Omit, 'query' | 'variables'> {}` + ? isV12Plus + ? `type QueryOptionsAlone = Omit, 'query' | 'variables'>` + : `interface QueryOptionsAlone extends Omit, 'query' | 'variables'> {}` : ''; const mutationType = hasMutations - ? `interface MutationOptionsAlone${tvConstraint} extends Omit, 'mutation' | 'variables'> {}` + ? isV12Plus + ? `type MutationOptionsAlone = Omit, 'mutation' | 'variables'>` + : `interface MutationOptionsAlone extends Omit, 'mutation' | 'variables'> {}` : ''; const subscriptionType = hasSubscriptions - ? `interface SubscriptionOptionsAlone${vConstraint} 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 75a70351c6..1dd99972cb 100644 --- a/packages/plugins/typescript/apollo-angular/tests/apollo-angular.spec.ts +++ b/packages/plugins/typescript/apollo-angular/tests/apollo-angular.spec.ts @@ -759,21 +759,22 @@ describe('Apollo Angular', () => { }, )) as Types.ComplexPluginOutput; - // For v12+, interfaces should have OperationVariables constraint + // For v12+, use type aliases with Apollo.Apollo namespace types expect(content.content).toBeSimilarStringTo(` type Omit = Pick>; - interface WatchQueryOptionsAlone extends Omit, 'query' | 'variables'> {} + type WatchQueryOptionsAlone = Omit, 'query' | 'variables'> - interface QueryOptionsAlone extends 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) { + myFeed(variables?: MyFeedQueryVariables, options?: QueryOptionsAlone) { return this.myFeedGql.fetch({ ...options, variables }) } - myFeedWatch(variables?: MyFeedQueryVariables, options?: WatchQueryOptionsAlone) { + myFeedWatch(variables?: MyFeedQueryVariables, options?: WatchQueryOptionsAlone): Apollo.QueryRef { return this.myFeedGql.watch({ ...options, variables }) } `); @@ -801,9 +802,9 @@ describe('Apollo Angular', () => { }, )) as Types.ComplexPluginOutput; - // For v12+, interface should have OperationVariables constraint + // For v12+, use type alias with Apollo.Apollo namespace expect(content.content).toBeSimilarStringTo(` - interface MutationOptionsAlone extends Omit, 'mutation' | 'variables'> {}`); + type MutationOptionsAlone = Omit, 'mutation' | 'variables'>`); // For v12+, SDK methods should combine variables into options internally expect(content.content).toBeSimilarStringTo(` @@ -835,13 +836,13 @@ describe('Apollo Angular', () => { }, )) as Types.ComplexPluginOutput; - // For v12+, interface should have OperationVariables constraint + // For v12+, use type alias with Apollo.Apollo namespace expect(content.content).toBeSimilarStringTo(` - interface SubscriptionOptionsAlone extends Omit, 'query' | 'variables'> {}`); + type SubscriptionOptionsAlone = Omit, 'query' | 'variables'>`); // For v12+, SDK methods should combine variables into options internally expect(content.content).toBeSimilarStringTo(` - myFeed(variables?: MyFeedSubscriptionVariables, options?: SubscriptionOptionsAlone) { + myFeed(variables?: MyFeedSubscriptionVariables, options?: SubscriptionOptionsAlone) { return this.myFeedGql.subscribe({ ...options, variables }) } `);