diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts index f9ef73954d0..6bc9d859bb8 100644 --- a/src/cache/inmemory/inMemoryCache.ts +++ b/src/cache/inmemory/inMemoryCache.ts @@ -20,7 +20,7 @@ import { import { StoreReader } from './readFromStore'; import { StoreWriter } from './writeToStore'; import { EntityStore, supportsResultCaching } from './entityStore'; -import { makeVar, forgetCache } from './reactiveVars'; +import { makeVar, forgetCache, recallCache } from './reactiveVars'; import { defaultDataIdFromObject, PossibleTypesMap, @@ -194,6 +194,19 @@ export class InMemoryCache extends ApolloCache { } public watch(watch: Cache.WatchOptions): () => void { + if (!this.watches.size) { + // In case we previously called forgetCache(this) because + // this.watches became empty (see below), reattach this cache to any + // reactive variables on which it previously depended. It might seem + // paradoxical that we're able to recall something we supposedly + // forgot, but the point of calling forgetCache(this) is to silence + // useless broadcasts while this.watches is empty, and to allow the + // cache to be garbage collected. If, however, we manage to call + // recallCache(this) here, this cache object must not have been + // garbage collected yet, and should resume receiving updates from + // reactive variables, now that it has a watcher to notify. + recallCache(this); + } this.watches.add(watch); if (watch.immediate) { this.maybeBroadcastWatch(watch); diff --git a/src/cache/inmemory/reactiveVars.ts b/src/cache/inmemory/reactiveVars.ts index 409c8611837..75761741901 100644 --- a/src/cache/inmemory/reactiveVars.ts +++ b/src/cache/inmemory/reactiveVars.ts @@ -35,10 +35,20 @@ const varsByCache = new WeakMap, Set>>(); export function forgetCache(cache: ApolloCache) { const vars = varsByCache.get(cache); - if (vars) { - consumeAndIterate(vars, rv => rv.forgetCache(cache)); - varsByCache.delete(cache); - } + if (vars) vars.forEach(rv => rv.forgetCache(cache)); +} + +// Calling forgetCache(cache) serves to silence broadcasts and allows the +// cache to be garbage collected. However, the varsByCache WeakMap +// preserves the set of reactive variables that were previously associated +// with this cache, which makes it possible to "recall" the cache at a +// later time, by reattaching it to those variables. If the cache has been +// garbage collected in the meantime, because it is no longer reachable, +// you won't be able to call recallCache(cache), and the cache will +// automatically disappear from the varsByCache WeakMap. +export function recallCache(cache: ApolloCache) { + const vars = varsByCache.get(cache); + if (vars) vars.forEach(rv => rv.attachCache(cache)); } export function makeVar(value: T): ReactiveVar { @@ -86,14 +96,7 @@ export function makeVar(value: T): ReactiveVar { return rv; }; - rv.forgetCache = cache => { - const deleted = caches.delete(cache); - if (deleted) { - const vars = varsByCache.get(cache); - if (vars) vars.delete(rv); - } - return deleted; - }; + rv.forgetCache = cache => caches.delete(cache); return rv; }