diff --git a/packages/apollo-cache-inmemory/src/__tests__/writeToStore.ts b/packages/apollo-cache-inmemory/src/__tests__/writeToStore.ts index e1902272bba..19c816c2226 100644 --- a/packages/apollo-cache-inmemory/src/__tests__/writeToStore.ts +++ b/packages/apollo-cache-inmemory/src/__tests__/writeToStore.ts @@ -1676,13 +1676,16 @@ describe('writing to the store', () => { ], }; + const writer = new StoreWriter({ + possibleTypes: {}, + }); + return withWarning(() => { const newStore = writer.writeResultToStore({ dataId: 'ROOT_QUERY', result, document: query, dataIdFromObject: getIdField, - possibleTypes: {}, }); expect(newStore.get('1')).toEqual(result.todos[0]); @@ -1724,15 +1727,21 @@ describe('writing to the store', () => { ], }; + const writer = new StoreWriter({ + possibleTypes: { + Todo: { + ShoppingCartItem: true, + TaskItem: true, + }, + }, + }); + return withWarning(() => { const newStore = writer.writeResultToStore({ dataId: 'ROOT_QUERY', result, document: queryWithInterface, dataIdFromObject: getIdField, - possibleTypes: { - Todo: ['ShoppingCartItem', 'TaskItem'], - }, }); expect(newStore.get('1')).toEqual(result.todos[0]); @@ -1750,13 +1759,16 @@ describe('writing to the store', () => { ], }; + const writer = new StoreWriter({ + possibleTypes: {}, + }); + return withWarning(() => { const newStore = writer.writeResultToStore({ dataId: 'ROOT_QUERY', result, document: addTypenameToDocument(query), dataIdFromObject: getIdField, - possibleTypes: {}, }); expect(newStore.get('1')).toEqual(result.todos[0]); diff --git a/packages/apollo-cache-inmemory/src/fragments.ts b/packages/apollo-cache-inmemory/src/fragments.ts index a6f67b633c5..8ea7b38736e 100644 --- a/packages/apollo-cache-inmemory/src/fragments.ts +++ b/packages/apollo-cache-inmemory/src/fragments.ts @@ -1,10 +1,10 @@ import { InlineFragmentNode, FragmentDefinitionNode } from 'graphql'; -import { PossibleTypesMap } from './types'; +type PossibleTypes = import('./inMemoryCache').InMemoryCache['possibleTypes']; export function fragmentMatches( fragment: InlineFragmentNode | FragmentDefinitionNode, typename: string, - possibleTypes?: PossibleTypesMap, + possibleTypes?: PossibleTypes, ) { if (!fragment.typeCondition) { return true; @@ -22,9 +22,9 @@ export function fragmentMatches( for (let i = 0; i < workQueue.length; ++i) { const subtypes = workQueue[i]; if (subtypes) { - if (subtypes.indexOf(typename) >= 0) return true; - for (let { length } = subtypes, j = 0; j < length; ++j) { - const subsubtypes = possibleTypes[subtypes[j]]; + if (subtypes[typename] === true) return true; + for (const subtype in subtypes) { + const subsubtypes = possibleTypes[subtype]; // If this subtype has subtypes of its own, and we haven't considered // this array of sub-subtypes before, add them the queue. if (subsubtypes && workQueue.indexOf(subsubtypes) < 0) { diff --git a/packages/apollo-cache-inmemory/src/inMemoryCache.ts b/packages/apollo-cache-inmemory/src/inMemoryCache.ts index d0680ea92dd..ecdd5b23240 100644 --- a/packages/apollo-cache-inmemory/src/inMemoryCache.ts +++ b/packages/apollo-cache-inmemory/src/inMemoryCache.ts @@ -15,6 +15,7 @@ import { ApolloReducerConfig, NormalizedCache, NormalizedCacheObject, + PossibleTypesMap, } from './types'; import { StoreReader } from './readFromStore'; @@ -84,6 +85,11 @@ export class InMemoryCache extends ApolloCache { protected config: InMemoryCacheConfig; private watches = new Set(); private addTypename: boolean; + private possibleTypes?: { + [supertype: string]: { + [subtype: string]: true; + }; + }; private typenameDocumentCache = new Map(); private storeReader: StoreReader; private storeWriter: StoreWriter; @@ -114,6 +120,10 @@ export class InMemoryCache extends ApolloCache { this.addTypename = !!this.config.addTypename; + if (this.config.possibleTypes) { + this.addPossibleTypes(this.config.possibleTypes); + } + // Passing { resultCaching: false } in the InMemoryCache constructor options // will completely disable dependency tracking, which will improve memory // usage but worsen the performance of repeated reads. @@ -128,10 +138,14 @@ export class InMemoryCache extends ApolloCache { // original this.data cache object. this.optimisticData = this.data; - this.storeWriter = new StoreWriter(); + this.storeWriter = new StoreWriter({ + possibleTypes: this.possibleTypes, + }); + this.storeReader = new StoreReader({ cacheKeyRoot: this.cacheKeyRoot, freezeResults: config.freezeResults, + possibleTypes: this.possibleTypes, }); const cache = this; @@ -199,7 +213,6 @@ export class InMemoryCache extends ApolloCache { document: this.transformDocument(write.query), store: this.data, dataIdFromObject: this.config.dataIdFromObject, - possibleTypes: this.config.possibleTypes, }); this.broadcastWatches(); @@ -321,6 +334,19 @@ export class InMemoryCache extends ApolloCache { return document; } + public addPossibleTypes(possibleTypes: PossibleTypesMap) { + if (!this.possibleTypes) this.possibleTypes = Object.create(null); + Object.keys(possibleTypes).forEach(supertype => { + let subtypeSet = this.possibleTypes[supertype]; + if (!subtypeSet) { + subtypeSet = this.possibleTypes[supertype] = Object.create(null); + } + possibleTypes[supertype].forEach(subtype => { + subtypeSet[subtype] = true; + }); + }); + } + protected broadcastWatches() { if (!this.silenceBroadcast) { this.watches.forEach(c => this.maybeBroadcastWatch(c)); diff --git a/packages/apollo-cache-inmemory/src/readFromStore.ts b/packages/apollo-cache-inmemory/src/readFromStore.ts index 77c60ef9f1b..60e02f13a6b 100644 --- a/packages/apollo-cache-inmemory/src/readFromStore.ts +++ b/packages/apollo-cache-inmemory/src/readFromStore.ts @@ -92,17 +92,21 @@ type ExecSubSelectedArrayOptions = { execContext: ExecContext; }; +type PossibleTypes = import('./inMemoryCache').InMemoryCache['possibleTypes']; export interface StoreReaderConfig { cacheKeyRoot?: KeyTrie; freezeResults?: boolean; + possibleTypes?: PossibleTypes; } export class StoreReader { private freezeResults: boolean; + private possibleTypes?: PossibleTypes; constructor({ cacheKeyRoot = new KeyTrie(canUseWeakMap), freezeResults = false, + possibleTypes, }: StoreReaderConfig = {}) { const { executeStoreQuery, @@ -111,6 +115,7 @@ export class StoreReader { } = this; this.freezeResults = freezeResults; + this.possibleTypes = possibleTypes; this.executeStoreQuery = wrap((options: ExecStoreQueryOptions) => { return executeStoreQuery.call(this, options); @@ -221,7 +226,6 @@ export class StoreReader { store, dataIdFromObject: config && config.dataIdFromObject, cacheRedirects: (config && config.cacheRedirects) || {}, - possibleTypes: config && config.possibleTypes, }; const execResult = this.executeStoreQuery({ @@ -364,7 +368,7 @@ export class StoreReader { const match = fragmentMatches( fragment, typename, - execContext.contextValue.possibleTypes, + this.possibleTypes, ); if (match && (object || typename === 'Query')) { diff --git a/packages/apollo-cache-inmemory/src/types.ts b/packages/apollo-cache-inmemory/src/types.ts index b50fe780f52..2feeacd9e0e 100644 --- a/packages/apollo-cache-inmemory/src/types.ts +++ b/packages/apollo-cache-inmemory/src/types.ts @@ -74,7 +74,6 @@ export type ReadStoreContext = { readonly store: NormalizedCache; readonly cacheRedirects: CacheResolverMap; readonly dataIdFromObject?: IdGetter; - readonly possibleTypes?: PossibleTypesMap; }; export type PossibleTypesMap = { [key: string]: string[] }; diff --git a/packages/apollo-cache-inmemory/src/writeToStore.ts b/packages/apollo-cache-inmemory/src/writeToStore.ts index ed96f591fba..ec2071a2f73 100644 --- a/packages/apollo-cache-inmemory/src/writeToStore.ts +++ b/packages/apollo-cache-inmemory/src/writeToStore.ts @@ -33,7 +33,6 @@ import { IdGetter, NormalizedCache, StoreObject, - PossibleTypesMap, } from './types'; import { fragmentMatches } from './fragments'; @@ -57,10 +56,16 @@ export type WriteContext = { readonly variables?: any; readonly dataIdFromObject?: IdGetter; readonly fragmentMap?: FragmentMap; - readonly possibleTypes?: PossibleTypesMap; }; +type PossibleTypes = import('./inMemoryCache').InMemoryCache['possibleTypes']; +export interface StoreWriterConfig { + possibleTypes?: PossibleTypes; +} + export class StoreWriter { + constructor(private config: StoreWriterConfig = {}) {} + /** * Writes the result of a query to the store. * @@ -84,14 +89,12 @@ export class StoreWriter { store = defaultNormalizedCacheFactory(), variables, dataIdFromObject, - possibleTypes, }: { query: DocumentNode; result: Object; store?: NormalizedCache; variables?: Object; dataIdFromObject?: IdGetter; - possibleTypes?: PossibleTypesMap; }): NormalizedCache { return this.writeResultToStore({ dataId: 'ROOT_QUERY', @@ -100,7 +103,6 @@ export class StoreWriter { store, variables, dataIdFromObject, - possibleTypes, }); } @@ -111,7 +113,6 @@ export class StoreWriter { store = defaultNormalizedCacheFactory(), variables, dataIdFromObject, - possibleTypes, }: { dataId: string; result: any; @@ -119,7 +120,6 @@ export class StoreWriter { store?: NormalizedCache; variables?: Object; dataIdFromObject?: IdGetter; - possibleTypes?: PossibleTypesMap; }): NormalizedCache { // XXX TODO REFACTOR: this is a temporary workaround until query normalization is made to work with documents. const operationDefinition = getOperationDefinition(document)!; @@ -139,7 +139,6 @@ export class StoreWriter { ), dataIdFromObject, fragmentMap: createFragmentMap(getFragmentDefinitions(document)), - possibleTypes, }, }); } catch (e) { @@ -177,7 +176,7 @@ export class StoreWriter { context, }); } else if ( - context.possibleTypes && + this.config.possibleTypes && !( selection.directives && selection.directives.some( @@ -217,7 +216,7 @@ export class StoreWriter { const match = fragmentMatches( fragment, typename, - context.possibleTypes, + this.config.possibleTypes, ); if (match && (result || typename === 'Query')) {