diff --git a/.changeset/odd-pumpkins-care.md b/.changeset/odd-pumpkins-care.md new file mode 100644 index 00000000000..d2ecc5b0e2e --- /dev/null +++ b/.changeset/odd-pumpkins-care.md @@ -0,0 +1,5 @@ +--- +'@apollo/client': patch +--- + +Fixes a bug in `BatchHttpLink` that removed variables from all requests by default. diff --git a/src/link/batch-http/__tests__/batchHttpLink.ts b/src/link/batch-http/__tests__/batchHttpLink.ts index 88a99c1fbaf..208ae6f9b43 100644 --- a/src/link/batch-http/__tests__/batchHttpLink.ts +++ b/src/link/batch-http/__tests__/batchHttpLink.ts @@ -352,6 +352,62 @@ describe('SharedHttpTest', () => { ); }); + itAsync('strips unused variables, respecting nested fragments', (resolve, reject) => { + const link = createHttpLink({ uri: '/data' }); + + const query = gql` + query PEOPLE ( + $declaredAndUsed: String, + $declaredButUnused: Int, + ) { + people( + surprise: $undeclared, + noSurprise: $declaredAndUsed, + ) { + ... on Doctor { + specialty(var: $usedByInlineFragment) + } + ...LawyerFragment + } + } + fragment LawyerFragment on Lawyer { + caseCount(var: $usedByNamedFragment) + } + `; + + const variables = { + unused: 'strip', + declaredButUnused: 'strip', + declaredAndUsed: 'keep', + undeclared: 'keep', + usedByInlineFragment: 'keep', + usedByNamedFragment: 'keep', + }; + + execute(link, { + query, + variables, + }).subscribe({ + next: makeCallback(resolve, reject, () => { + const [uri, options] = fetchMock.lastCall()!; + const { method, body } = options!; + expect(JSON.parse(body as string)).toEqual([{ + operationName: "PEOPLE", + query: print(query), + variables: { + declaredAndUsed: 'keep', + undeclared: 'keep', + usedByInlineFragment: 'keep', + usedByNamedFragment: 'keep', + }, + }]); + expect(method).toBe('POST'); + expect(uri).toBe('/data'); + }), + error: error => reject(error), + }); + }); + itAsync('unsubscribes without calling subscriber', (resolve, reject) => { const link = createHttpLink({ uri: '/data' }); const observable = execute(link, { diff --git a/src/link/batch-http/batchHttpLink.ts b/src/link/batch-http/batchHttpLink.ts index 380a578ada3..04753f9bf69 100644 --- a/src/link/batch-http/batchHttpLink.ts +++ b/src/link/batch-http/batchHttpLink.ts @@ -132,7 +132,10 @@ export class BatchHttpLink extends ApolloLink { ); if (result.body.variables && !includeUnusedVariables) { - result.body.variables = filterOperationVariables(result.body, operation); + result.body.variables = filterOperationVariables( + result.body.variables, + operation.query + ); } return result; diff --git a/src/link/http/createHttpLink.ts b/src/link/http/createHttpLink.ts index 48a9170cf01..f0858cc0f17 100644 --- a/src/link/http/createHttpLink.ts +++ b/src/link/http/createHttpLink.ts @@ -116,7 +116,7 @@ export const createHttpLink = (linkOptions: HttpOptions = {}) => { ); if (body.variables && !includeUnusedVariables) { - body.variables = filterOperationVariables(body.variables, operation); + body.variables = filterOperationVariables(body.variables, operation.query); } let controller: any; diff --git a/src/link/utils/__tests__/filterOperationVariables.ts b/src/link/utils/__tests__/filterOperationVariables.ts index 5a4889a3944..3b54ef38cd4 100644 --- a/src/link/utils/__tests__/filterOperationVariables.ts +++ b/src/link/utils/__tests__/filterOperationVariables.ts @@ -1,6 +1,5 @@ import gql from 'graphql-tag'; import { filterOperationVariables } from '../filterOperationVariables'; -import { createOperation } from '../createOperation'; const sampleQueryWithVariables = gql` query MyQuery($a: Int!) { @@ -23,7 +22,7 @@ describe('filterOperationVariables', () => { const variables = { a: 1, b: 2, c: 3 }; const result = filterOperationVariables( variables, - createOperation({}, { query: sampleQueryWithoutVariables, variables }) + sampleQueryWithoutVariables ); expect(result).toEqual({}); }); @@ -32,7 +31,7 @@ describe('filterOperationVariables', () => { const variables = { a: 1, b: 2, c: 3 }; const result = filterOperationVariables( variables, - createOperation({}, { query: sampleQueryWithVariables, variables }) + sampleQueryWithVariables ); expect(result).toEqual({ a: 1 }); }); diff --git a/src/link/utils/filterOperationVariables.ts b/src/link/utils/filterOperationVariables.ts index 8f1b9528903..5d35a2d6e58 100644 --- a/src/link/utils/filterOperationVariables.ts +++ b/src/link/utils/filterOperationVariables.ts @@ -1,23 +1,27 @@ -import type { VariableDefinitionNode} from "graphql"; -import { visit } from "graphql"; +import type { VariableDefinitionNode, DocumentNode } from 'graphql'; +import { visit } from 'graphql'; -import type { Operation } from "../core"; - -export function filterOperationVariables(variables: Record, operation: Operation) { +export function filterOperationVariables( + variables: Record, + query: DocumentNode +) { const result = { ...variables }; const unusedNames = new Set(Object.keys(variables)); - visit(operation.query, { + visit(query, { Variable(node, _key, parent) { // A variable type definition at the top level of a query is not // enough to silence server-side errors about the variable being // unused, so variable definitions do not count as usage. // https://spec.graphql.org/draft/#sec-All-Variables-Used - if (parent && (parent as VariableDefinitionNode).kind !== 'VariableDefinition') { + if ( + parent && + (parent as VariableDefinitionNode).kind !== 'VariableDefinition' + ) { unusedNames.delete(node.name.value); } }, }); - unusedNames.forEach(name => { + unusedNames.forEach((name) => { delete result![name]; }); return result;