From 4699f5ca3ef295a07ecc1e1f1048f70893f03cc5 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Mon, 11 May 2020 14:52:40 -0400 Subject: [PATCH] Allow silencing broadcast for individual cache writes/evictions. https://github.com/apollographql/apollo-client/issues/6262#issuecomment-626864804 --- src/cache/core/cache.ts | 2 + src/cache/core/types/Cache.ts | 4 +- src/cache/core/types/DataProxy.ts | 8 ++ src/cache/inmemory/__tests__/writeToStore.ts | 119 +++++++++++++++++++ src/cache/inmemory/inMemoryCache.ts | 9 +- 5 files changed, 139 insertions(+), 3 deletions(-) diff --git a/src/cache/core/cache.ts b/src/cache/core/cache.ts index 5ae6f8c466b..ff0415d9b73 100644 --- a/src/cache/core/cache.ts +++ b/src/cache/core/cache.ts @@ -139,6 +139,7 @@ export abstract class ApolloCache implements DataProxy { result: options.data, query: options.query, variables: options.variables, + broadcast: options.broadcast, }); } @@ -150,6 +151,7 @@ export abstract class ApolloCache implements DataProxy { result: options.data, variables: options.variables, query: this.getFragmentDoc(options.fragment, options.fragmentName), + broadcast: options.broadcast, }); } } diff --git a/src/cache/core/types/Cache.ts b/src/cache/core/types/Cache.ts index 234fa05282d..55d7e4279c8 100644 --- a/src/cache/core/types/Cache.ts +++ b/src/cache/core/types/Cache.ts @@ -1,7 +1,7 @@ import { DataProxy } from './DataProxy'; export namespace Cache { - export type WatchCallback = (newData: any) => void; + export type WatchCallback = (diff: Cache.DiffResult) => void; export interface ReadOptions extends DataProxy.Query { @@ -14,6 +14,7 @@ export namespace Cache { extends DataProxy.Query { dataId: string; result: TResult; + broadcast?: boolean; } export interface DiffOptions extends ReadOptions { @@ -29,6 +30,7 @@ export namespace Cache { id: string; fieldName?: string; args?: Record; + broadcast?: boolean; } export import DiffResult = DataProxy.DiffResult; diff --git a/src/cache/core/types/DataProxy.ts b/src/cache/core/types/DataProxy.ts index b20038e4bcd..d9da863b13c 100644 --- a/src/cache/core/types/DataProxy.ts +++ b/src/cache/core/types/DataProxy.ts @@ -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 @@ -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 = { diff --git a/src/cache/inmemory/__tests__/writeToStore.ts b/src/cache/inmemory/__tests__/writeToStore.ts index b120144c787..1dbbef8d0d9 100644 --- a/src/cache/inmemory/__tests__/writeToStore.ts +++ b/src/cache/inmemory/__tests__/writeToStore.ts @@ -15,6 +15,7 @@ import { } from '../../../utilities/graphql/storeUtils'; import { addTypenameToDocument } from '../../../utilities/graphql/transform'; import { cloneDeep } from '../../../utilities/common/cloneDeep'; +import { itAsync } from '../../../utilities/testing/itAsync'; import { StoreWriter } from '../writeToStore'; import { defaultNormalizedCacheFactory } from '../entityStore'; import { InMemoryCache } from '../inMemoryCache'; @@ -2085,4 +2086,122 @@ describe('writing to the store', () => { expect(mergeCounts).toEqual({ first: 1, second: 1, third: 1, fourth: 1 }); }); + + itAsync("should allow silencing broadcast of cache updates", function (resolve, reject) { + const cache = new InMemoryCache({ + typePolicies: { + Counter: { + // Counter is a singleton, but we want to be able to test + // writing to it with writeFragment, so it needs to have an ID. + keyFields: [], + }, + }, + }); + + const query = gql` + query { + counter { + count + } + } + `; + + const results: number[] = []; + + cache.watch({ + query, + optimistic: true, + callback(diff) { + results.push(diff.result); + expect(diff.result).toEqual({ + counter: { + __typename: "Counter", + count: 3, + }, + }); + resolve(); + }, + }); + + let count = 0; + + cache.writeQuery({ + query, + data: { + counter: { + __typename: "Counter", + count: ++count, + }, + }, + broadcast: false, + }); + + expect(cache.extract()).toEqual({ + ROOT_QUERY: { + __typename: "Query", + counter: { __ref: "Counter:{}" }, + }, + "Counter:{}": { + __typename: "Counter", + count: 1, + }, + }); + + expect(results).toEqual([]); + + const counterId = cache.identify({ + __typename: "Counter", + })!; + + cache.writeFragment({ + id: counterId, + fragment: gql`fragment Count on Counter { count }`, + data: { + count: ++count, + }, + broadcast: false, + }); + + expect(cache.extract()).toEqual({ + ROOT_QUERY: { + __typename: "Query", + counter: { __ref: "Counter:{}" }, + }, + "Counter:{}": { + __typename: "Counter", + count: 2, + }, + }); + + expect(results).toEqual([]); + + expect(cache.evict({ + id: counterId, + fieldName: "count", + broadcast: false, + })).toBe(true); + + expect(cache.extract()).toEqual({ + ROOT_QUERY: { + __typename: "Query", + counter: { __ref: "Counter:{}" }, + }, + "Counter:{}": { + __typename: "Counter", + }, + }); + + expect(results).toEqual([]); + + // Only this write should trigger a broadcast. + cache.writeQuery({ + query, + data: { + counter: { + __typename: "Counter", + count: 3, + }, + }, + }); + }); }); diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts index c6cc2873619..22d797c924c 100644 --- a/src/cache/inmemory/inMemoryCache.ts +++ b/src/cache/inmemory/inMemoryCache.ts @@ -146,7 +146,9 @@ export class InMemoryCache extends ApolloCache { variables: options.variables, }); - this.broadcastWatches(); + if (options.broadcast !== false) { + this.broadcastWatches(); + } } public modify( @@ -235,7 +237,10 @@ export class InMemoryCache extends ApolloCache { args, } : idOrOptions, ); - this.broadcastWatches(); + if (typeof idOrOptions === "string" || + idOrOptions.broadcast !== false) { + this.broadcastWatches(); + } return evicted; }