diff --git a/src/cache/inmemory/__tests__/entityStore.ts b/src/cache/inmemory/__tests__/entityStore.ts index bd1fb502e42..c1ac73c284b 100644 --- a/src/cache/inmemory/__tests__/entityStore.ts +++ b/src/cache/inmemory/__tests__/entityStore.ts @@ -200,19 +200,32 @@ describe('EntityStore', () => { }, }); - // Nothing left to garbage collect, but let's also reset the result cache to + // Nothing left to collect, but let's also reset the result cache to // demonstrate that the recomputed cache results are unchanged. + const originalReader = cache["storeReader"]; expect(cache.gc({ resetResultCache: true, })).toEqual([]); + expect(cache["storeReader"]).not.toBe(originalReader); + const resultAfterResetResultCache = cache.readQuery({ query }); + expect(resultAfterResetResultCache).toBe(resultBeforeGC); + expect(resultAfterResetResultCache).toBe(resultAfterGC); + + // Now discard cache.storeReader.canon as well. + expect(cache.gc({ + resetResultCache: true, + preserveCanon: false, + })).toEqual([]); const resultAfterFullGC = cache.readQuery({ query }); expect(resultAfterFullGC).toEqual(resultBeforeGC); expect(resultAfterFullGC).toEqual(resultAfterGC); - // These !== relations are triggered by the resetResultCache:true option + // These !== relations are triggered by the preserveCanon:false option // passed to cache.gc, above. expect(resultAfterFullGC).not.toBe(resultBeforeGC); expect(resultAfterFullGC).not.toBe(resultAfterGC); + // Result caching immediately begins working again after the intial reset. + expect(cache.readQuery({ query })).toBe(resultAfterFullGC); // Go back to the pre-GC snapshot. cache.restore(snapshot); diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts index d7aef25d83b..115cd8dd01e 100644 --- a/src/cache/inmemory/inMemoryCache.ts +++ b/src/cache/inmemory/inMemoryCache.ts @@ -109,7 +109,9 @@ export class InMemoryCache extends ApolloCache { this.resetResultCache(); } - private resetResultCache() { + private resetResultCache(preserveCanon = true) { + const previousReader = this.storeReader; + // The StoreWriter is mostly stateless and so doesn't really need to be // reset, but it does need to have its writer.storeReader reference updated, // so it's simpler to update this.storeWriter as well. @@ -119,6 +121,11 @@ export class InMemoryCache extends ApolloCache { cache: this, addTypename: this.addTypename, resultCacheMaxSize: this.config.resultCacheMaxSize, + canon: ( + preserveCanon && + previousReader && + previousReader["canon"] + ) || void 0, }), ); @@ -269,14 +276,19 @@ export class InMemoryCache extends ApolloCache { }; } - // Request garbage collection of unreachable normalized entities. public gc(options?: { + // If true, also free non-essential result cache memory by bulk-releasing + // this.{store{Reader,Writer},maybeBroadcastWatch}. Defaults to false. resetResultCache?: boolean; + // If resetResultCache is true, this.storeReader.canon will be preserved by + // default, but can also be discarded by passing false for preserveCanon. + // Defaults to true, but has no effect if resetResultCache is false. + preserveCanon?: boolean; }) { canonicalStringify.reset(); const ids = this.optimisticData.gc(); - if (options && options.resetResultCache) { - this.resetResultCache(); + if (options && options.resetResultCache && !this.txCount) { + this.resetResultCache(options.preserveCanon); } return ids; }