Skip to content

Commit eb0b7fc

Browse files
alunyovfacebook-github-bot
authored andcommitted
Cleanup outdated records after model/ouput type resolvers updates
Reviewed By: voideanvalue Differential Revision: D52547082 fbshipit-source-id: 550bee0c535f98f0ebe8965b1e19ebc49721fa88
1 parent d2a6b7c commit eb0b7fc

File tree

3 files changed

+132
-60
lines changed

3 files changed

+132
-60
lines changed

packages/react-relay/__tests__/RelayResolverModel-test.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -313,11 +313,7 @@ describe.each([
313313
completeTodo('todo-1');
314314
jest.runAllImmediates();
315315
});
316-
// `completeTodo` should publish new update to the record with Todo item
317-
// and it will create a new subscription for the `live_color` field
318-
// without unsubscribing from the previous one. So now we have two active
319-
// subscriptions for the `live_color` field.
320-
expect(LiveColorSubscriptions.activeSubscriptions.length).toBe(2);
316+
expect(LiveColorSubscriptions.activeSubscriptions.length).toBe(1);
321317

322318
expect(renderer.toJSON()).toEqual('Test todo - green');
323319

@@ -331,7 +327,7 @@ describe.each([
331327
store.scheduleGC();
332328
jest.runAllImmediates();
333329

334-
expect(LiveColorSubscriptions.activeSubscriptions.length).toBe(1);
330+
expect(LiveColorSubscriptions.activeSubscriptions.length).toBe(0);
335331
});
336332

