Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce the removeTypenameFromVariables link to remove __typename from variables #10853

Merged
merged 24 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bf297c7
Add option to omitDeep to allow some paths to be kept
jerelmiller May 9, 2023
c8db491
Allow typename to be kept with `keep` option
jerelmiller May 9, 2023
84a5f94
Create removeTypenameFromVariables link that handles excluding scalar…
jerelmiller May 9, 2023
62bd9e3
Add `excludeScalarPaths` to removeTypenameFromVariables options
jerelmiller May 9, 2023
2a02f4b
Combine excludeScalars and excludeScalarPaths into single excludeScal…
jerelmiller May 10, 2023
6e86608
Small refactor to destructure variables from result
jerelmiller May 10, 2023
9c990ca
Minor tweaks to test names
jerelmiller May 10, 2023
269cb55
Add additional test to ensure mixed forms work well
jerelmiller May 10, 2023
d0fb068
Refactor collectPaths for clarity and move out of link
jerelmiller May 10, 2023
df99d13
Add clarifying comments
jerelmiller May 10, 2023
7b53b02
Export RemoveTypenameFromVariablesOptions
jerelmiller May 10, 2023
d0f85b9
Add remove-typename link as an entry point
jerelmiller May 10, 2023
04896a2
Undo automatic stripping of __typename from HttpLink and GraphQLWsLin…
jerelmiller May 10, 2023
0065bc5
Add additional test for `keep` option to stripTypename tests
jerelmiller May 10, 2023
c5c1a53
Update exports test to include new link
jerelmiller May 10, 2023
717c4e4
Add changeset
jerelmiller May 10, 2023
3167fd9
Minor refactor to simplify collectPaths
jerelmiller May 10, 2023
2f39292
Merge branch 'release-3.8' into remove-typename-link
jerelmiller May 10, 2023
ab9d458
Rename `excludeScalars` to `except` and switch to new object-based co…
jerelmiller May 10, 2023
16e9300
Minor update to comment
jerelmiller May 10, 2023
33dd969
Update snapshot tests
jerelmiller May 10, 2023
2a80a40
Switch changeset change from `patch` to `minor`
jerelmiller May 15, 2023
70b8693
Merge remote-tracking branch 'origin/release-3.8' into remove-typenam…
jerelmiller May 15, 2023
b94fbda
Fix lint errors
jerelmiller May 15, 2023
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/silent-eagles-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@apollo/client': patch
jerelmiller marked this conversation as resolved.
Show resolved Hide resolved
---

Introduce the new `removeTypenameFromVariables` link. This link will automatically remove `__typename` fields from `variables` for all operations. This link can be configured to exclude JSON-scalars for scalars that utilize `__typename`.

