Skip to content

Commit d64b579

Browse files
Juan Tejadafacebook-github-bot
Juan Tejada
authored andcommitted
Extract store subscription management into separate module
Reviewed By: josephsavona, kassens Differential Revision: D24964756 fbshipit-source-id: ef93462be9bb745f0cff47dd2500f72763820d43
1 parent f29f08f commit d64b579

File tree

2 files changed

+172
-105
lines changed

2 files changed

+172
-105
lines changed

packages/relay-runtime/store/RelayModernStore.js

+11-105
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ const RelayProfiler = require('../util/RelayProfiler');
1919
const RelayReader = require('./RelayReader');
2020
const RelayReferenceMarker = require('./RelayReferenceMarker');
2121
const RelayStoreReactFlightUtils = require('./RelayStoreReactFlightUtils');
22+
const RelayStoreSubscriptions = require('./RelayStoreSubscriptions');
2223
const RelayStoreUtils = require('./RelayStoreUtils');
2324

2425
const deepFreeze = require('../util/deepFreeze');
2526
const defaultGetDataID = require('./defaultGetDataID');
2627
const hasOverlappingIDs = require('./hasOverlappingIDs');
2728
const invariant = require('invariant');
28-
const isEmptyObject = require('../util/isEmptyObject');
2929
const recycleNodesInto = require('../util/recycleNodesInto');
3030
const resolveImmediate = require('../util/resolveImmediate');
3131

@@ -55,13 +55,6 @@ export opaque type InvalidationState = {|
5555
invalidations: Map<DataID, ?number>,
5656
|};
5757

