Skip to content

Commit

Permalink
Enable local state only when client resolvers provided. (#4499)
Browse files Browse the repository at this point in the history
* Enable local state only when client resolvers provided.

If an application was previously using apollo-link-state, updating to
apollo-client@2.5.0 could cause problems because @client fields are now
stripped by the integrated LocalState API, and thus will not be passed
into the link chain.

This commit should ease the transition by enabling the LocalState
functionality only if client resolvers were passed to the ApolloClient
constructor, or the LocalState#setResolvers method has been called.

If no client resolvers have been specified, @client fields will remain in
the query passed to the link chain, so apollo-link-state can still process
them, though a warning will be logged in development.

If you want to use @client directives to read from or write to the cache
without running resolver functions, you can pass an empty resolvers:{} map
to enable the LocalState functionality (including the stripping of @client
fields from queries).

* Refine private LocalState resolvers field type.

The setResolvers method normalizes Resolvers[] arrays into a combined
(non-array) Resolvers object.
  • Loading branch information
benjamn authored Feb 26, 2019
1 parent f4cd2fa commit 96fa3da
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 6 deletions.
8 changes: 8 additions & 0 deletions packages/apollo-client/src/__tests__/local-state/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('@client @export tests', () => {
const client = new ApolloClient({
cache,
link: ApolloLink.empty(),
resolvers: {},
});
cache.writeData({ data: { field: 1 } });

Expand Down Expand Up @@ -47,6 +48,7 @@ describe('@client @export tests', () => {
const client = new ApolloClient({
cache,
link: ApolloLink.empty(),
resolvers: {},
});

cache.writeData({
Expand Down Expand Up @@ -198,6 +200,7 @@ describe('@client @export tests', () => {
const client = new ApolloClient({
cache,
link,
resolvers: {},
});

cache.writeData({
Expand Down Expand Up @@ -259,6 +262,7 @@ describe('@client @export tests', () => {
const client = new ApolloClient({
cache,
link,
resolvers: {},
});

cache.writeData({
Expand Down Expand Up @@ -307,6 +311,7 @@ describe('@client @export tests', () => {
const client = new ApolloClient({
cache: new InMemoryCache(),
link,
resolvers: {},
});

return client.query({ query }).then(({ data }: any) => {
Expand Down Expand Up @@ -360,6 +365,7 @@ describe('@client @export tests', () => {
const client = new ApolloClient({
cache,
link,
resolvers: {},
});

cache.writeData({
Expand Down Expand Up @@ -548,6 +554,7 @@ describe('@client @export tests', () => {
const client = new ApolloClient({
cache,
link,
resolvers: {},
});

cache.writeData({
Expand Down Expand Up @@ -599,6 +606,7 @@ describe('@client @export tests', () => {
const client = new ApolloClient({
cache,
link,
resolvers: {},
});

cache.writeData({
Expand Down
38 changes: 38 additions & 0 deletions packages/apollo-client/src/__tests__/local-state/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
IntrospectionFragmentMatcher,
} from 'apollo-cache-inmemory';
import { ApolloLink, Observable, Operation } from 'apollo-link';
import { hasDirectives } from 'apollo-utilities';

describe('General functionality', () => {
it('should not impact normal non-@client use', () => {
Expand All @@ -34,6 +35,38 @@ 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 query but no client resolvers were specified. ' +
'You can now pass apollo-link-state resolvers to the ApolloClient constructor.',
]);
} finally {
console.warn = warn;
}
});

it('should not interfere with server introspection queries', () => {
const query = gql`
${introspectionQuery}
Expand Down Expand Up @@ -232,6 +265,7 @@ describe('Cache manipulation', () => {
const client = new ApolloClient({
cache,
link: ApolloLink.empty(),
resolvers: {},
});

cache.writeQuery({ query, data: { field: 'yo' } });
Expand Down Expand Up @@ -410,6 +444,7 @@ describe('Sample apps', () => {
const client = new ApolloClient({
link,
cache: new InMemoryCache(),
resolvers: {},
});

const update = (
Expand Down Expand Up @@ -504,6 +539,7 @@ describe('Sample apps', () => {
const client = new ApolloClient({
link: ApolloLink.empty(),
cache: new InMemoryCache(),
resolvers: {},
});

interface Todo {
Expand Down Expand Up @@ -749,6 +785,7 @@ describe('Combining client and server state/operations', () => {
const client = new ApolloClient({
cache,
link,
resolvers: {},
});

cache.writeData({
Expand Down Expand Up @@ -786,6 +823,7 @@ describe('Combining client and server state/operations', () => {
const client = new ApolloClient({
cache,
link,
resolvers: {},
});

cache.writeData({
Expand Down
21 changes: 15 additions & 6 deletions packages/apollo-client/src/core/LocalState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import ApolloClient from '../ApolloClient';
import { Resolvers, OperationVariables } from './types';
import { capitalizeFirstLetter } from '../util/capitalizeFirstLetter';


export type Resolver = (
fieldName: string,
rootValue: any,
Expand Down Expand Up @@ -74,7 +73,7 @@ export type LocalStateOptions<TCacheShape> = {
export class LocalState<TCacheShape> {
private cache: ApolloCache<TCacheShape>;
private client: ApolloClient<TCacheShape>;
private resolvers: Resolvers | Resolvers[] = {};
private resolvers?: Resolvers;
private fragmentMatcher: FragmentMatcher;

constructor({
Expand All @@ -99,6 +98,7 @@ export class LocalState<TCacheShape> {
}

public addResolvers(resolvers: Resolvers | Resolvers[]) {
this.resolvers = this.resolvers || {};
if (Array.isArray(resolvers)) {
resolvers.forEach(resolverGroup => {
this.resolvers = mergeDeep(this.resolvers, resolverGroup);
Expand All @@ -114,7 +114,7 @@ export class LocalState<TCacheShape> {
}

public getResolvers() {
return this.resolvers;
return this.resolvers || {};
}

// Run local client resolvers against the incoming query and remote data.
Expand Down Expand Up @@ -162,12 +162,21 @@ export class LocalState<TCacheShape> {
// Client queries contain everything in the incoming document (if a @client
// directive is found).
public clientQuery(document: DocumentNode) {
return hasDirectives(['client'], document) ? document : null;
if (hasDirectives(['client'], document)) {
if (this.resolvers) {
return document;
}
invariant.warn(
'Found @client directives in query but no client resolvers were specified. ' +
'You can now pass apollo-link-state resolvers to the ApolloClient constructor.',
);
}
return null;
}

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

public prepareContext(context = {}) {
Expand Down Expand Up @@ -376,7 +385,7 @@ export class LocalState<TCacheShape> {
) {
const resolverType =
rootValue.__typename || execContext.defaultOperationType;
const resolverMap = (this.resolvers as any)[resolverType];
const resolverMap = this.resolvers && this.resolvers[resolverType];
if (resolverMap) {
const resolve = resolverMap[aliasUsed ? fieldName : aliasedFieldName];
if (resolve) {
Expand Down

0 comments on commit 96fa3da

Please sign in to comment.