Skip to content

Commit

Permalink
Prevent @client fields from being passed into the Link chain
Browse files Browse the repository at this point in the history
Apollo Client 2.x allowed `@client` fields to be passed into the
`link` chain if `resolvers` were not set in the constructor. This
allowed `@client` fields to be passed into Links like
`apollo-link-state`. Apollo Client 3 enforces that `@client` fields
are local only, meaning they are no longer passed into the `link`
chain, under any circumstances. We're making this change as we're
slowly starting to deprecate the Local State API (and local
resolvers) in favor of the new Type Policies API. Since we want
people to be able to use the Type Policies API (and `@client`)
without local resolvers, we don't want to force them to always
define `resolvers: {}` in their `ApolloClient` constructor, to
prevent `@client` fields from being passed into the `link` chain.

If anyone wants to preserve this behavior in AC 3, they can
consider using their own directive (instead of `@client`), and
using that directive in the `link` chain.
  • Loading branch information
hwillson committed Feb 26, 2020
1 parent 15536f5 commit 7e2fa1c
Show file tree
Hide file tree
Showing 7 changed files with 24 additions and 65 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
- **[BREAKING?]** Remove `fixPolyfills.ts`, except when bundling for React Native. If you have trouble with `Map` or `Set` operations due to frozen key objects in React Native, either update React Native to version 0.59.0 (or 0.61.x, if possible) or investigate why `fixPolyfills.native.js` is not included in your bundle. <br/>
[@benjamn](https://github.com/benjamn) in [#5962](https://github.com/apollographql/apollo-client/pull/5962)

- **[BREAKING]** Apollo Client 2.x allowed `@client` fields to be passed into the `link` chain if `resolvers` were not set in the constructor. This allowed `@client` fields to be passed into Links like `apollo-link-state`. Apollo Client 3 enforces that `@client` fields are local only, meaning they are no longer passed into the `link` chain, under any circumstances. <br/>
[@hwillson](https://github.com/hwillson) in [#5982](https://github.com/apollographql/apollo-client/pull/5982)

- `InMemoryCache` now supports tracing garbage collection and eviction. Note that the signature of the `evict` method has been simplified in a potentially backwards-incompatible way. <br/>
[@benjamn](https://github.com/benjamn) in [#5310](https://github.com/apollographql/apollo-client/pull/5310)

Expand Down
4 changes: 3 additions & 1 deletion docs/source/migrating/apollo-client-3-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ These packages provide the same functionality as their non-`@apollo` counterpart

`apollo-link-rest` has also been updated to use `@apollo/client`, but does not use `@apollo/link-X` naming. It should still be referenced using `apollo-link-rest`, and updated to its `latest` version.

It is important to note that Apollo Client 3 no longer allows `@client` fields to be passed through a Link chain. While Apollo Client 2 made it possible to intercept `@client` fields in Link's like `apollo-link-state` and `@apollo/link-schema`, Apollo Client 3 enforces that `@client` fields are local only. This helps ensure Apollo Client's local state story is easier to understand, and prevents unwanted fields from accidentally ending up in network requests ([PR #5982](https://github.com/apollographql/apollo-client/pull/5982)).

### graphql-anywhere

The `graphql-anywhere` package’s functionality is no longer included with Apollo Client. You can continue to use the `graphql-anywhere` package, but Apollo no longer uses it and will not actively support it moving forward.
Expand Down Expand Up @@ -169,7 +171,7 @@ Apollo Client 3.0 introduces powerful improvements to its caching system. Most o
* [Configuring the cache](../caching/cache-configuration/)
* [Interacting with cached data](../caching/cache-interaction/)

### Breaking changes
### Breaking cache changes

The following cache changes are **not** backward compatible. Take them into consideration before you upgrade to Apollo Client 3.0.

Expand Down
17 changes: 7 additions & 10 deletions src/ApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,19 +132,16 @@ export class ApolloClient<TCacheShape> implements DataProxy {
let { link } = options;

if (!link) {
if (uri) {
link = new HttpLink({ uri, credentials, headers });
} else if (resolvers) {
link = ApolloLink.empty();
}
link = uri
? new HttpLink({ uri, credentials, headers })
: ApolloLink.empty();
}

if (!link || !cache) {
if (!cache) {
throw new InvariantError(
"To initialize Apollo Client, you must specify 'uri' or 'link' and " +
"'cache' properties in the options object. \n" +
"For more information, please visit: " +
"https://www.apollographql.com/docs/react/"
"To initialize Apollo Client, you must specify a 'cache' property " +
"in the options object. \n" +
"For more information, please visit: https://go.apollo.dev/c/docs"
);
}

Expand Down
14 changes: 8 additions & 6 deletions src/__tests__/ApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ describe('ApolloClient', () => {
window.fetch = oldFetch;
});

it('will throw an error if `uri` or `link` is not passed in', () => {
expect(() => {
new ApolloClient({ cache: new InMemoryCache() } as any);
}).toThrowErrorMatchingSnapshot();
});

it('will throw an error if cache is not passed in', () => {
expect(() => {
new ApolloClient({ link: ApolloLink.empty() } as any);
Expand Down Expand Up @@ -57,6 +51,14 @@ describe('ApolloClient', () => {
});
expect((client.link as HttpLink).options.uri).toEqual(uri2);
});

it('should create an empty Link if `uri` and `link` are not provided', () => {
const client = new ApolloClient({
cache: new InMemoryCache(),
});
expect(client.link).toBeDefined();
expect(client.link instanceof ApolloLink).toBeTruthy();
});
});

describe('readQuery', () => {
Expand Down
9 changes: 2 additions & 7 deletions src/__tests__/__snapshots__/ApolloClient.ts.snap
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ApolloClient constructor will throw an error if \`uri\` or \`link\` is not passed in 1`] = `
"To initialize Apollo Client, you must specify 'uri' or 'link' and 'cache' properties in the options object.
For more information, please visit: https://www.apollographql.com/docs/react/"
`;

exports[`ApolloClient constructor will throw an error if cache is not passed in 1`] = `
"To initialize Apollo Client, you must specify 'uri' or 'link' and 'cache' properties in the options object.
For more information, please visit: https://www.apollographql.com/docs/react/"
"To initialize Apollo Client, you must specify a 'cache' property in the options object.
For more information, please visit: https://go.apollo.dev/c/docs"
`;

exports[`ApolloClient write then read will not use a default id getter if either _id or id is present when __typename is not also present 1`] = `
Expand Down
34 changes: 0 additions & 34 deletions src/__tests__/local-state/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,40 +35,6 @@ describe('General functionality', () => {
});
});

// TODO The functionality tested here should be removed (along with the test)
// once apollo-link-state is fully deprecated.
it('should strip @client fields only if client resolvers specified', async () => {
const query = gql`
{
field @client
}
`;

const client = new ApolloClient({
cache: new InMemoryCache(),
link: new ApolloLink(operation => {
expect(hasDirectives(['client'], operation.query)).toBe(true);
return Observable.of({ data: { field: 'local' } });
}),
});

const { warn } = console;
const messages: string[] = [];
console.warn = (message: string) => messages.push(message);
try {
const result = await client.query({ query });
expect(result.data).toEqual({ field: 'local' });
expect(messages).toEqual([
'Found @client directives in a query but no ApolloClient resolvers ' +
'were specified. This means ApolloClient local resolver handling ' +
'has been disabled, and @client directives will be passed through ' +
'to your link chain.',
]);
} finally {
console.warn = warn;
}
});

it('should not interfere with server introspection queries', () => {
const query = gql`
${getIntrospectionQuery()}
Expand Down
8 changes: 1 addition & 7 deletions src/core/LocalState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,19 +165,13 @@ export class LocalState<TCacheShape> {
if (this.resolvers) {
return document;
}
invariant.warn(
'Found @client directives in a query but no ApolloClient resolvers ' +
'were specified. This means ApolloClient local resolver handling ' +
'has been disabled, and @client directives will be passed through ' +
'to your link chain.',
);
}
return null;
}

// Server queries are stripped of all @client based selection sets.
public serverQuery(document: DocumentNode) {
return this.resolvers ? removeClientSetsFromDocument(document) : document;
return removeClientSetsFromDocument(document);
}

public prepareContext(context?: Record<string, any>) {
Expand Down

1 comment on commit 7e2fa1c

@freshollie
Copy link

@freshollie freshollie commented on 7e2fa1c Apr 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hwillson the reasons behind this feature makes sense, but this means that @client queries don't work for the current mocks api on MockedProvider.

Are client queries expected to be mocked via the resolvers prop to MockedProvider?

Are there any plans to allow @client queries to be mocked through the normal mocks api?

Please sign in to comment.