This change undoes some work from #10724 where `__typename` was automatically stripped for all operations with no configuration. This was determined to be a breaking change and therefore moved into this link.
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ src/utilities/common/__tests__/*
!src/utilities/common/__tests__/omitDeep.ts
!src/utilities/common/__tests__/stripTypename.ts

# Allowed links
!src/link
src/link/*
!src/link/remove-typename

## Allowed React Hooks
!src/react/hooks/
src/react/hooks/*
Expand Down
1 change: 1 addition & 0 deletions config/entryPoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const entryPoints = [
{ dirs: ['link', 'http'] },
{ dirs: ['link', 'persisted-queries'] },
{ dirs: ['link', 'retry'] },
{ dirs: ['link', 'remove-typename'] },
{ dirs: ['link', 'schema'] },
{ dirs: ['link', 'subscriptions'] },
{ dirs: ['link', 'utils'] },
Expand Down
6 changes: 6 additions & 0 deletions src/__tests__/__snapshots__/exports.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ Array [
]
`;

exports[`exports of public entry points @apollo/client/link/remove-typename 1`] = `
Array [
"removeTypenameFromVariables",
]
`;

exports[`exports of public entry points @apollo/client/link/retry 1`] = `
Array [
"RetryLink",
Expand Down
2 changes: 2 additions & 0 deletions src/__tests__/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as linkError from "../link/error";
import * as linkHTTP from "../link/http";
import * as linkPersistedQueries from "../link/persisted-queries";
import * as linkRetry from "../link/retry";
import * as linkRemoveTypename from "../link/remove-typename";
import * as linkSchema from "../link/schema";
import * as linkSubscriptions from "../link/subscriptions";
import * as linkUtils from "../link/utils";
Expand Down Expand Up @@ -56,6 +57,7 @@ describe('exports of public entry points', () => {
check("@apollo/client/link/error", linkError);
check("@apollo/client/link/http", linkHTTP);
check("@apollo/client/link/persisted-queries", linkPersistedQueries);
check("@apollo/client/link/remove-typename", linkRemoveTypename);
check("@apollo/client/link/retry", linkRetry);
check("@apollo/client/link/schema", linkSchema);
check("@apollo/client/link/subscriptions", linkSubscriptions);
Expand Down
256 changes: 0 additions & 256 deletions src/link/http/__tests__/HttpLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1111,262 +1111,6 @@ describe('HttpLink', () => {
new Error('HttpLink: Trying to send a client-only query to the server. To send to the server, ensure a non-client field is added to the query or set the `transformOptions.removeClientFields` option to `true`.')
);
});

it('strips __typename from object argument when sending a mutation', async () => {
fetchMock.mock('https://example.com/graphql', {
status: 200,
body: JSON.stringify({
data: {
__typename: 'Mutation',
updateTodo: {
__typename: 'Todo',
id: 1,
name: 'Take out trash',
completed: true
}
}
}),
headers: { 'content-type': 'application/json' }
});

const query = gql`
mutation UpdateTodo($todo: TodoInput!) {
updateTodo(todo: $todo) {
id
name
completed
}
}
`;

const link = createHttpLink({ uri: 'https://example.com/graphql' });

const todo = {
__typename: 'Todo',
id: 1,
name: 'Take out trash',
completed: true,
}

await new Promise((resolve, reject) => {
execute(link, { query, variables: { todo } }).subscribe({
next: resolve,
error: reject
});
});

const [, options] = fetchMock.lastCall()!;
const { body } = options!

expect(JSON.parse(body!.toString())).toEqual({
operationName: 'UpdateTodo',
query: print(query),
variables: {
todo: {
id: 1,
name: 'Take out trash',
completed: true,
}
}
});
});

it('strips __typename from array argument when sending a mutation', async () => {
fetchMock.mock('https://example.com/graphql', {
status: 200,
body: JSON.stringify({
data: {
__typename: 'Mutation',
updateTodos: [
{
__typename: 'Todo',
id: 1,
name: 'Take out trash',
completed: true
},
{
__typename: 'Todo',
id: 2,
name: 'Clean room',
completed: true
},
]
}
}),
headers: { 'content-type': 'application/json' }
});

const query = gql`
mutation UpdateTodos($todos: [TodoInput!]!) {
updateTodos(todos: $todos) {
id
name
completed
}
}
`;

const link = createHttpLink({ uri: 'https://example.com/graphql' });

const todos = [
{
__typename: 'Todo',
id: 1,
name: 'Take out trash',
completed: true,
},
{
__typename: 'Todo',
id: 2,
name: 'Clean room',
completed: true,
},
];

await new Promise((resolve, reject) => {
execute(link, { query, variables: { todos } }).subscribe({
next: resolve,
error: reject
});
});

const [, options] = fetchMock.lastCall()!;
const { body } = options!

expect(JSON.parse(body!.toString())).toEqual({
operationName: 'UpdateTodos',
query: print(query),
variables: {
todos: [
{
id: 1,
name: 'Take out trash',
completed: true,
},
{
id: 2,
name: 'Clean room',
completed: true,
},
]
}
});
});

it('strips __typename from mixed argument when sending a mutation', async () => {
fetchMock.mock('https://example.com/graphql', {
status: 200,
body: JSON.stringify({
data: {
__typename: 'Mutation',
updateProfile: {
__typename: 'Profile',
id: 1,
},
}
}),
headers: { 'content-type': 'application/json' }
});

const query = gql`
mutation UpdateProfile($profile: ProfileInput!) {
updateProfile(profile: $profile) {
id
}
}
`;

const link = createHttpLink({ uri: 'https://example.com/graphql' });

const profile = {
__typename: 'Profile',
id: 1,
interests: [
{ __typename: 'Interest', name: 'Hiking' },
{ __typename: 'Interest', name: 'Nature' }
],
avatar: {
__typename: 'Avatar',
url: 'https://example.com/avatar.jpg',
}
};

await new Promise((resolve, reject) => {
execute(link, { query, variables: { profile } }).subscribe({
next: resolve,
error: reject
});
});

const [, options] = fetchMock.lastCall()!;
const { body } = options!

expect(JSON.parse(body!.toString())).toEqual({
operationName: 'UpdateProfile',
query: print(query),
variables: {
profile: {
id: 1,
interests: [
{ name: 'Hiking' },
{ name: 'Nature' }
],
avatar: {
url: 'https://example.com/avatar.jpg',
},
},
}
});
});
});

it('strips __typename when sending a query', async () => {
fetchMock.mock('https://example.com/graphql', {
status: 200,
body: JSON.stringify({
data: {
__typename: 'Query',
searchTodos: []
}
}),
headers: { 'content-type': 'application/json' }
});

const query = gql`
query SearchTodos($filter: TodoFilter!) {
searchTodos(filter: $filter) {
id
name
}
}
`;

const link = createHttpLink({ uri: 'https://example.com/graphql' });

const filter = {
__typename: 'Filter',
completed: true,
};

await new Promise((resolve, reject) => {
execute(link, { query, variables: { filter } }).subscribe({
next: resolve,
error: reject
});
});

const [, options] = fetchMock.lastCall()!;
const { body } = options!

expect(JSON.parse(body!.toString())).toEqual({
operationName: 'SearchTodos',
query: print(query),
variables: {
filter: {
completed: true,
},
},
});
});

describe('Dev warnings', () => {
Expand Down
21 changes: 0 additions & 21 deletions src/link/http/__tests__/selectHttpOptionsAndBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,25 +104,4 @@ describe('selectHttpOptionsAndBody', () => {

expect(body.query).toBe('query SampleQuery{stub{id}}');
});

it('strips __typename from variables', () => {
const operation = createOperation(
{},
{
query,
variables: {
__typename: 'Test',
nested: { __typename: 'Nested', foo: 'bar' },
array: [{ __typename: 'Item', baz: 'foo' }]
},
}
);

const { body } = selectHttpOptionsAndBody(operation, {});

expect(body.variables).toEqual({
nested: { foo: 'bar' },
array: [{ baz: 'foo' }],
});
})
});
3 changes: 1 addition & 2 deletions src/link/http/selectHttpOptionsAndBody.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ASTNode, print } from 'graphql';
import { stripTypename } from '../../utilities';

import { Operation } from '../core';

Expand Down Expand Up @@ -180,7 +179,7 @@ export function selectHttpOptionsAndBodyInternal(

//The body depends on the http options
const { operationName, extensions, variables, query } = operation;
const body: Body = { operationName, variables: stripTypename(variables) };
const body: Body = { operationName, variables };

if (http.includeExtensions) (body as any).extensions = extensions;

Expand Down
Loading