Skip to content

Commit

Permalink
Merge pull request #6288 from apollographql/make-readField-and-evict-…
Browse files Browse the repository at this point in the history
…accept-options

Allow silencing broadcast for cache update methods.
  • Loading branch information
benjamn authored May 18, 2020
2 parents 7429d9d + 7df4487 commit eb0461f
Show file tree
Hide file tree
Showing 9 changed files with 346 additions and 34 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
- The `cache.evict` method can optionally take an arguments object as its third parameter (following the entity ID and field name), to delete only those field values with specific arguments. <br/>
[@danReynolds](https://github.com/danReynolds) in [#6141](https://github.com/apollographql/apollo-client/pull/6141)

- Cache methods that would normally trigger a broadcast, like `cache.evict`, `cache.writeQuery`, and `cache.writeFragment`, can now be called with a named options object, which supports a `broadcast: boolean` property that can be used to silence the broadcast, for situations where you want to update the cache multiple times without triggering a broadcast each time. <br/>
[@benjamn](https://github.com/benjamn) in [#6288](https://github.com/apollographql/apollo-client/pull/6288)

- The contents of the `@apollo/react-hooks` package have been merged into `@apollo/client`, enabling the following all-in-one `import`:
```ts
import { ApolloClient, ApolloProvider, useQuery } from '@apollo/client';
Expand Down
2 changes: 1 addition & 1 deletion src/cache/core/__tests__/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class TestCache extends ApolloCache<unknown> {
return {};
}

public evict(dataId: string, fieldName?: string): boolean {
public evict(): boolean {
return false;
}

Expand Down
20 changes: 16 additions & 4 deletions src/cache/core/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,20 @@ export abstract class ApolloCache<TSerialized> implements DataProxy {
public abstract watch(watch: Cache.WatchOptions): () => void;
public abstract reset(): Promise<void>;

// If called with only one argument, removes the entire entity
// identified by dataId. If called with a fieldName as well, removes all
// fields of the identified entity whose store names match fieldName.
public abstract evict(dataId: string, fieldName?: string): boolean;
// Remove whole objects from the cache by passing just options.id, or
// specific fields by passing options.field and/or options.args. If no
// options.args are provided, all fields matching options.field (even
// those with arguments) will be removed. Returns true iff any data was
// removed from the cache.
public abstract evict(options: Cache.EvictOptions): boolean;

// For backwards compatibility, evict can also take positional
// arguments. Please prefer the Cache.EvictOptions style (above).
public abstract evict(
id: string,
field?: string,
args?: Record<string, any>,
): boolean;

// intializer / offline / ssr API
/**
Expand Down Expand Up @@ -129,6 +139,7 @@ export abstract class ApolloCache<TSerialized> implements DataProxy {
result: options.data,
query: options.query,
variables: options.variables,
broadcast: options.broadcast,
});
}

Expand All @@ -140,6 +151,7 @@ export abstract class ApolloCache<TSerialized> implements DataProxy {
result: options.data,
variables: options.variables,
query: this.getFragmentDoc(options.fragment, options.fragmentName),
broadcast: options.broadcast,
});
}
}
10 changes: 9 additions & 1 deletion src/cache/core/types/Cache.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DataProxy } from './DataProxy';

export namespace Cache {
export type WatchCallback = (newData: any) => void;
export type WatchCallback = (diff: Cache.DiffResult<any>) => void;

export interface ReadOptions<TVariables = any>
extends DataProxy.Query<TVariables> {
Expand All @@ -14,6 +14,7 @@ export namespace Cache {
extends DataProxy.Query<TVariables> {
dataId: string;
result: TResult;
broadcast?: boolean;
}

export interface DiffOptions extends ReadOptions {
Expand All @@ -25,6 +26,13 @@ export namespace Cache {
callback: WatchCallback;
}

export interface EvictOptions {
id: string;
fieldName?: string;
args?: Record<string, any>;
broadcast?: boolean;
}

export import DiffResult = DataProxy.DiffResult;
export import WriteQueryOptions = DataProxy.WriteQueryOptions;
export import WriteFragmentOptions = DataProxy.WriteFragmentOptions;
Expand Down
8 changes: 8 additions & 0 deletions src/cache/core/types/DataProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export namespace DataProxy {
* The data you will be writing to the store.
*/
data: TData;
/**
* Whether to notify query watchers (default: true).
*/
broadcast?: boolean;
}

export interface WriteFragmentOptions<TData, TVariables>
Expand All @@ -67,6 +71,10 @@ export namespace DataProxy {
* The data you will be writing to the store.
*/
data: TData;
/**
* Whether to notify query watchers (default: true).
*/
broadcast?: boolean;
}

export type DiffResult<T> = {
Expand Down
182 changes: 168 additions & 14 deletions src/cache/inmemory/__tests__/entityStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -925,13 +925,13 @@ describe('EntityStore', () => {
publisherOfBook: MelvilleData,
});

cache.evict(
cache.identify({
cache.evict({
id: cache.identify({
__typename: "Publisher",
name: "Alfred A. Knopf",
})!,
"yearOfFounding",
);
fieldName: "yearOfFounding",
});

expect(cache.extract()).toEqual({
ROOT_QUERY: {
Expand All @@ -951,10 +951,12 @@ describe('EntityStore', () => {
// Nothing to garbage collect yet.
expect(cache.gc()).toEqual([]);

cache.evict(cache.identify({
__typename: "Publisher",
name: "Melville House",
})!);
cache.evict({
id: cache.identify({
__typename: "Publisher",
name: "Melville House",
})!,
});

expect(cache.extract()).toEqual({
ROOT_QUERY: {
Expand All @@ -970,7 +972,7 @@ describe('EntityStore', () => {
// Melville House has been removed
});

cache.evict("ROOT_QUERY", "publisherOfBook");
cache.evict({ id: "ROOT_QUERY", fieldName: "publisherOfBook" });

function withoutPublisherOfBook(obj: Record<string, any>) {
const clean = { ...obj };
Expand Down Expand Up @@ -1049,10 +1051,10 @@ describe('EntityStore', () => {
name: "Ted Chiang",
};

cache.evict(
cache.identify(tedWithoutHobby)!,
"hobby",
);
cache.evict({
id: cache.identify(tedWithoutHobby)!,
fieldName: "hobby",
});

expect(cache.diff<any>({
query,
Expand Down Expand Up @@ -1082,7 +1084,7 @@ describe('EntityStore', () => {
],
});

cache.evict("ROOT_QUERY", "authorOfBook");
cache.evict({ id: "ROOT_QUERY", fieldName: "authorOfBook"});
expect(cache.gc().sort()).toEqual([
'Author:{"name":"Jenny Odell"}',
'Author:{"name":"Ted Chiang"}',
Expand Down Expand Up @@ -1232,6 +1234,158 @@ describe('EntityStore', () => {
});
});

it("allows evicting specific fields with specific arguments using EvictOptions", () => {
const query: DocumentNode = gql`
query {
authorOfBook(isbn: $isbn) {
name
hobby
}
}
`;

const cache = new InMemoryCache();

const TedChiangData = {
__typename: "Author",
name: "Ted Chiang",
hobby: "video games",
};

const IsaacAsimovData = {
__typename: "Author",
name: "Isaac Asimov",
hobby: "chemistry",
};

const JamesCoreyData = {
__typename: "Author",
name: "James S.A. Corey",
hobby: "tabletop games",
};

cache.writeQuery({
query,
data: {
authorOfBook: TedChiangData,
},
variables: {
isbn: "1",
},
});

cache.writeQuery({
query,
data: {
authorOfBook: IsaacAsimovData,
},
variables: {
isbn: "2",
},
});

cache.writeQuery({
query,
data: {
authorOfBook: JamesCoreyData,
},
variables: {},
});

expect(cache.extract()).toEqual({
ROOT_QUERY: {
__typename: "Query",
"authorOfBook({\"isbn\":\"1\"})": {
__typename: "Author",
name: "Ted Chiang",
hobby: "video games",
},
"authorOfBook({\"isbn\":\"2\"})": {
__typename: "Author",
name: "Isaac Asimov",
hobby: "chemistry",
},
"authorOfBook({})": {
__typename: "Author",
name: "James S.A. Corey",
hobby: "tabletop games",
}
},
});

cache.evict({
id: 'ROOT_QUERY',
fieldName: 'authorOfBook',
args: { isbn: "1" },
});

expect(cache.extract()).toEqual({
ROOT_QUERY: {
__typename: "Query",
"authorOfBook({\"isbn\":\"2\"})": {
__typename: "Author",
name: "Isaac Asimov",
hobby: "chemistry",
},
"authorOfBook({})": {
__typename: "Author",
name: "James S.A. Corey",
hobby: "tabletop games",
}
},
});

cache.evict({
id: 'ROOT_QUERY',
fieldName: 'authorOfBook',
args: { isbn: '3' },
});

expect(cache.extract()).toEqual({
ROOT_QUERY: {
__typename: "Query",
"authorOfBook({\"isbn\":\"2\"})": {
__typename: "Author",
name: "Isaac Asimov",
hobby: "chemistry",
},
"authorOfBook({})": {
__typename: "Author",
name: "James S.A. Corey",
hobby: "tabletop games",
}
},
});

cache.evict({
id: 'ROOT_QUERY',
fieldName: 'authorOfBook',
args: {},
});

expect(cache.extract()).toEqual({
ROOT_QUERY: {
__typename: "Query",
"authorOfBook({\"isbn\":\"2\"})": {
__typename: "Author",
name: "Isaac Asimov",
hobby: "chemistry",
},
},
});

cache.evict({
id: 'ROOT_QUERY',
fieldName: 'authorOfBook',
});

expect(cache.extract()).toEqual({
ROOT_QUERY: {
__typename: "Query",
},
});
});

it("supports cache.identify(object)", () => {
const queryWithAliases: DocumentNode = gql`
query {
Expand Down
Loading

0 comments on commit eb0461f

Please sign in to comment.