From 56c5dcb8771391546a9ef980841ab489c15f066c Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 19 Dec 2018 11:25:00 -0500 Subject: [PATCH 1/8] Avoid DepTrackingCache for optimistic reads. This should help with #4210, since temporary optimistic ObjectCache objects will no longer be instanceof DepTrackingCache, so the result caching system will be disabled for those reads, and thus the temporary objects will not be retained in any cache keys: https://github.com/apollographql/apollo-client/blob/7d0ed16a97e57cb3789f352fd4f359eaaa2eec0b/packages/apollo-cache-inmemory/src/readFromStore.ts#L123 https://github.com/apollographql/apollo-client/blob/7d0ed16a97e57cb3789f352fd4f359eaaa2eec0b/packages/apollo-cache-inmemory/src/readFromStore.ts#L144 --- .../apollo-cache-inmemory/src/inMemoryCache.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/apollo-cache-inmemory/src/inMemoryCache.ts b/packages/apollo-cache-inmemory/src/inMemoryCache.ts index ce80f6c0b61..9515a0b9e06 100644 --- a/packages/apollo-cache-inmemory/src/inMemoryCache.ts +++ b/packages/apollo-cache-inmemory/src/inMemoryCache.ts @@ -23,6 +23,7 @@ import { StoreWriter } from './writeToStore'; import { defaultNormalizedCacheFactory, DepTrackingCache } from './depTrackingCache'; import { wrap, CacheKeyNode, OptimisticWrapperFunction } from './optimism'; +import { ObjectCache } from './objectCache'; import { record } from './recordingCache'; const defaultConfig: ApolloReducerConfig = { @@ -134,9 +135,10 @@ export class InMemoryCache extends ApolloCache { return null; } - const store = (query.optimistic && this.optimistic.length) - ? defaultNormalizedCacheFactory(this.extract(true)) - : this.data; + const store = + query.optimistic && this.optimistic.length + ? new ObjectCache(this.extract(true)) + : this.data; return this.storeReader.readQueryFromStore({ store, @@ -164,9 +166,10 @@ export class InMemoryCache extends ApolloCache { } public diff(query: Cache.DiffOptions): Cache.DiffResult { - const store = (query.optimistic && this.optimistic.length) - ? defaultNormalizedCacheFactory(this.extract(true)) - : this.data; + const store = + query.optimistic && this.optimistic.length + ? new ObjectCache(this.extract(true)) + : this.data; return this.storeReader.diffQueryAgainstStore({ store: store, From b1ac9ef17f99231d62545c26b25e90dd98eaa69a Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 19 Dec 2018 11:43:42 -0500 Subject: [PATCH 2/8] Bump apollo-cache-inmemory npm version to 1.3.12-beta.0. --- .../apollo-cache-inmemory/package-lock.json | 53 ++++++------------- packages/apollo-cache-inmemory/package.json | 2 +- 2 files changed, 18 insertions(+), 37 deletions(-) diff --git a/packages/apollo-cache-inmemory/package-lock.json b/packages/apollo-cache-inmemory/package-lock.json index a158d8945c8..44a9b3b7660 100644 --- a/packages/apollo-cache-inmemory/package-lock.json +++ b/packages/apollo-cache-inmemory/package-lock.json @@ -1,62 +1,43 @@ { "name": "apollo-cache-inmemory", - "version": "1.3.11", + "version": "1.3.12-beta.0", "lockfileVersion": 1, "requires": true, "dependencies": { "apollo-cache": { - "version": "file:../apollo-cache", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.1.21.tgz", + "integrity": "sha512-5ErNb78KHtrJNimkDBTEigcvHkIqUmS7QJIk4lpZZ+XLVVgvk2fD+GhD1PLP+s8vHfAKVbO6vdbRxCCjGGrh5w==", "requires": { - "apollo-utilities": "file:../apollo-utilities" - }, - "dependencies": { - "apollo-utilities": { - "version": "file:../apollo-utilities", - "bundled": true, - "requires": { - "fast-json-stable-stringify": "^2.0.0", - "fclone": "^1.0.11" - }, - "dependencies": { - "fast-json-stable-stringify": { - "version": "2.0.0", - "bundled": true - }, - "fclone": { - "version": "1.0.11", - "bundled": true - } - } - } + "apollo-utilities": "^1.0.26" } }, "apollo-utilities": { - "version": "file:../apollo-utilities", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.26.tgz", + "integrity": "sha512-URw7o3phymliqYCYatcird2YRPUU2eWCNvip64U9gQrX56mEfK4m99yBIDCMTpmcvOFsKLii1sIEZsHIs/bvnw==", "requires": { "fast-json-stable-stringify": "^2.0.0" }, "dependencies": { "fast-json-stable-stringify": { "version": "2.0.0", - "bundled": true - }, - "fclone": { - "version": "1.0.11", - "bundled": true + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" } } }, "immutable-tuple": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/immutable-tuple/-/immutable-tuple-0.4.8.tgz", - "integrity": "sha512-1m29EVSrF+LJJAyVo1v12NsIalVKjyi4HNQVQDBx+LNCIuRXnfeMCHuLao5CyN1m3Sn0T63U5JEkmPArPCipQA==" + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/immutable-tuple/-/immutable-tuple-0.4.9.tgz", + "integrity": "sha512-LWbJPZnidF8eczu7XmcnLBsumuyRBkpwIRPCZxlojouhBo5jEBO4toj6n7hMy6IxHU/c+MqDSWkvaTpPlMQcyA==" }, "optimism": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.6.6.tgz", - "integrity": "sha512-1Y6LY7pYbXP5y6yeXYfXhxJi9hsxYAZWpt7bBp4seAwfcYtaN7tq9wot/pdrhyI809/K4gDm3BcZcEkmwGevjg==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.6.8.tgz", + "integrity": "sha512-bN5n1KCxSqwBDnmgDnzMtQTHdL+uea2HYFx1smvtE+w2AMl0Uy31g0aXnP/Nt85OINnMJPRpJyfRQLTCqn5Weg==", "requires": { - "immutable-tuple": "^0.4.4" + "immutable-tuple": "^0.4.9" } } } diff --git a/packages/apollo-cache-inmemory/package.json b/packages/apollo-cache-inmemory/package.json index ecfd2eb9726..e54969a0e71 100644 --- a/packages/apollo-cache-inmemory/package.json +++ b/packages/apollo-cache-inmemory/package.json @@ -1,6 +1,6 @@ { "name": "apollo-cache-inmemory", - "version": "1.3.11", + "version": "1.3.12-beta.0", "description": "Core abstract of Caching layer for Apollo Client", "author": "James Baxley ", "contributors": [ From 307c8c981dd42c3df0c845867d2ee2b649e3f534 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 19 Dec 2018 11:50:22 -0500 Subject: [PATCH 3/8] Update CHANGELOG.md to mention PR #4251. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f6b5f92e6d..2e4f04df4ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ `@client` directives.
[@justinmakaila](https://github.com/justinmakaila) in [#3482](https://github.com/apollographql/apollo-client/pull/3482) +### Apollo Cache In-Memory (1.3.12) + +- Avoid using `DepTrackingCache` for optimistic reads. + [PR #4521](https://github.com/apollographql/apollo-client/pull/4251) ## Apollo Client (2.4.7) From cfc8fdfad06c05676b38bc89f0b0ef2f2c518d0d Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 19 Dec 2018 12:35:52 -0500 Subject: [PATCH 4/8] Allow disabling InMemoryCache result caching. Although this is a rather drastic option, creating an InMemoryCache that does not attempt to cache previous results may help reduce memory usage, at the expense of cache performance on repeated reads: new InMemoryCache({ resultCaching: false, // defaults to true }) See this comment by @barbalex for one such situation: https://github.com/apollographql/apollo-client/issues/4210#issuecomment-446366207 At the very least, this option should help diagnose problems with result caching, even if it's not the right long-term solution. --- .../src/__tests__/__snapshots__/cache.ts.snap | 146 ++++++- .../__tests__/__snapshots__/mapCache.ts.snap | 150 ++++++- .../src/__tests__/cache.ts | 405 ++++++++---------- .../src/inMemoryCache.ts | 18 +- packages/apollo-cache-inmemory/src/index.ts | 7 +- 5 files changed, 463 insertions(+), 263 deletions(-) diff --git a/packages/apollo-cache-inmemory/src/__tests__/__snapshots__/cache.ts.snap b/packages/apollo-cache-inmemory/src/__tests__/__snapshots__/cache.ts.snap index 08cc1a7e210..0cd8b06df71 100644 --- a/packages/apollo-cache-inmemory/src/__tests__/__snapshots__/cache.ts.snap +++ b/packages/apollo-cache-inmemory/src/__tests__/__snapshots__/cache.ts.snap @@ -1,8 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[ - `Cache writeFragment will write some deeply nested data into the store at any id 1` -] = ` +exports[`Cache writeFragment will write some deeply nested data into the store at any id (1/2) 1`] = ` Object { "bar": Object { "i": 7, @@ -19,9 +17,7 @@ Object { } `; -exports[ - `Cache writeFragment will write some deeply nested data into the store at any id 2` -] = ` +exports[`Cache writeFragment will write some deeply nested data into the store at any id (1/2) 2`] = ` Object { "bar": Object { "i": 7, @@ -42,9 +38,7 @@ Object { } `; -exports[ - `Cache writeFragment will write some deeply nested data into the store at any id 3` -] = ` +exports[`Cache writeFragment will write some deeply nested data into the store at any id (1/2) 3`] = ` Object { "bar": Object { "i": 10, @@ -65,9 +59,7 @@ Object { } `; -exports[ - `Cache writeFragment will write some deeply nested data into the store at any id 4` -] = ` +exports[`Cache writeFragment will write some deeply nested data into the store at any id (1/2) 4`] = ` Object { "bar": Object { "i": 10, @@ -88,9 +80,7 @@ Object { } `; -exports[ - `Cache writeFragment will write some deeply nested data into the store at any id 5` -] = ` +exports[`Cache writeFragment will write some deeply nested data into the store at any id (1/2) 5`] = ` Object { "bar": Object { "i": 7, @@ -111,9 +101,129 @@ Object { } `; -exports[ - `Cache writeFragment will write some deeply nested data into the store at any id 6` -] = ` +exports[`Cache writeFragment will write some deeply nested data into the store at any id (1/2) 6`] = ` +Object { + "bar": Object { + "i": 10, + "j": 11, + "k": 12, + }, + "foo": Object { + "e": 4, + "f": 5, + "g": 6, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": "Bar", + }, + }, +} +`; + +exports[`Cache writeFragment will write some deeply nested data into the store at any id (2/2) 1`] = ` +Object { + "bar": Object { + "i": 7, + }, + "foo": Object { + "e": 4, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": undefined, + }, + }, +} +`; + +exports[`Cache writeFragment will write some deeply nested data into the store at any id (2/2) 2`] = ` +Object { + "bar": Object { + "i": 7, + "j": 8, + "k": 9, + }, + "foo": Object { + "e": 4, + "f": 5, + "g": 6, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": undefined, + }, + }, +} +`; + +exports[`Cache writeFragment will write some deeply nested data into the store at any id (2/2) 3`] = ` +Object { + "bar": Object { + "i": 10, + "j": 8, + "k": 9, + }, + "foo": Object { + "e": 4, + "f": 5, + "g": 6, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": undefined, + }, + }, +} +`; + +exports[`Cache writeFragment will write some deeply nested data into the store at any id (2/2) 4`] = ` +Object { + "bar": Object { + "i": 10, + "j": 11, + "k": 12, + }, + "foo": Object { + "e": 4, + "f": 5, + "g": 6, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": undefined, + }, + }, +} +`; + +exports[`Cache writeFragment will write some deeply nested data into the store at any id (2/2) 5`] = ` +Object { + "bar": Object { + "i": 7, + "j": 8, + "k": 9, + }, + "foo": Object { + "e": 4, + "f": 5, + "g": 6, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": "Bar", + }, + }, +} +`; + +exports[`Cache writeFragment will write some deeply nested data into the store at any id (2/2) 6`] = ` Object { "bar": Object { "i": 10, diff --git a/packages/apollo-cache-inmemory/src/__tests__/__snapshots__/mapCache.ts.snap b/packages/apollo-cache-inmemory/src/__tests__/__snapshots__/mapCache.ts.snap index d644838f32c..0ff0bd5d836 100644 --- a/packages/apollo-cache-inmemory/src/__tests__/__snapshots__/mapCache.ts.snap +++ b/packages/apollo-cache-inmemory/src/__tests__/__snapshots__/mapCache.ts.snap @@ -1,8 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[ - `MapCache Cache writeFragment will write some deeply nested data into the store at any id 1` -] = ` +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (1/2) 1`] = ` Object { "bar": Object { "i": 7, @@ -19,9 +17,7 @@ Object { } `; -exports[ - `MapCache Cache writeFragment will write some deeply nested data into the store at any id 2` -] = ` +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (1/2) 2`] = ` Object { "bar": Object { "i": 7, @@ -42,9 +38,7 @@ Object { } `; -exports[ - `MapCache Cache writeFragment will write some deeply nested data into the store at any id 3` -] = ` +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (1/2) 3`] = ` Object { "bar": Object { "i": 10, @@ -65,9 +59,7 @@ Object { } `; -exports[ - `MapCache Cache writeFragment will write some deeply nested data into the store at any id 4` -] = ` +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (1/2) 4`] = ` Object { "bar": Object { "i": 10, @@ -88,9 +80,7 @@ Object { } `; -exports[ - `MapCache Cache writeFragment will write some deeply nested data into the store at any id 5` -] = ` +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (1/2) 5`] = ` Object { "bar": Object { "i": 7, @@ -111,9 +101,7 @@ Object { } `; -exports[ - `MapCache Cache writeFragment will write some deeply nested data into the store at any id 6` -] = ` +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (1/2) 6`] = ` Object { "bar": Object { "i": 10, @@ -134,9 +122,129 @@ Object { } `; -exports[ - `MapCache writing to the store throws when trying to write an object without id that was previously queried with id 1` -] = ` +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (2/2) 1`] = ` +Object { + "bar": Object { + "i": 7, + }, + "foo": Object { + "e": 4, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": undefined, + }, + }, +} +`; + +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (2/2) 2`] = ` +Object { + "bar": Object { + "i": 7, + "j": 8, + "k": 9, + }, + "foo": Object { + "e": 4, + "f": 5, + "g": 6, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": undefined, + }, + }, +} +`; + +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (2/2) 3`] = ` +Object { + "bar": Object { + "i": 10, + "j": 8, + "k": 9, + }, + "foo": Object { + "e": 4, + "f": 5, + "g": 6, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": undefined, + }, + }, +} +`; + +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (2/2) 4`] = ` +Object { + "bar": Object { + "i": 10, + "j": 11, + "k": 12, + }, + "foo": Object { + "e": 4, + "f": 5, + "g": 6, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": undefined, + }, + }, +} +`; + +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (2/2) 5`] = ` +Object { + "bar": Object { + "i": 7, + "j": 8, + "k": 9, + }, + "foo": Object { + "e": 4, + "f": 5, + "g": 6, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": "Bar", + }, + }, +} +`; + +exports[`MapCache Cache writeFragment will write some deeply nested data into the store at any id (2/2) 6`] = ` +Object { + "bar": Object { + "i": 10, + "j": 11, + "k": 12, + }, + "foo": Object { + "e": 4, + "f": 5, + "g": 6, + "h": Object { + "generated": false, + "id": "bar", + "type": "id", + "typename": "Bar", + }, + }, +} +`; + +exports[`MapCache writing to the store throws when trying to write an object without id that was previously queried with id 1`] = ` "Error writing result to store for query: query Failure { item { diff --git a/packages/apollo-cache-inmemory/src/__tests__/cache.ts b/packages/apollo-cache-inmemory/src/__tests__/cache.ts index 0cd4f2cc631..f0cb6e27ed1 100644 --- a/packages/apollo-cache-inmemory/src/__tests__/cache.ts +++ b/packages/apollo-cache-inmemory/src/__tests__/cache.ts @@ -3,39 +3,66 @@ import gql, { disableFragmentWarnings } from 'graphql-tag'; import { stripSymbols } from 'apollo-utilities'; import { cloneDeep } from 'lodash'; -import { InMemoryCache, ApolloReducerConfig, NormalizedCache } from '..'; +import { InMemoryCache, InMemoryCacheConfig, NormalizedCache } from '..'; disableFragmentWarnings(); describe('Cache', () => { - function createCache({ - initialState, - config, - }: { - initialState?: any; - config?: ApolloReducerConfig; - } = {}): ApolloCache { - return new InMemoryCache( - config || { addTypename: false }, - // XXX this is the old format. The tests need to be updated but since it is mapped down - ).restore(initialState ? initialState.apollo.data : {}); + function itWithInitialData( + message: string, + initialDataForCaches: ({ [key: string]: any })[], + callback: (...caches: InMemoryCache[]) => any, + ) { + const cachesList: InMemoryCache[][] = [ + initialDataForCaches.map( + data => new InMemoryCache({ + addTypename: false, + }).restore(cloneDeep(data)) + ), + initialDataForCaches.map( + data => new InMemoryCache({ + addTypename: false, + resultCaching: false, + }).restore(cloneDeep(data)) + ), + ]; + + cachesList.forEach((caches, i) => { + it(message + ` (${i+1}/${cachesList.length})`, () => callback(...caches)); + }); + } + + function itWithCacheConfig( + message: string, + config: InMemoryCacheConfig, + callback: (cache: InMemoryCache) => any, + ) { + const caches = [ + new InMemoryCache({ + addTypename: false, + ...config, + resultCaching: true, + }), + new InMemoryCache({ + addTypename: false, + ...config, + resultCaching: false, + }), + ]; + + caches.forEach((cache, i) => { + it(message + ` (${i+1}/${caches.length})`, () => callback(cache)); + }); } describe('readQuery', () => { - it('will read some data from the store', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - a: 1, - b: 2, - c: 3, - }, - }, - }, - }, - }); + itWithInitialData('will read some data from the store', [{ + ROOT_QUERY: { + a: 1, + b: 2, + c: 3, + }, + }], proxy => { expect( stripSymbols( proxy.readQuery({ @@ -74,41 +101,33 @@ describe('Cache', () => { ).toEqual({ a: 1, b: 2, c: 3 }); }); - it('will read some deeply nested data from the store', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - a: 1, - b: 2, - c: 3, - d: { - type: 'id', - id: 'foo', - generated: false, - }, - }, - foo: { - e: 4, - f: 5, - g: 6, - h: { - type: 'id', - id: 'bar', - generated: false, - }, - }, - bar: { - i: 7, - j: 8, - k: 9, - }, - }, - }, + itWithInitialData('will read some deeply nested data from the store', [{ + ROOT_QUERY: { + a: 1, + b: 2, + c: 3, + d: { + type: 'id', + id: 'foo', + generated: false, }, - }); - + }, + foo: { + e: 4, + f: 5, + g: 6, + h: { + type: 'id', + id: 'bar', + generated: false, + }, + }, + bar: { + i: 7, + j: 8, + k: 9, + }, + }], proxy => { expect( stripSymbols( proxy.readQuery({ @@ -170,20 +189,12 @@ describe('Cache', () => { }); }); - it('will read some data from the store with variables', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - 'field({"literal":true,"value":42})': 1, - 'field({"literal":false,"value":42})': 2, - }, - }, - }, - }, - }); - + itWithInitialData('will read some data from the store with variables', [{ + ROOT_QUERY: { + 'field({"literal":true,"value":42})': 1, + 'field({"literal":false,"value":42})': 2, + }, + }], proxy => { expect( stripSymbols( proxy.readQuery({ @@ -202,19 +213,11 @@ describe('Cache', () => { ).toEqual({ a: 1, b: 2 }); }); - it('will read some data from the store with null variables', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - 'field({"literal":false,"value":null})': 1, - }, - }, - }, - }, - }); - + itWithInitialData('will read some data from the store with null variables', [{ + ROOT_QUERY: { + 'field({"literal":false,"value":null})': 1, + }, + }], proxy => { expect( stripSymbols( proxy.readQuery({ @@ -232,20 +235,12 @@ describe('Cache', () => { ).toEqual({ a: 1 }); }); - it('should not mutate arguments passed in', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - 'field({"literal":true,"value":42})': 1, - 'field({"literal":false,"value":42})': 2, - }, - }, - }, - }, - }); - + itWithInitialData('should not mutate arguments passed in', [{ + ROOT_QUERY: { + 'field({"literal":true,"value":42})': 1, + 'field({"literal":false,"value":42})': 2, + }, + }], proxy => { const options = { query: gql` query($literal: Boolean, $value: Int) { @@ -266,9 +261,10 @@ describe('Cache', () => { }); describe('readFragment', () => { - it('will throw an error when there is no fragment', () => { - const proxy = createCache(); - + itWithInitialData('will throw an error when there is no fragment', [ + // Empty data, but still want to test with/without result caching. + {}, + ], proxy => { expect(() => { proxy.readFragment({ id: 'x', @@ -297,9 +293,9 @@ describe('Cache', () => { ); }); - it('will throw an error when there is more than one fragment but no fragment name', () => { - const proxy = createCache(); - + itWithInitialData('will throw an error when there is more than one fragment but no fragment name', [ + {}, + ], proxy => { expect(() => { proxy.readFragment({ id: 'x', @@ -338,44 +334,36 @@ describe('Cache', () => { ); }); - it('will read some deeply nested data from the store at any id', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - ROOT_QUERY: { - __typename: 'Type1', - a: 1, - b: 2, - c: 3, - d: { - type: 'id', - id: 'foo', - generated: false, - }, - }, - foo: { - __typename: 'Foo', - e: 4, - f: 5, - g: 6, - h: { - type: 'id', - id: 'bar', - generated: false, - }, - }, - bar: { - __typename: 'Bar', - i: 7, - j: 8, - k: 9, - }, - }, - }, + itWithInitialData('will read some deeply nested data from the store at any id', [{ + ROOT_QUERY: { + __typename: 'Type1', + a: 1, + b: 2, + c: 3, + d: { + type: 'id', + id: 'foo', + generated: false, }, - }); - + }, + foo: { + __typename: 'Foo', + e: 4, + f: 5, + g: 6, + h: { + type: 'id', + id: 'bar', + generated: false, + }, + }, + bar: { + __typename: 'Bar', + i: 7, + j: 8, + k: 9, + }, + }], proxy => { expect( stripSymbols( proxy.readFragment({ @@ -490,21 +478,13 @@ describe('Cache', () => { ).toEqual({ i: 7, j: 8, k: 9 }); }); - it('will read some data from the store with variables', () => { - const proxy = createCache({ - initialState: { - apollo: { - data: { - foo: { - __typename: 'Foo', - 'field({"literal":true,"value":42})': 1, - 'field({"literal":false,"value":42})': 2, - }, - }, - }, - }, - }); - + itWithInitialData('will read some data from the store with variables', [{ + foo: { + __typename: 'Foo', + 'field({"literal":true,"value":42})': 1, + 'field({"literal":false,"value":42})': 2, + }, + }], proxy => { expect( stripSymbols( proxy.readFragment({ @@ -524,27 +504,18 @@ describe('Cache', () => { ).toEqual({ a: 1, b: 2 }); }); - it('will return null when an id that can’t be found is provided', () => { - const client1 = createCache(); - const client2 = createCache({ - initialState: { - apollo: { - data: { - bar: { __typename: 'Bar', a: 1, b: 2, c: 3 }, - }, - }, - }, - }); - const client3 = createCache({ - initialState: { - apollo: { - data: { - foo: { __typename: 'Foo', a: 1, b: 2, c: 3 }, - }, - }, - }, - }); - + itWithInitialData('will return null when an id that can’t be found is provided', [ + // client1 + {}, + // client2 + { + bar: { __typename: 'Bar', a: 1, b: 2, c: 3 }, + }, + // client3 + { + foo: { __typename: 'Foo', a: 1, b: 2, c: 3 }, + }, + ], (client1, client2, client3) => { expect( stripSymbols( client1.readFragment({ @@ -591,9 +562,9 @@ describe('Cache', () => { }); describe('writeQuery', () => { - it('will write some data to the store', () => { - const proxy = createCache(); - + itWithInitialData('will write some data to the store', [ + {} + ], proxy => { proxy.writeQuery({ data: { a: 1 }, query: gql` @@ -647,9 +618,9 @@ describe('Cache', () => { }); }); - it('will write some deeply nested data to the store', () => { - const proxy = createCache(); - + itWithInitialData('will write some deeply nested data to the store', [ + {} + ], proxy => { proxy.writeQuery({ data: { a: 1, d: { e: 4 } }, query: gql` @@ -767,9 +738,9 @@ describe('Cache', () => { }); }); - it('will write some data to the store with variables', () => { - const proxy = createCache(); - + itWithInitialData('will write some data to the store with variables', [ + {}, + ], proxy => { proxy.writeQuery({ data: { a: 1, @@ -794,9 +765,10 @@ describe('Cache', () => { }, }); }); - it('will write some data to the store with variables where some are null', () => { - const proxy = createCache(); + itWithInitialData('will write some data to the store with variables where some are null', [ + {} + ], proxy => { proxy.writeQuery({ data: { a: 1, @@ -824,9 +796,9 @@ describe('Cache', () => { }); describe('writeFragment', () => { - it('will throw an error when there is no fragment', () => { - const proxy = createCache(); - + itWithInitialData('will throw an error when there is no fragment', [ + {}, + ], proxy => { expect(() => { proxy.writeFragment({ data: {}, @@ -857,9 +829,9 @@ describe('Cache', () => { ); }); - it('will throw an error when there is more than one fragment but no fragment name', () => { - const proxy = createCache(); - + itWithInitialData('will throw an error when there is more than one fragment but no fragment name', [ + {} + ], proxy => { expect(() => { proxy.writeFragment({ data: {}, @@ -900,11 +872,10 @@ describe('Cache', () => { ); }); - it('will write some deeply nested data into the store at any id', () => { - const proxy = createCache({ - config: { dataIdFromObject: (o: any) => o.id, addTypename: false }, - }); - + itWithCacheConfig('will write some deeply nested data into the store at any id', { + dataIdFromObject: (o: any) => o.id, + addTypename: false, + }, proxy => { proxy.writeFragment({ data: { __typename: 'Foo', e: 4, h: { id: 'bar', i: 7 } }, id: 'foo', @@ -1019,10 +990,10 @@ describe('Cache', () => { expect((proxy as InMemoryCache).extract()).toMatchSnapshot(); }); - it('writes data that can be read back', () => { - const proxy = createCache({ - config: { addTypename: true }, - }); + + itWithCacheConfig('writes data that can be read back', { + addTypename: true, + }, proxy => { const readWriteFragment = gql` fragment aFragment on query { getSomething { @@ -1047,11 +1018,9 @@ describe('Cache', () => { expect(stripSymbols(result)).toEqual(data); }); - it('will write some data to the store with variables', () => { - const proxy = createCache({ - config: { addTypename: true }, - }); - + itWithCacheConfig('will write some data to the store with variables', { + addTypename: true, + }, proxy => { proxy.writeFragment({ data: { a: 1, @@ -1082,9 +1051,9 @@ describe('Cache', () => { }); describe('performTransaction', () => { - it('will not broadcast mid-transaction', () => { - const cache = createCache(); - + itWithInitialData('will not broadcast mid-transaction', [ + {}, + ], cache => { let numBroadcasts = 0; const query = gql` @@ -1130,9 +1099,9 @@ describe('Cache', () => { }); describe('performOptimisticTransaction', () => { - it('will only broadcast once', () => { - const cache = createCache(); - + itWithInitialData('will only broadcast once', [ + {}, + ], cache => { let numBroadcasts = 0; const query = gql` @@ -1171,7 +1140,7 @@ describe('Cache', () => { }); expect(numBroadcasts).toEqual(0); - }, 1); + }, 1 as any); expect(numBroadcasts).toEqual(1); }); diff --git a/packages/apollo-cache-inmemory/src/inMemoryCache.ts b/packages/apollo-cache-inmemory/src/inMemoryCache.ts index 9515a0b9e06..41dd5ade92d 100644 --- a/packages/apollo-cache-inmemory/src/inMemoryCache.ts +++ b/packages/apollo-cache-inmemory/src/inMemoryCache.ts @@ -21,15 +21,21 @@ import { import { StoreReader } from './readFromStore'; import { StoreWriter } from './writeToStore'; -import { defaultNormalizedCacheFactory, DepTrackingCache } from './depTrackingCache'; +import { DepTrackingCache } from './depTrackingCache'; import { wrap, CacheKeyNode, OptimisticWrapperFunction } from './optimism'; import { ObjectCache } from './objectCache'; import { record } from './recordingCache'; -const defaultConfig: ApolloReducerConfig = { + +export interface InMemoryCacheConfig extends ApolloReducerConfig { + resultCaching?: boolean; +} + +const defaultConfig: InMemoryCacheConfig = { fragmentMatcher: new HeuristicFragmentMatcher(), dataIdFromObject: defaultDataIdFromObject, addTypename: true, + resultCaching: true, }; export function defaultDataIdFromObject(result: any): string | null { @@ -46,7 +52,7 @@ export function defaultDataIdFromObject(result: any): string | null { export class InMemoryCache extends ApolloCache { protected data: NormalizedCache; - protected config: ApolloReducerConfig; + protected config: InMemoryCacheConfig; protected optimistic: OptimisticStoreItem[] = []; private watches = new Set(); private addTypename: boolean; @@ -59,7 +65,7 @@ export class InMemoryCache extends ApolloCache { // don't forget to turn it back on! private silenceBroadcast: boolean = false; - constructor(config: ApolloReducerConfig = {}) { + constructor(config: InMemoryCacheConfig = {}) { super(); this.config = { ...defaultConfig, ...config }; @@ -79,7 +85,9 @@ export class InMemoryCache extends ApolloCache { } this.addTypename = this.config.addTypename; - this.data = defaultNormalizedCacheFactory(); + this.data = this.config.resultCaching + ? new DepTrackingCache() + : new ObjectCache(); this.storeReader = new StoreReader(this.cacheKeyRoot); this.storeWriter = new StoreWriter(); diff --git a/packages/apollo-cache-inmemory/src/index.ts b/packages/apollo-cache-inmemory/src/index.ts index 5ef7f2df296..d3e1779cb8b 100644 --- a/packages/apollo-cache-inmemory/src/index.ts +++ b/packages/apollo-cache-inmemory/src/index.ts @@ -1,4 +1,9 @@ -export { InMemoryCache, defaultDataIdFromObject } from './inMemoryCache'; +export { + InMemoryCache, + InMemoryCacheConfig, + defaultDataIdFromObject, +} from './inMemoryCache'; + export * from './readFromStore'; export * from './writeToStore'; export * from './fragmentMatcher'; From d5a9dd13603c75306468b0de7fa54a00eb4b9d20 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 19 Dec 2018 13:47:54 -0500 Subject: [PATCH 5/8] Remove extremely frustrating auto-`git add` behavior from lint-staged. Anyone who uses `git add -p` or `git commit -p` to contruct their commits from a careful subset of current changes has been bitten by this thoughtless policy many times. I'm done working around it. --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 7a21cf20eef..290488a35a1 100644 --- a/package.json +++ b/package.json @@ -71,12 +71,10 @@ }, "lint-staged": { "*.ts*": [ - "prettier --ignore-path \"./config/prettierignore\" --trailing-comma all --single-quote --write", - "git add" + "prettier --ignore-path \"./config/prettierignore\" --trailing-comma all --single-quote --write" ], "*.js*": [ - "prettier --ignore-path \"./config/prettierignore\" --trailing-comma all --single-quote --write", - "git add" + "prettier --ignore-path \"./config/prettierignore\" --trailing-comma all --single-quote --write" ] }, "pre-commit": "lint-staged", From c9a5370c028b310593cbf83adf34886db0cebd80 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 19 Dec 2018 13:46:02 -0500 Subject: [PATCH 6/8] Require latest version of optimism npm dependency. --- packages/apollo-cache-inmemory/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-cache-inmemory/package.json b/packages/apollo-cache-inmemory/package.json index e54969a0e71..0c1919188d2 100644 --- a/packages/apollo-cache-inmemory/package.json +++ b/packages/apollo-cache-inmemory/package.json @@ -44,7 +44,7 @@ "dependencies": { "apollo-cache": "file:../apollo-cache", "apollo-utilities": "file:../apollo-utilities", - "optimism": "^0.6.6" + "optimism": "^0.6.8" }, "peerDependencies": { "graphql": "0.11.7 || ^0.12.0 || ^0.13.0 || ^14.0.0" From 9b828e1c0d2078dac1243a780e10de7254ebee95 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 19 Dec 2018 13:46:20 -0500 Subject: [PATCH 7/8] Bump apollo-cache-inmemory npm version to 1.3.12-beta.1. --- packages/apollo-cache-inmemory/package-lock.json | 2 +- packages/apollo-cache-inmemory/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apollo-cache-inmemory/package-lock.json b/packages/apollo-cache-inmemory/package-lock.json index 44a9b3b7660..268b4dd112e 100644 --- a/packages/apollo-cache-inmemory/package-lock.json +++ b/packages/apollo-cache-inmemory/package-lock.json @@ -1,6 +1,6 @@ { "name": "apollo-cache-inmemory", - "version": "1.3.12-beta.0", + "version": "1.3.12-beta.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/apollo-cache-inmemory/package.json b/packages/apollo-cache-inmemory/package.json index 0c1919188d2..9c6a4ccf45f 100644 --- a/packages/apollo-cache-inmemory/package.json +++ b/packages/apollo-cache-inmemory/package.json @@ -1,6 +1,6 @@ { "name": "apollo-cache-inmemory", - "version": "1.3.12-beta.0", + "version": "1.3.12-beta.1", "description": "Core abstract of Caching layer for Apollo Client", "author": "James Baxley ", "contributors": [ From f25c0dd4a6fec7eb15376562d0f3d18f4b73de37 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 19 Dec 2018 14:06:50 -0500 Subject: [PATCH 8/8] Mention { resultCaching: false } in CHANGELOG.md. --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e4f04df4ac..b1a79680132 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,17 @@ - Avoid using `DepTrackingCache` for optimistic reads. [PR #4521](https://github.com/apollographql/apollo-client/pull/4251) +- When creating an `InMemoryCache` object, it's now possible to disable the + result caching behavior introduced in [#3394](https://github.com/apollographql/apollo-client/pull/3394), + either for diagnostic purposes or because the benefit of caching repeated + reads is not worth the extra memory usage in your application: + ```ts + new InMemoryCache({ + resultCaching: false + }) + ``` + Part of [PR #4521](https://github.com/apollographql/apollo-client/pull/4251). + ## Apollo Client (2.4.7) ### Apollo Client (2.4.7)