Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions .changeset/support-apollo-angular-v12.md
Original file line number Diff line number Diff line change
@@ -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.
45 changes: 36 additions & 9 deletions packages/plugins/typescript/apollo-angular/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 <T, V> 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('');
Expand All @@ -407,17 +422,29 @@ ${camelCase(o.node.name.value)}Watch(variables${
hasQueries || hasMutations || hasSubscriptions
? `type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;`
: '';

// 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 <TData, TVariables> order
const watchType = hasQueries
? `interface WatchQueryOptionsAlone<V> extends Omit<ApolloCore.WatchQueryOptions<V>, 'query' | 'variables'> {}`
? isV12Plus
? `type WatchQueryOptionsAlone<T, V extends ApolloCore.OperationVariables> = Omit<Apollo.Apollo.WatchQueryOptions<T, V>, 'query' | 'variables'>`
: `interface WatchQueryOptionsAlone<V> extends Omit<ApolloCore.WatchQueryOptions<V>, 'query' | 'variables'> {}`
: '';
const queryType = hasQueries
? `interface QueryOptionsAlone<V> extends Omit<ApolloCore.QueryOptions<V>, 'query' | 'variables'> {}`
? isV12Plus
? `type QueryOptionsAlone<T, V extends ApolloCore.OperationVariables> = Omit<Apollo.Apollo.QueryOptions<T, V>, 'query' | 'variables'>`
: `interface QueryOptionsAlone<V> extends Omit<ApolloCore.QueryOptions<V>, 'query' | 'variables'> {}`
: '';
const mutationType = hasMutations
? `interface MutationOptionsAlone<T, V> extends Omit<ApolloCore.MutationOptions<T, V>, 'mutation' | 'variables'> {}`
? isV12Plus
? `type MutationOptionsAlone<T, V extends ApolloCore.OperationVariables> = Omit<Apollo.Apollo.MutateOptions<T, V>, 'mutation' | 'variables'>`
: `interface MutationOptionsAlone<T, V> extends Omit<ApolloCore.MutationOptions<T, V>, 'mutation' | 'variables'> {}`
: '';
const subscriptionType = hasSubscriptions
? `interface SubscriptionOptionsAlone<V> extends Omit<ApolloCore.SubscriptionOptions<V>, 'query' | 'variables'> {}`
? isV12Plus
? `type SubscriptionOptionsAlone<T, V extends ApolloCore.OperationVariables> = Omit<Apollo.Apollo.SubscribeOptions<T, V>, 'query' | 'variables'>`
: `interface SubscriptionOptionsAlone<V> extends Omit<ApolloCore.SubscriptionOptions<V>, 'query' | 'variables'> {}`
: '';

const types = [omitType, watchType, queryType, mutationType, subscriptionType]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

type WatchQueryOptionsAlone<T, V extends ApolloCore.OperationVariables> = Omit<Apollo.Apollo.WatchQueryOptions<T, V>, 'query' | 'variables'>

type QueryOptionsAlone<T, V extends ApolloCore.OperationVariables> = Omit<Apollo.Apollo.QueryOptions<T, V>, '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<MyFeedQuery, MyFeedQueryVariables>) {
return this.myFeedGql.fetch({ ...options, variables })
}

myFeedWatch(variables?: MyFeedQueryVariables, options?: WatchQueryOptionsAlone<MyFeedQuery, MyFeedQueryVariables>): Apollo.QueryRef<MyFeedQuery, MyFeedQueryVariables> {
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<T, V extends ApolloCore.OperationVariables> = Omit<Apollo.Apollo.MutateOptions<T, V>, 'mutation' | 'variables'>`);

// For v12+, SDK methods should combine variables into options internally
expect(content.content).toBeSimilarStringTo(`
update(variables?: UpdateMutationVariables, options?: MutationOptionsAlone<UpdateMutation, UpdateMutationVariables>) {
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<T, V extends ApolloCore.OperationVariables> = Omit<Apollo.Apollo.SubscribeOptions<T, V>, 'query' | 'variables'>`);

// For v12+, SDK methods should combine variables into options internally
expect(content.content).toBeSimilarStringTo(`
myFeed(variables?: MyFeedSubscriptionVariables, options?: SubscriptionOptionsAlone<MyFeedSubscription, MyFeedSubscriptionVariables>) {
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<V> extends Omit<ApolloCore.WatchQueryOptions<V>, '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<MyFeedQueryVariables>) {
return this.myFeedGql.fetch(variables, options)
}
`);
});
});

describe('near-operation-file', () => {
Expand Down