58-
type Subscription = {|
59-
callback: (snapshot: Snapshot) => void,
60-
snapshot: Snapshot,
61-
stale: boolean,
62-
backup: ?Snapshot,
63-
|};
64-
6558
type InvalidationSubscription = {|
6659
callback: () => void,
6760
invalidationState: InvalidationState,
@@ -107,7 +100,7 @@ class RelayModernStore implements Store {
107100
|},
108101
>;
109102
_shouldScheduleGC: boolean;
110-
_subscriptions: Set<Subscription>;
103+
_storeSubscriptions: RelayStoreSubscriptions;
111104
_updatedRecordIDs: UpdatedRecords;
112105

113106
constructor(
@@ -150,7 +143,7 @@ class RelayModernStore implements Store {
150143
this._releaseBuffer = [];
151144
this._roots = new Map();
152145
this._shouldScheduleGC = false;
153-
this._subscriptions = new Set();
146+
this._storeSubscriptions = new RelayStoreSubscriptions();
154147
this._updatedRecordIDs = {};
155148

156149
initializeRecordSource(this._recordSource);
@@ -303,17 +296,11 @@ class RelayModernStore implements Store {
303296

304297
const source = this.getSource();
305298
const updatedOwners = [];
306-
const hasUpdatedRecords = !isEmptyObject(this._updatedRecordIDs);
307-
this._subscriptions.forEach(subscription => {
308-
const owner = this._updateSubscription(
309-
source,
310-
subscription,
311-
hasUpdatedRecords,
312-
);
313-
if (owner != null) {
314-
updatedOwners.push(owner);
315-
}
316-
});
299+
this._storeSubscriptions.updateSubscriptions(
300+
source,
301+
this._updatedRecordIDs,
302+
updatedOwners,
303+
);
317304
this._invalidationSubscriptions.forEach(subscription => {
318305
this._updateInvalidationSubscription(
319306
subscription,
@@ -395,12 +382,7 @@ class RelayModernStore implements Store {
395382
snapshot: Snapshot,
396383
callback: (snapshot: Snapshot) => void,
397384
): Disposable {
398-
const subscription = {backup: null, callback, snapshot, stale: false};
399-
const dispose = () => {
400-
this._subscriptions.delete(subscription);
401-
};
402-
this._subscriptions.add(subscription);
403-
return {dispose};
385+
return this._storeSubscriptions.subscribe(snapshot, callback);
404386
}
405387

406388
holdGC(): Disposable {
@@ -430,43 +412,6 @@ class RelayModernStore implements Store {
430412
return this._updatedRecordIDs;
431413
}
432414

433-
// Returns the owner (RequestDescriptor) if the subscription was affected by the
434-
// latest update, or null if it was not affected.
435-
_updateSubscription(
436-
source: RecordSource,
437-
subscription: Subscription,
438-
hasUpdatedRecords: boolean,
439-
): ?RequestDescriptor {
440-
const {backup, callback, snapshot, stale} = subscription;
441-
const hasOverlappingUpdates =
442-
hasUpdatedRecords &&
443-
hasOverlappingIDs(snapshot.seenRecords, this._updatedRecordIDs);
444-
if (!stale && !hasOverlappingUpdates) {
445-
return;
446-
}
447-
let nextSnapshot: Snapshot =
448-
hasOverlappingUpdates || !backup
449-
? RelayReader.read(source, snapshot.selector)
450-
: backup;
451-
const nextData = recycleNodesInto(snapshot.data, nextSnapshot.data);
452-
nextSnapshot = ({
453-
data: nextData,
454-
isMissingData: nextSnapshot.isMissingData,
455-
seenRecords: nextSnapshot.seenRecords,
456-
selector: nextSnapshot.selector,
457-
missingRequiredFields: nextSnapshot.missingRequiredFields,
458-
}: Snapshot);
459-
if (__DEV__) {
460-
deepFreeze(nextSnapshot);
461-
}
462-
subscription.snapshot = nextSnapshot;
463-
subscription.stale = false;
464-
if (nextSnapshot.data !== snapshot.data) {
465-
callback(nextSnapshot);
466-
return snapshot.selector.owner;
467-
}
468-
}
469-
470415
lookupInvalidationState(dataIDs: $ReadOnlyArray<DataID>): InvalidationState {
471416
const invalidations = new Map();
472417
dataIDs.forEach(dataID => {
@@ -546,29 +491,7 @@ class RelayModernStore implements Store {
546491
name: 'store.snapshot',
547492
});
548493
}
549-
this._subscriptions.forEach(subscription => {
550-
// Backup occurs after writing a new "final" payload(s) and before (re)applying
551-
// optimistic changes. Each subscription's `snapshot` represents what was *last
552-
// published to the subscriber*, which notably may include previous optimistic
553-
// updates. Therefore a subscription can be in any of the following states:
554-
// - stale=true: This subscription was restored to a different value than
555-
// `snapshot`. That means this subscription has changes relative to its base,
556-
// but its base has changed (we just applied a final payload): recompute
557-
// a backup so that we can later restore to the state the subscription
558-
// should be in.
559-
// - stale=false: This subscription was restored to the same value than
560-
// `snapshot`. That means this subscription does *not* have changes relative
561-
// to its base, so the current `snapshot` is valid to use as a backup.
562-
if (!subscription.stale) {
563-
subscription.backup = subscription.snapshot;
564-
return;
565-
}
566-
const snapshot = subscription.snapshot;
567-
const backup = RelayReader.read(this.getSource(), snapshot.selector);
568-
const nextData = recycleNodesInto(snapshot.data, backup.data);
569-
(backup: $FlowFixMe).data = nextData; // backup owns the snapshot and can safely mutate
570-
subscription.backup = backup;
571-
});
494+
this._storeSubscriptions.snapshotSubscriptions(this.getSource());
572495
if (this._gcRun) {
573496
this._gcRun = null;
574497
this._shouldScheduleGC = true;
@@ -594,24 +517,7 @@ class RelayModernStore implements Store {
594517
if (this._shouldScheduleGC) {
595518
this.scheduleGC();
596519
}
597-
this._subscriptions.forEach(subscription => {
598-
const backup = subscription.backup;
599-
subscription.backup = null;
600-
if (backup) {
601-
if (backup.data !== subscription.snapshot.data) {
602-
subscription.stale = true;
603-
}
604-
subscription.snapshot = {
605-
data: subscription.snapshot.data,
606-
isMissingData: backup.isMissingData,
607-
seenRecords: backup.seenRecords,
608-
selector: backup.selector,
609-
missingRequiredFields: backup.missingRequiredFields,
610-
};
611-
} else {
612-
subscription.stale = true;
613-
}
614-
});
520+
this._storeSubscriptions.restoreSubscriptions();
615521
}
616522

617523
scheduleGC() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
// flowlint ambiguous-object-type:error
12+
13+
'use strict';
14+
15+
const RelayReader = require('./RelayReader');
16+
17+
const deepFreeze = require('../util/deepFreeze');
18+
const hasOverlappingIDs = require('./hasOverlappingIDs');
19+
const isEmptyObject = require('../util/isEmptyObject');
20+
const recycleNodesInto = require('../util/recycleNodesInto');
21+
22+
import type {Disposable} from '../util/RelayRuntimeTypes';
23+
import type {
24+
RecordSource,
25+
RequestDescriptor,
26+
Snapshot,
27+
UpdatedRecords,
28+
} from './RelayStoreTypes';
29+
30+
export type Subscription = {|
31+
callback: (snapshot: Snapshot) => void,
32+
snapshot: Snapshot,
33+
stale: boolean,
34+
backup: ?Snapshot,
35+
|};
36+
37+
class RelayStoreSubscriptions {
38+
_subscriptions: Set<Subscription>;
39+
40+
constructor() {
41+
this._subscriptions = new Set();
42+
}
43+
44+
subscribe(
45+
snapshot: Snapshot,
46+
callback: (snapshot: Snapshot) => void,
47+
): Disposable {
48+
const subscription = {backup: null, callback, snapshot, stale: false};
49+
const dispose = () => {
50+
this._subscriptions.delete(subscription);
51+
};
52+
this._subscriptions.add(subscription);
53+
return {dispose};
54+
}
55+
56+
snapshotSubscriptions(source: RecordSource) {
57+
this._subscriptions.forEach(subscription => {
58+
// Backup occurs after writing a new "final" payload(s) and before (re)applying
59+
// optimistic changes. Each subscription's `snapshot` represents what was *last
60+
// published to the subscriber*, which notably may include previous optimistic
61+
// updates. Therefore a subscription can be in any of the following states:
62+
// - stale=true: This subscription was restored to a different value than
63+
// `snapshot`. That means this subscription has changes relative to its base,
64+
// but its base has changed (we just applied a final payload): recompute
65+
// a backup so that we can later restore to the state the subscription
66+
// should be in.
67+
// - stale=false: This subscription was restored to the same value than
68+
// `snapshot`. That means this subscription does *not* have changes relative
69+
// to its base, so the current `snapshot` is valid to use as a backup.
70+
if (!subscription.stale) {
71+
subscription.backup = subscription.snapshot;
72+
return;
73+
}
74+
const snapshot = subscription.snapshot;
75+
const backup = RelayReader.read(source, snapshot.selector);
76+
const nextData = recycleNodesInto(snapshot.data, backup.data);
77+
(backup: $FlowFixMe).data = nextData; // backup owns the snapshot and can safely mutate
78+
subscription.backup = backup;
79+
});
80+
}
81+
82+
restoreSubscriptions() {
83+
this._subscriptions.forEach(subscription => {
84+
const backup = subscription.backup;
85+
subscription.backup = null;
86+
if (backup) {
87+
if (backup.data !== subscription.snapshot.data) {
88+
subscription.stale = true;
89+
}
90+
subscription.snapshot = {
91+
data: subscription.snapshot.data,
92+
isMissingData: backup.isMissingData,
93+
seenRecords: backup.seenRecords,
94+
selector: backup.selector,
95+
missingRequiredFields: backup.missingRequiredFields,
96+
};
97+
} else {
98+
subscription.stale = true;
99+
}
100+
});
101+
}
102+
103+
updateSubscriptions(
104+
source: RecordSource,
105+
updatedRecordIDs: UpdatedRecords,
106+
updatedOwners: Array<RequestDescriptor>,
107+
) {
108+
const hasUpdatedRecords = !isEmptyObject(updatedRecordIDs);
109+
this._subscriptions.forEach(subscription => {
110+
const owner = this._updateSubscription(
111+
source,
112+
subscription,
113+
updatedRecordIDs,
114+
hasUpdatedRecords,
115+
);
116+
if (owner != null) {
117+
updatedOwners.push(owner);
118+
}
119+
});
120+
}
121+
122+
// Returns the owner (RequestDescriptor) if the subscription was affected by the
123+
// latest update, or null if it was not affected.
124+
_updateSubscription(
125+
source: RecordSource,
126+
subscription: Subscription,
127+
updatedRecordIDs: UpdatedRecords,
128+
hasUpdatedRecords: boolean,
129+
): ?RequestDescriptor {
130+
const {backup, callback, snapshot, stale} = subscription;
131+
const hasOverlappingUpdates =
132+
hasUpdatedRecords &&
133+
hasOverlappingIDs(snapshot.seenRecords, updatedRecordIDs);
134+
if (!stale && !hasOverlappingUpdates) {
135+
return;
136+
}
137+
let nextSnapshot: Snapshot =
138+
hasOverlappingUpdates || !backup
139+
? RelayReader.read(source, snapshot.selector)
140+
: backup;
141+
const nextData = recycleNodesInto(snapshot.data, nextSnapshot.data);
142+
nextSnapshot = ({
143+
data: nextData,
144+
isMissingData: nextSnapshot.isMissingData,
145+
seenRecords: nextSnapshot.seenRecords,
146+
selector: nextSnapshot.selector,
147+
missingRequiredFields: nextSnapshot.missingRequiredFields,
148+
}: Snapshot);
149+
if (__DEV__) {
150+
deepFreeze(nextSnapshot);
151+
}
152+
subscription.snapshot = nextSnapshot;
153+
subscription.stale = false;
154+
if (nextSnapshot.data !== snapshot.data) {
155+
callback(nextSnapshot);
156+
return snapshot.selector.owner;
157+
}
158+
}
159+
}
160+
161+
module.exports = RelayStoreSubscriptions;

0 commit comments

Comments
 (0)