Skip to content

Commit

Permalink
Relay: Create Cached Store
Browse files Browse the repository at this point in the history
Summary: Currently, there are only two record stores in `RelayStoreData`:

- `queuedStore` reads through optimistic, "true", and cached data.
- `recordStore` reads only "true" data.

All store update operations (excludes preparing data for containeres) currently use `recordStore`, allowing diffing/writing to operate without respect for optimistic payloads.

Also, these record stores store not only records, but also the mapping from root fields to data IDs.

----

Since we use the `recordStore` when writing payloads, if we encounter a root field that does not exist as "true" data, we will generate a new client ID. If we had previously vended a data ID using `queuedStore`, containers will already have a reference to the data ID from cached data. This inconsistency causes problems when containers need to refetch data when new data is avaialable (e.g. via `setVariables`).

This fixes the problem by creating a new `cachedStore` that allows reading through all both "true" and cached data. This allows the mapping from root fields to data IDs to be consistent throughout the life of an application (from rendering cached data to rendering newly fetched "true" data).

Reviewed By: josephsavona

Differential Revision: D2698788

fb-gh-sync-id: fd41bcaf4f843aad3db47a9b9c76c1d52481fc52
  • Loading branch information
yungsters authored and facebook-github-bot-4 committed Nov 26, 2015
1 parent cd1471e commit b9a17a3
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 55 deletions.
2 changes: 1 addition & 1 deletion src/container/RelayContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ function createContainerComponent(
'fragment. Ensure that there are no failing `if` or `unless` ' +
'conditions.'
);
return storeData.getRecordStore().hasDeferredFragmentData(
return storeData.getCachedStore().hasDeferredFragmentData(
dataID,
fragment.getFragmentID()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('RelayContainer', () => {
mockPointer = {__dataID__: '42'};
const storeData = RelayStoreData.getDefaultInstance();
pendingQueryTracker = storeData.getPendingQueryTracker();
store = storeData.getRecordStore();
store = storeData.getCachedStore();
});

it('returns true when there are no pending queries', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/store/RelayRecordStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export type RangeInfo = {
type RangeOperation = 'append' | 'prepend' | 'remove';

type RecordCollection = {
cachedRecords?: Records;
queuedRecords?: Records;
cachedRecords?: ?Records;
queuedRecords?: ?Records;
records: Records;
};

Expand Down
157 changes: 106 additions & 51 deletions src/store/RelayStoreData.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ var forEachObject = require('forEachObject');
var invariant = require('invariant');
var generateForceIndex = require('generateForceIndex');
var readRelayDiskCache = require('readRelayDiskCache');
var resolveImmediate = require('resolveImmediate');
var warning = require('warning');
var writeRelayQueryPayload = require('writeRelayQueryPayload');
var writeRelayUpdatePayload = require('writeRelayUpdatePayload');
Expand All @@ -63,7 +62,8 @@ var _instance;
class RelayStoreData {
_cacheManager: ?CacheManager;
_cachedRecords: Records;
_cachedRootCalls: RootCallMap;
_cachedRootCallMap: RootCallMap;
_cachedStore: RelayRecordStore;
_changeEmitter: GraphQLStoreChangeEmitter;
_garbageCollector: ?RelayStoreGarbageCollector;
_mutationQueue: RelayMutationQueue;
Expand All @@ -76,7 +76,7 @@ class RelayStoreData {
_queryTracker: RelayQueryTracker;
_queryRunner: GraphQLQueryRunner;
_rangeData: GraphQLStoreRangeUtils;
_rootCalls: RootCallMap;
_rootCallMap: RootCallMap;

/**
* Get the data set backing actual Relay operations. Used in GraphQLStore.
Expand All @@ -89,27 +89,31 @@ class RelayStoreData {
}

constructor() {
var cachedRecords: Records = ({}: $FixMe);
var cachedRootCallMap: RootCallMap = {};
var queuedRecords: Records = ({}: $FixMe);
var records: Records = ({}: $FixMe);
var rootCallMap: RootCallMap = {};
var nodeRangeMap: NodeRangeMap = ({}: $FixMe);
var queuedStore = new RelayRecordStore(
({cachedRecords, queuedRecords, records}: $FixMe),
({cachedRootCallMap, rootCallMap}: $FixMe),
(nodeRangeMap: $FixMe)
);
var recordStore = new RelayRecordStore(
({records}: $FixMe),
({rootCallMap}: $FixMe),
(nodeRangeMap: $FixMe)
);
var rangeData = new GraphQLStoreRangeUtils();
const cachedRecords: Records = ({}: $FixMe);
const cachedRootCallMap: RootCallMap = {};
const queuedRecords: Records = ({}: $FixMe);
const records: Records = ({}: $FixMe);
const rootCallMap: RootCallMap = {};
const nodeRangeMap: NodeRangeMap = ({}: $FixMe);
const {
cachedStore,
queuedStore,
recordStore,
} = createRecordCollection({
cachedRecords,
cachedRootCallMap,
cacheWriter: null,
queuedRecords,
nodeRangeMap,
records,
rootCallMap,
});
const rangeData = new GraphQLStoreRangeUtils();

this._cacheManager = null;
this._cachedRecords = cachedRecords;
this._cachedRootCalls = cachedRootCallMap;
this._cachedRootCallMap = cachedRootCallMap;
this._cachedStore = cachedStore;
this._changeEmitter = new GraphQLStoreChangeEmitter(rangeData);
this._mutationQueue = new RelayMutationQueue(this);
this._nodeRangeMap = nodeRangeMap;
Expand All @@ -121,7 +125,7 @@ class RelayStoreData {
this._records = records;
this._recordStore = recordStore;
this._rangeData = rangeData;
this._rootCalls = rootCallMap;
this._rootCallMap = rootCallMap;
}

/**
Expand Down Expand Up @@ -150,35 +154,45 @@ class RelayStoreData {
* the store.
*/
injectCacheManager(cacheManager: ?CacheManager): void {
var cachedRecords = this._cachedRecords;
var cachedRootCallMap = this._cachedRootCalls;
var rootCallMap = this._rootCalls;
var queuedRecords = this._queuedRecords;
var records = this._records;
const {
cachedStore,
queuedStore,
recordStore,
} = createRecordCollection({
cachedRecords: this._cachedRecords,
cachedRootCallMap: this._cachedRootCallMap,
cacheWriter: cacheManager ? cacheManager.getQueryWriter() : null,
queuedRecords: this._queuedRecords,
nodeRangeMap: this._nodeRangeMap,
records: this._records,
rootCallMap: this._rootCallMap,
});

this._cacheManager = cacheManager;
this._queuedStore = new RelayRecordStore(
({cachedRecords, queuedRecords, records}: $FixMe),
({cachedRootCallMap, rootCallMap}: $FixMe),
(this._nodeRangeMap: $FixMe)
);
this._recordStore = new RelayRecordStore(
({records}: $FixMe),
({rootCallMap}: $FixMe),
(this._nodeRangeMap: $FixMe),
cacheManager ?
cacheManager.getQueryWriter() :
null
);
this._cachedStore = cachedStore;
this._queuedStore = queuedStore;
this._recordStore = recordStore;
}

clearCacheManager(): void {
const {
cachedStore,
queuedStore,
recordStore,
} = createRecordCollection({
cachedRecords: this._cachedRecords,
cachedRootCallMap: this._cachedRootCallMap,
cacheWriter: null,
queuedRecords: this._queuedRecords,
nodeRangeMap: this._nodeRangeMap,
records: this._records,
rootCallMap: this._rootCallMap,
});

this._cacheManager = null;
this._recordStore = new RelayRecordStore(
({records: this._records}: $FixMe),
({rootCallMap: this._rootCalls}: $FixMe),
(this._nodeRangeMap: $FixMe)
);
this._cachedStore = cachedStore;
this._queuedStore = queuedStore;
this._recordStore = recordStore;
}

hasCacheManager(): boolean {
Expand Down Expand Up @@ -223,7 +237,7 @@ class RelayStoreData {
queries,
this._queuedStore,
this._cachedRecords,
this._cachedRootCalls,
this._cachedRootCallMap,
cacheManager,
{
onSuccess: () => {
Expand All @@ -249,7 +263,7 @@ class RelayStoreData {
var profiler = RelayProfiler.profile('RelayStoreData.handleQueryPayload');
var changeTracker = new RelayChangeTracker();
var writer = new RelayQueryWriter(
this._recordStore,
this._cachedStore,
this._queryTracker,
changeTracker,
{
Expand Down Expand Up @@ -365,6 +379,13 @@ class RelayStoreData {
return this._mutationQueue;
}

/**
* Get the record store with only the cached and base data (no queued data).
*/
getCachedStore(): RelayRecordStore {
return this._cachedStore;
}

/**
* Get the record store with full data (cached, base, queued).
*/
Expand Down Expand Up @@ -410,7 +431,7 @@ class RelayStoreData {
* handled through a `RelayRecordStore` instance.
*/
getRootCallData(): RootCallMap {
return this._rootCalls;
return this._rootCallMap;
}

_isStoreDataEmpty(): boolean {
Expand All @@ -437,7 +458,7 @@ class RelayStoreData {

_getRecordStoreForMutation(): RelayRecordStore {
var records = this._records;
var rootCallMap = this._rootCalls;
var rootCallMap = this._rootCallMap;

return new RelayRecordStore(
({records}: $FixMe),
Expand All @@ -453,8 +474,8 @@ class RelayStoreData {
clientMutationID: ClientMutationID
): RelayRecordStore {
var cachedRecords = this._cachedRecords;
var cachedRootCallMap = this._cachedRootCalls;
var rootCallMap = this._rootCalls;
var cachedRootCallMap = this._cachedRootCallMap;
var rootCallMap = this._rootCallMap;
var queuedRecords = this._queuedRecords;
var records = this._records;

Expand All @@ -468,6 +489,40 @@ class RelayStoreData {
}
}

function createRecordCollection({
cachedRecords,
cachedRootCallMap,
cacheWriter,
queuedRecords,
nodeRangeMap,
records,
rootCallMap,
}): {
cachedStore: RelayRecordStore,
queuedStore: RelayRecordStore,
recordStore: RelayRecordStore
} {
return {
queuedStore: new RelayRecordStore(
({cachedRecords, queuedRecords, records}: $FixMe),
({cachedRootCallMap, rootCallMap}: $FixMe),
(nodeRangeMap: $FixMe)
),
cachedStore: new RelayRecordStore(
({cachedRecords, records}: $FixMe),
({cachedRootCallMap, rootCallMap}: $FixMe),
(nodeRangeMap: $FixMe),
cacheWriter
),
recordStore: new RelayRecordStore(
({records}: $FixMe),
({rootCallMap}: $FixMe),
(nodeRangeMap: $FixMe),
cacheWriter
),
};
}

RelayProfiler.instrumentMethods(RelayStoreData.prototype, {
handleQueryPayload: 'RelayStoreData.prototype.handleQueryPayload',
handleUpdatePayload: 'RelayStoreData.prototype.handleUpdatePayload',
Expand Down
37 changes: 37 additions & 0 deletions src/store/__tests__/RelayStoreData-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,44 @@ describe('RelayStoreData', () => {
topLevelComments: {
count: 1,
},
},
};
storeData.handleQueryPayload(query, response);

// results are written to `records`
var recordStore = storeData.getRecordStore();
expect(recordStore.getRecordState('123')).toBe('EXISTENT');
expect(recordStore.getField('123', 'doesViewerLike')).toBe(false);
var commentsID =
recordStore.getLinkedRecordID('123', 'topLevelComments');
expect(recordStore.getField(commentsID, 'count')).toBe(1);

// `queuedRecords` is unchanged
expect(storeData.getQueuedData()).toEqual({});
});

it('uses cached IDs for root fields without IDs', () => {
var storeData = new RelayStoreData();

var query = getNode(Relay.QL`
query {
node(id:"123") {
id,
doesViewerLike,
topLevelComments {
count,
},
}
}
`);
var response = {
node: {
id: '123',
doesViewerLike: false,
topLevelComments: {
count: 1,
},
},
};
storeData.handleQueryPayload(query, response);

Expand Down
43 changes: 43 additions & 0 deletions src/traversal/__tests__/writeRelayQueryPayload_rootRecord-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,49 @@ describe('writeRelayQueryPayload()', () => {
expect(store.getDataID('viewer')).toBe('client:1');
});

it('uses existing id for custom root calls without an id', () => {
const cachedRootCallMap = {
'viewer': {'': 'client:12345'},
};
const cachedRecords = {
'client:12345': {__dataID__: 'client:12345'},
};
const rootCallMap = {};
const records = {};
const store = new RelayRecordStore(
{records, cachedRecords},
{cachedRootCallMap, rootCallMap}
);
const query = getNode(Relay.QL`
query {
viewer {
actor {
id,
},
}
}
`);
const payload = {
viewer: {
actor: {
id: '123',
},
},
};
const results = writePayload(store, query, payload);
expect(results).toEqual({
created: {
'123': true,
},
updated: {
'client:12345': true,
},
});
expect(store.getRecordState('client:12345')).toBe('EXISTENT');
expect(store.getLinkedRecordID('client:12345', 'actor')).toBe('123');
expect(store.getDataID('viewer')).toBe('client:12345');
});

it('is created for custom root calls with an id', () => {
var records = {};
var store = new RelayRecordStore({records});
Expand Down

0 comments on commit b9a17a3

Please sign in to comment.