Skip to content

Commit

Permalink
Represent possibleTypes more efficiently internally.
Browse files Browse the repository at this point in the history
This avoids calling subtypes.indexOf in favor of an object key lookup.

It's also now possible to call cache.addPossibleTypes after constructing
the InMemoryCache, and the new types will be merged appropriately.
  • Loading branch information
benjamn committed Aug 1, 2019
1 parent 0122b7f commit 9ccd310
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 25 deletions.
22 changes: 17 additions & 5 deletions packages/apollo-cache-inmemory/src/__tests__/writeToStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down Expand Up @@ -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]);
Expand All @@ -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]);
Expand Down
10 changes: 5 additions & 5 deletions packages/apollo-cache-inmemory/src/fragments.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) {
Expand Down
30 changes: 28 additions & 2 deletions packages/apollo-cache-inmemory/src/inMemoryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ApolloReducerConfig,
NormalizedCache,
NormalizedCacheObject,
PossibleTypesMap,
} from './types';

import { StoreReader } from './readFromStore';
Expand Down Expand Up @@ -84,6 +85,11 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
protected config: InMemoryCacheConfig;
private watches = new Set<Cache.WatchOptions>();
private addTypename: boolean;
private possibleTypes?: {
[supertype: string]: {
[subtype: string]: true;
};
};
private typenameDocumentCache = new Map<DocumentNode, DocumentNode>();
private storeReader: StoreReader;
private storeWriter: StoreWriter;
Expand Down Expand Up @@ -114,6 +120,10 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {

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.
Expand All @@ -128,10 +138,14 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
// 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;
Expand Down Expand Up @@ -199,7 +213,6 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
document: this.transformDocument(write.query),
store: this.data,
dataIdFromObject: this.config.dataIdFromObject,
possibleTypes: this.config.possibleTypes,
});

this.broadcastWatches();
Expand Down Expand Up @@ -321,6 +334,19 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
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));
Expand Down
8 changes: 6 additions & 2 deletions packages/apollo-cache-inmemory/src/readFromStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,21 @@ type ExecSubSelectedArrayOptions = {
execContext: ExecContext;
};

type PossibleTypes = import('./inMemoryCache').InMemoryCache['possibleTypes'];
export interface StoreReaderConfig {
cacheKeyRoot?: KeyTrie<object>;
freezeResults?: boolean;
possibleTypes?: PossibleTypes;
}

export class StoreReader {
private freezeResults: boolean;
private possibleTypes?: PossibleTypes;

constructor({
cacheKeyRoot = new KeyTrie<object>(canUseWeakMap),
freezeResults = false,
possibleTypes,
}: StoreReaderConfig = {}) {
const {
executeStoreQuery,
Expand All @@ -111,6 +115,7 @@ export class StoreReader {
} = this;

this.freezeResults = freezeResults;
this.possibleTypes = possibleTypes;

this.executeStoreQuery = wrap((options: ExecStoreQueryOptions) => {
return executeStoreQuery.call(this, options);
Expand Down Expand Up @@ -221,7 +226,6 @@ export class StoreReader {
store,
dataIdFromObject: config && config.dataIdFromObject,
cacheRedirects: (config && config.cacheRedirects) || {},
possibleTypes: config && config.possibleTypes,
};

const execResult = this.executeStoreQuery({
Expand Down Expand Up @@ -364,7 +368,7 @@ export class StoreReader {
const match = fragmentMatches(
fragment,
typename,
execContext.contextValue.possibleTypes,
this.possibleTypes,
);

if (match && (object || typename === 'Query')) {
Expand Down
1 change: 0 additions & 1 deletion packages/apollo-cache-inmemory/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] };
Expand Down
19 changes: 9 additions & 10 deletions packages/apollo-cache-inmemory/src/writeToStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
IdGetter,
NormalizedCache,
StoreObject,
PossibleTypesMap,
} from './types';
import { fragmentMatches } from './fragments';

Expand All @@ -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.
*
Expand All @@ -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',
Expand All @@ -100,7 +103,6 @@ export class StoreWriter {
store,
variables,
dataIdFromObject,
possibleTypes,
});
}

Expand All @@ -111,15 +113,13 @@ export class StoreWriter {
store = defaultNormalizedCacheFactory(),
variables,
dataIdFromObject,
possibleTypes,
}: {
dataId: string;
result: any;
document: DocumentNode;
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)!;
Expand All @@ -139,7 +139,6 @@ export class StoreWriter {
),
dataIdFromObject,
fragmentMap: createFragmentMap(getFragmentDefinitions(document)),
possibleTypes,
},
});
} catch (e) {
Expand Down Expand Up @@ -177,7 +176,7 @@ export class StoreWriter {
context,
});
} else if (
context.possibleTypes &&
this.config.possibleTypes &&
!(
selection.directives &&
selection.directives.some(
Expand Down Expand Up @@ -217,7 +216,7 @@ export class StoreWriter {
const match = fragmentMatches(
fragment,
typename,
context.possibleTypes,
this.config.possibleTypes,
);

if (match && (result || typename === 'Query')) {
Expand Down

0 comments on commit 9ccd310

Please sign in to comment.