337333
test('read a field with arguments', () => {

packages/relay-runtime/store/RelayModernRecord.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,20 @@ function getLinkedRecordID(record: Record, storageKey: StorageKey): ?DataID {
256256
return link[REF_KEY];
257257
}
258258

259+
/**
260+
* @public
261+
*
262+
* Checks if a field has a reference to another record.
263+
*/
264+
function hasLinkedRecordID(record: Record, storageKey: StorageKey): boolean {
265+
const maybeLink = record[storageKey];
266+
if (maybeLink == null) {
267+
return false;
268+
}
269+
const link = maybeLink;
270+
return typeof link === 'object' && link && typeof link[REF_KEY] === 'string';
271+
}
272+
259273
/**
260274
* @public
261275
*
@@ -286,6 +300,23 @@ function getLinkedRecordIDs(
286300
return (links[REFS_KEY]: any);
287301
}
288302

303+
/**
304+
* @public
305+
*
306+
* Checks if a field have references to other records.
307+
*/
308+
function hasLinkedRecordIDs(record: Record, storageKey: StorageKey): boolean {
309+
const links = record[storageKey];
310+
if (links == null) {
311+
return false;
312+
}
313+
return (
314+
typeof links === 'object' &&
315+
Array.isArray(links[REFS_KEY]) &&
316+
links[REFS_KEY].every(link => typeof link === 'string')
317+
);
318+
}
319+
289320
/**
290321
* @public
291322
*
@@ -677,6 +708,8 @@ module.exports = {
677708
getType,
678709
getValue,
679710
hasValue,
711+
hasLinkedRecordID,
712+
hasLinkedRecordIDs,
680713
merge,
681714
setErrors,
682715
setValue,

packages/relay-runtime/store/experimental-live-resolvers/LiveResolverCache.js

Lines changed: 97 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class LiveResolverCache implements ResolverCache {
145145
// Clean up any existing subscriptions before creating the new subscription
146146
// to avoid being double subscribed, or having a dangling subscription in
147147
// the event of an error during subscription.
148-
this._maybeUnsubscribeFromLiveState(linkedRecord);
148+
maybeUnsubscribeFromLiveState(linkedRecord);
149149
}
150150
linkedID = linkedID ?? generateClientID(recordID, storageKey);
151151
linkedRecord = RelayModernRecord.create(
@@ -339,18 +339,6 @@ class LiveResolverCache implements ResolverCache {
339339
});
340340
}
341341

342-
_maybeUnsubscribeFromLiveState(linkedRecord: Record) {
343-
// If there's an existing subscription, unsubscribe.
344-
// $FlowFixMe[incompatible-type] - casting mixed
345-
const previousUnsubscribe: () => void = RelayModernRecord.getValue(
346-
linkedRecord,
347-
RELAY_RESOLVER_LIVE_STATE_SUBSCRIPTION_KEY,
348-
);
349-
if (previousUnsubscribe != null) {
350-
previousUnsubscribe();
351-
}
352-
}
353-
354342
// Register a new Live State object in the store, subscribing to future
355343
// updates.
356344
_setLiveStateValue(
@@ -674,7 +662,7 @@ class LiveResolverCache implements ResolverCache {
674662
continue;
675663
}
676664
for (const anotherRecordID of recordSet) {
677-
this._markInvalidatedResolverRecord(anotherRecordID, recordSource);
665+
markInvalidatedResolverRecord(anotherRecordID, recordSource);
678666
if (!visited.has(anotherRecordID)) {
679667
recordsToVisit.push(anotherRecordID);
680668
}
@@ -684,28 +672,6 @@ class LiveResolverCache implements ResolverCache {
684672
}
685673
}
686674

687-
_markInvalidatedResolverRecord(
688-
dataID: DataID,
689-
recordSource: MutableRecordSource, // Written to
690-
) {
691-
const record = recordSource.get(dataID);
692-
if (!record) {
693-
warning(
694-
false,
695-
'Expected a resolver record with ID %s, but it was missing.',
696-
dataID,
697-
);
698-
return;
699-
}
700-
const nextRecord = RelayModernRecord.clone(record);
701-
RelayModernRecord.setValue(
702-
nextRecord,
703-
RELAY_RESOLVER_INVALIDATION_KEY,
704-
true,
705-
);
706-
recordSource.set(dataID, nextRecord);
707-
}
708-
709675
_isInvalid(
710676
record: Record,
711677
getDataForResolverFragment: GetDataForResolverFragmentFn,
@@ -752,19 +718,10 @@ class LiveResolverCache implements ResolverCache {
752718
}
753719

754720
unsubscribeFromLiveResolverRecords(invalidatedDataIDs: Set<DataID>): void {
755-
if (invalidatedDataIDs.size === 0) {
756-
return;
757-
}
758-
759-
for (const dataID of invalidatedDataIDs) {
760-
const record = this._getRecordSource().get(dataID);
761-
if (
762-
record != null &&
763-
RelayModernRecord.getType(record) === RELAY_RESOLVER_RECORD_TYPENAME
764-
) {
765-
this._maybeUnsubscribeFromLiveState(record);
766-
}
767-
}
721+
return unsubscribeFromLiveResolverRecordsImpl(
722+
this._getRecordSource(),
723+
invalidatedDataIDs,
724+
);
768725
}
769726

770727
// Given the set of possible invalidated DataID
@@ -778,10 +735,7 @@ class LiveResolverCache implements ResolverCache {
778735

779736
for (const dataID of invalidatedDataIDs) {
780737
const record = this._getRecordSource().get(dataID);
781-
if (
782-
record != null &&
783-
RelayModernRecord.getType(record) === RELAY_RESOLVER_RECORD_TYPENAME
784-
) {
738+
if (record != null && isResolverRecord(record)) {
785739
this._getRecordSource().delete(dataID);
786740
}
787741
}
@@ -856,7 +810,11 @@ function updateCurrentSource(
856810
const updatedRecord = RelayModernRecord.update(currentRecord, nextRecord);
857811
if (updatedRecord !== currentRecord) {
858812
updatedDataIDs.add(recordID);
859-
currentSource.set(recordID, nextRecord);
813+
currentSource.set(recordID, updatedRecord);
814+
// We also need to mark all linked records from the current record as invalidated,
815+
// so that the next time these records are accessed in RelayReader,
816+
// they will be re-read and re-evaluated by the LiveResolverCache and re-subscribed.
817+
markInvalidatedLinkedResolverRecords(currentRecord, currentSource);
860818
}
861819
} else {
862820
currentSource.set(recordID, nextRecord);
@@ -866,6 +824,91 @@ function updateCurrentSource(
866824
return updatedDataIDs;
867825
}
868826

827+
function getAllLinkedRecordIds(record: Record): DataIDSet {
828+
const linkedRecordIDs = new Set<DataID>();
829+
RelayModernRecord.getFields(record).forEach(field => {
830+
if (RelayModernRecord.hasLinkedRecordID(record, field)) {
831+
const linkedRecordID = RelayModernRecord.getLinkedRecordID(record, field);
832+
if (linkedRecordID != null) {
833+
linkedRecordIDs.add(linkedRecordID);
834+
}
835+
} else if (RelayModernRecord.hasLinkedRecordIDs(record, field)) {
836+
RelayModernRecord.getLinkedRecordIDs(record, field)?.forEach(
837+
linkedRecordID => {
838+
if (linkedRecordID != null) {
839+
linkedRecordIDs.add(linkedRecordID);
840+
}
841+
},
842+
);
843+
}
844+
});
845+
846+
return linkedRecordIDs;
847+
}
848+
849+
function markInvalidatedResolverRecord(
850+
dataID: DataID,
851+
recordSource: MutableRecordSource, // Written to
852+
) {
853+
const record = recordSource.get(dataID);
854+
if (!record) {
855+
warning(
856+
false,
857+
'Expected a resolver record with ID %s, but it was missing.',
858+
dataID,
859+
);
860+
return;
861+
}
862+
const nextRecord = RelayModernRecord.clone(record);
863+
RelayModernRecord.setValue(nextRecord, RELAY_RESOLVER_INVALIDATION_KEY, true);
864+
recordSource.set(dataID, nextRecord);
865+
}
866+
867+
function markInvalidatedLinkedResolverRecords(
868+
record: Record,
869+
recordSource: MutableRecordSource,
870+
): void {
871+
const currentLinkedDataIDs = getAllLinkedRecordIds(record);
872+
for (const recordID of currentLinkedDataIDs) {
873+
const record = recordSource.get(recordID);
874+
if (record != null && isResolverRecord(record)) {
875+
markInvalidatedResolverRecord(recordID, recordSource);
876+
}
877+
}
878+
}
879+
880+
function unsubscribeFromLiveResolverRecordsImpl(
881+
recordSource: RecordSource,
882+
invalidatedDataIDs: $ReadOnlySet<DataID>,
883+
): void {
884+
if (invalidatedDataIDs.size === 0) {
885+
return;
886+
}
887+
888+
for (const dataID of invalidatedDataIDs) {
889+
const record = recordSource.get(dataID);
890+
if (record != null && isResolverRecord(record)) {
891+
maybeUnsubscribeFromLiveState(record);
892+
}
893+
}
894+
}
895+
896+
function isResolverRecord(record: Record): boolean {
897+
return RelayModernRecord.getType(record) === RELAY_RESOLVER_RECORD_TYPENAME;
898+
}
899+
900+
function maybeUnsubscribeFromLiveState(linkedRecord: Record): void {
901+
// If there's an existing subscription, unsubscribe.
902+
// $FlowFixMe[incompatible-type] - casting mixed
903+
const previousUnsubscribe: () => void = RelayModernRecord.getValue(
904+
linkedRecord,
905+
RELAY_RESOLVER_LIVE_STATE_SUBSCRIPTION_KEY,
906+
);
907+
if (previousUnsubscribe != null) {
908+
previousUnsubscribe();
909+
}
910+
}
911+
869912
function expectRecord(source: RecordSource, recordID: DataID): Record {
870913
const record = source.get(recordID);
871914
invariant(

0 commit comments

Comments
 (0)