relay-runtime 11.0.2 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -1
- package/index.js.flow +16 -1
- package/lib/index.js +15 -0
- package/lib/multi-actor-environment/ActorIdentifier.js +11 -1
- package/lib/multi-actor-environment/ActorSpecificEnvironment.js +59 -19
- package/lib/multi-actor-environment/ActorUtils.js +27 -0
- package/lib/multi-actor-environment/MultiActorEnvironment.js +305 -55
- package/lib/multi-actor-environment/index.js +5 -1
- package/lib/mutations/RelayRecordSourceSelectorProxy.js +6 -1
- package/lib/mutations/commitMutation.js +4 -1
- package/lib/mutations/validateMutation.js +6 -1
- package/lib/network/RelayObservable.js +3 -1
- package/lib/network/RelayQueryResponseCache.js +19 -3
- package/lib/network/wrapNetworkWithLogObserver.js +78 -0
- package/lib/store/DataChecker.js +110 -40
- package/lib/store/OperationExecutor.js +478 -204
- package/lib/store/RelayConcreteVariables.js +21 -0
- package/lib/store/RelayModernEnvironment.js +41 -85
- package/lib/store/RelayModernFragmentSpecResolver.js +48 -22
- package/lib/store/RelayModernRecord.js +35 -1
- package/lib/store/RelayModernStore.js +48 -14
- package/lib/store/RelayOperationTracker.js +33 -23
- package/lib/store/RelayPublishQueue.js +23 -5
- package/lib/store/RelayReader.js +138 -44
- package/lib/store/RelayRecordSource.js +87 -3
- package/lib/store/RelayReferenceMarker.js +28 -15
- package/lib/store/RelayResponseNormalizer.js +164 -91
- package/lib/store/RelayStoreReactFlightUtils.js +1 -7
- package/lib/store/RelayStoreSubscriptions.js +8 -5
- package/lib/store/RelayStoreUtils.js +7 -2
- package/lib/store/ResolverCache.js +213 -0
- package/lib/store/ResolverFragments.js +1 -1
- package/lib/store/createRelayContext.js +1 -1
- package/lib/subscription/requestSubscription.js +27 -29
- package/lib/util/RelayConcreteNode.js +1 -0
- package/lib/util/RelayFeatureFlags.js +3 -5
- package/lib/util/RelayReplaySubject.js +21 -6
- package/lib/util/getPaginationMetadata.js +41 -0
- package/lib/util/getPaginationVariables.js +67 -0
- package/lib/util/getPendingOperationsForFragment.js +55 -0
- package/lib/util/getRefetchMetadata.js +36 -0
- package/lib/util/getValueAtPath.js +51 -0
- package/lib/util/isEmptyObject.js +1 -1
- package/lib/util/registerEnvironmentWithDevTools.js +26 -0
- package/lib/util/withDuration.js +31 -0
- package/multi-actor-environment/ActorIdentifier.js.flow +17 -1
- package/multi-actor-environment/ActorSpecificEnvironment.js.flow +72 -44
- package/multi-actor-environment/ActorUtils.js.flow +33 -0
- package/multi-actor-environment/MultiActorEnvironment.js.flow +332 -80
- package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +61 -12
- package/multi-actor-environment/index.js.flow +3 -0
- package/mutations/RelayRecordSourceSelectorProxy.js.flow +7 -2
- package/mutations/commitMutation.js.flow +2 -0
- package/mutations/validateMutation.js.flow +8 -0
- package/network/RelayObservable.js.flow +2 -0
- package/network/RelayQueryResponseCache.js.flow +31 -18
- package/network/wrapNetworkWithLogObserver.js.flow +99 -0
- package/package.json +1 -1
- package/relay-runtime.js +2 -2
- package/relay-runtime.min.js +2 -2
- package/store/ClientID.js.flow +5 -1
- package/store/DataChecker.js.flow +126 -35
- package/store/OperationExecutor.js.flow +528 -265
- package/store/RelayConcreteVariables.js.flow +26 -1
- package/store/RelayModernEnvironment.js.flow +41 -94
- package/store/RelayModernFragmentSpecResolver.js.flow +40 -14
- package/store/RelayModernOperationDescriptor.js.flow +9 -3
- package/store/RelayModernRecord.js.flow +49 -0
- package/store/RelayModernStore.js.flow +50 -12
- package/store/RelayOperationTracker.js.flow +56 -34
- package/store/RelayPublishQueue.js.flow +31 -8
- package/store/RelayReader.js.flow +148 -42
- package/store/RelayRecordSource.js.flow +72 -6
- package/store/RelayReferenceMarker.js.flow +29 -12
- package/store/RelayResponseNormalizer.js.flow +164 -48
- package/store/RelayStoreReactFlightUtils.js.flow +1 -7
- package/store/RelayStoreSubscriptions.js.flow +10 -3
- package/store/RelayStoreTypes.js.flow +128 -12
- package/store/RelayStoreUtils.js.flow +17 -3
- package/store/ResolverCache.js.flow +247 -0
- package/store/ResolverFragments.js.flow +6 -3
- package/store/createRelayContext.js.flow +1 -1
- package/subscription/requestSubscription.js.flow +41 -29
- package/util/NormalizationNode.js.flow +10 -3
- package/util/ReaderNode.js.flow +15 -1
- package/util/RelayConcreteNode.js.flow +1 -0
- package/util/RelayFeatureFlags.js.flow +8 -10
- package/util/RelayReplaySubject.js.flow +7 -6
- package/util/getPaginationMetadata.js.flow +74 -0
- package/util/getPaginationVariables.js.flow +112 -0
- package/util/getPendingOperationsForFragment.js.flow +62 -0
- package/util/getRefetchMetadata.js.flow +80 -0
- package/util/getValueAtPath.js.flow +46 -0
- package/util/isEmptyObject.js.flow +1 -0
- package/util/registerEnvironmentWithDevTools.js.flow +33 -0
- package/util/withDuration.js.flow +32 -0
- package/lib/store/RelayRecordSourceMapImpl.js +0 -107
- package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +0 -318
- package/store/RelayRecordSourceMapImpl.js.flow +0 -91
- package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +0 -283
|
@@ -20,29 +20,36 @@ const RelayObservable = require('../network/RelayObservable');
|
|
|
20
20
|
const RelayRecordSource = require('./RelayRecordSource');
|
|
21
21
|
const RelayResponseNormalizer = require('./RelayResponseNormalizer');
|
|
22
22
|
|
|
23
|
+
const generateID = require('../util/generateID');
|
|
23
24
|
const getOperation = require('../util/getOperation');
|
|
24
25
|
const invariant = require('invariant');
|
|
25
26
|
const stableCopy = require('../util/stableCopy');
|
|
26
27
|
const warning = require('warning');
|
|
28
|
+
const withDuration = require('../util/withDuration');
|
|
27
29
|
|
|
28
30
|
const {generateClientID, generateUniqueClientID} = require('./ClientID');
|
|
31
|
+
const {getLocalVariables} = require('./RelayConcreteVariables');
|
|
29
32
|
const {
|
|
30
33
|
createNormalizationSelector,
|
|
31
34
|
createReaderSelector,
|
|
32
35
|
} = require('./RelayModernSelector');
|
|
33
36
|
const {ROOT_TYPE, TYPENAME_KEY, getStorageKey} = require('./RelayStoreUtils');
|
|
34
37
|
|
|
38
|
+
import type {ActorIdentifier} from '../multi-actor-environment/ActorIdentifier';
|
|
35
39
|
import type {
|
|
36
40
|
GraphQLResponse,
|
|
37
41
|
GraphQLSingularResponse,
|
|
38
42
|
GraphQLResponseWithData,
|
|
43
|
+
ReactFlightServerTree,
|
|
39
44
|
} from '../network/RelayNetworkTypes';
|
|
40
45
|
import type {Sink, Subscription} from '../network/RelayObservable';
|
|
41
46
|
import type {
|
|
42
47
|
DeferPlaceholder,
|
|
48
|
+
FollowupPayload,
|
|
43
49
|
RequestDescriptor,
|
|
44
50
|
HandleFieldPayload,
|
|
45
51
|
IncrementalDataPlaceholder,
|
|
52
|
+
LogFunction,
|
|
46
53
|
ModuleImportPayload,
|
|
47
54
|
NormalizationSelector,
|
|
48
55
|
OperationDescriptor,
|
|
@@ -53,6 +60,7 @@ import type {
|
|
|
53
60
|
PublishQueue,
|
|
54
61
|
ReactFlightPayloadDeserializer,
|
|
55
62
|
ReactFlightServerErrorHandler,
|
|
63
|
+
ReactFlightClientResponse,
|
|
56
64
|
Record,
|
|
57
65
|
RelayResponsePayload,
|
|
58
66
|
SelectorStoreUpdater,
|
|
@@ -71,23 +79,25 @@ import type {GetDataID} from './RelayResponseNormalizer';
|
|
|
71
79
|
import type {NormalizationOptions} from './RelayResponseNormalizer';
|
|
72
80
|
|
|
73
81
|
export type ExecuteConfig = {|
|
|
82
|
+
+actorIdentifier: ActorIdentifier,
|
|
74
83
|
+getDataID: GetDataID,
|
|
75
|
-
+
|
|
84
|
+
+getPublishQueue: (actorIdentifier: ActorIdentifier) => PublishQueue,
|
|
85
|
+
+getStore: (actorIdentifier: ActorIdentifier) => Store,
|
|
86
|
+
+isClientPayload?: boolean,
|
|
76
87
|
+operation: OperationDescriptor,
|
|
77
88
|
+operationExecutions: Map<string, ActiveState>,
|
|
78
89
|
+operationLoader: ?OperationLoader,
|
|
79
90
|
+operationTracker: OperationTracker,
|
|
80
91
|
+optimisticConfig: ?OptimisticResponseConfig,
|
|
81
|
-
+publishQueue: PublishQueue,
|
|
82
92
|
+reactFlightPayloadDeserializer?: ?ReactFlightPayloadDeserializer,
|
|
83
93
|
+reactFlightServerErrorHandler?: ?ReactFlightServerErrorHandler,
|
|
84
94
|
+scheduler?: ?TaskScheduler,
|
|
95
|
+
+shouldProcessClientComponents?: ?boolean,
|
|
85
96
|
+sink: Sink<GraphQLResponse>,
|
|
86
97
|
+source: RelayObservable<GraphQLResponse>,
|
|
87
|
-
+
|
|
98
|
+
+treatMissingFieldsAsNull: boolean,
|
|
88
99
|
+updater?: ?SelectorStoreUpdater,
|
|
89
|
-
+
|
|
90
|
-
+shouldProcessClientComponents?: ?boolean,
|
|
100
|
+
+log: LogFunction,
|
|
91
101
|
|};
|
|
92
102
|
|
|
93
103
|
export type ActiveState = 'active' | 'inactive';
|
|
@@ -125,10 +135,13 @@ function execute(config: ExecuteConfig): Executor {
|
|
|
125
135
|
* dependencies, etc.
|
|
126
136
|
*/
|
|
127
137
|
class Executor {
|
|
138
|
+
_actorIdentifier: ActorIdentifier;
|
|
128
139
|
_getDataID: GetDataID;
|
|
129
140
|
_treatMissingFieldsAsNull: boolean;
|
|
130
141
|
_incrementalPayloadsPending: boolean;
|
|
131
142
|
_incrementalResults: Map<Label, Map<PathKey, IncrementalResults>>;
|
|
143
|
+
_log: LogFunction;
|
|
144
|
+
_executeId: number;
|
|
132
145
|
_nextSubscriptionId: number;
|
|
133
146
|
_operation: OperationDescriptor;
|
|
134
147
|
_operationExecutions: Map<string, ActiveState>;
|
|
@@ -137,7 +150,7 @@ class Executor {
|
|
|
137
150
|
_operationUpdateEpochs: Map<string, number>;
|
|
138
151
|
_optimisticUpdates: null | Array<OptimisticUpdate>;
|
|
139
152
|
_pendingModulePayloadsCount: number;
|
|
140
|
-
|
|
153
|
+
+_getPublishQueue: (actorIdentifier: ActorIdentifier) => PublishQueue;
|
|
141
154
|
_reactFlightPayloadDeserializer: ?ReactFlightPayloadDeserializer;
|
|
142
155
|
_reactFlightServerErrorHandler: ?ReactFlightServerErrorHandler;
|
|
143
156
|
_shouldProcessClientComponents: ?boolean;
|
|
@@ -148,36 +161,44 @@ class Executor {
|
|
|
148
161
|
{|+record: Record, +fieldPayloads: Array<HandleFieldPayload>|},
|
|
149
162
|
>;
|
|
150
163
|
_state: 'started' | 'loading_incremental' | 'loading_final' | 'completed';
|
|
151
|
-
|
|
164
|
+
+_getStore: (actorIdentifier: ActorIdentifier) => Store;
|
|
152
165
|
_subscriptions: Map<number, Subscription>;
|
|
153
166
|
_updater: ?SelectorStoreUpdater;
|
|
154
|
-
|
|
167
|
+
_asyncStoreUpdateDisposable: ?Disposable;
|
|
168
|
+
_completeFns: Array<() => void>;
|
|
169
|
+
+_retainDisposables: Map<ActorIdentifier, Disposable>;
|
|
155
170
|
+_isClientPayload: boolean;
|
|
156
171
|
+_isSubscriptionOperation: boolean;
|
|
172
|
+
+_seenActors: Set<ActorIdentifier>;
|
|
157
173
|
|
|
158
174
|
constructor({
|
|
175
|
+
actorIdentifier,
|
|
176
|
+
getDataID,
|
|
177
|
+
getPublishQueue,
|
|
178
|
+
getStore,
|
|
179
|
+
isClientPayload,
|
|
159
180
|
operation,
|
|
160
181
|
operationExecutions,
|
|
161
182
|
operationLoader,
|
|
183
|
+
operationTracker,
|
|
162
184
|
optimisticConfig,
|
|
163
|
-
|
|
185
|
+
reactFlightPayloadDeserializer,
|
|
186
|
+
reactFlightServerErrorHandler,
|
|
164
187
|
scheduler,
|
|
188
|
+
shouldProcessClientComponents,
|
|
165
189
|
sink,
|
|
166
190
|
source,
|
|
167
|
-
store,
|
|
168
|
-
updater,
|
|
169
|
-
operationTracker,
|
|
170
191
|
treatMissingFieldsAsNull,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
reactFlightPayloadDeserializer,
|
|
174
|
-
reactFlightServerErrorHandler,
|
|
175
|
-
shouldProcessClientComponents,
|
|
192
|
+
updater,
|
|
193
|
+
log,
|
|
176
194
|
}: ExecuteConfig): void {
|
|
195
|
+
this._actorIdentifier = actorIdentifier;
|
|
177
196
|
this._getDataID = getDataID;
|
|
178
197
|
this._treatMissingFieldsAsNull = treatMissingFieldsAsNull;
|
|
179
198
|
this._incrementalPayloadsPending = false;
|
|
180
199
|
this._incrementalResults = new Map();
|
|
200
|
+
this._log = log;
|
|
201
|
+
this._executeId = generateID();
|
|
181
202
|
this._nextSubscriptionId = 0;
|
|
182
203
|
this._operation = operation;
|
|
183
204
|
this._operationExecutions = operationExecutions;
|
|
@@ -186,12 +207,12 @@ class Executor {
|
|
|
186
207
|
this._operationUpdateEpochs = new Map();
|
|
187
208
|
this._optimisticUpdates = null;
|
|
188
209
|
this._pendingModulePayloadsCount = 0;
|
|
189
|
-
this.
|
|
210
|
+
this._getPublishQueue = getPublishQueue;
|
|
190
211
|
this._scheduler = scheduler;
|
|
191
212
|
this._sink = sink;
|
|
192
213
|
this._source = new Map();
|
|
193
214
|
this._state = 'started';
|
|
194
|
-
this.
|
|
215
|
+
this._getStore = getStore;
|
|
195
216
|
this._subscriptions = new Map();
|
|
196
217
|
this._updater = updater;
|
|
197
218
|
this._isClientPayload = isClientPayload === true;
|
|
@@ -200,6 +221,9 @@ class Executor {
|
|
|
200
221
|
this._isSubscriptionOperation =
|
|
201
222
|
this._operation.request.node.params.operationKind === 'subscription';
|
|
202
223
|
this._shouldProcessClientComponents = shouldProcessClientComponents;
|
|
224
|
+
this._retainDisposables = new Map();
|
|
225
|
+
this._seenActors = new Set();
|
|
226
|
+
this._completeFns = [];
|
|
203
227
|
|
|
204
228
|
const id = this._nextSubscriptionId++;
|
|
205
229
|
source.subscribe({
|
|
@@ -212,7 +236,16 @@ class Executor {
|
|
|
212
236
|
sink.error(error);
|
|
213
237
|
}
|
|
214
238
|
},
|
|
215
|
-
start: subscription =>
|
|
239
|
+
start: subscription => {
|
|
240
|
+
this._start(id, subscription);
|
|
241
|
+
this._log({
|
|
242
|
+
name: 'execute.start',
|
|
243
|
+
executeId: this._executeId,
|
|
244
|
+
params: this._operation.request.node.params,
|
|
245
|
+
variables: this._operation.request.variables,
|
|
246
|
+
cacheConfig: this._operation.request.cacheConfig ?? {},
|
|
247
|
+
});
|
|
248
|
+
},
|
|
216
249
|
});
|
|
217
250
|
|
|
218
251
|
if (optimisticConfig != null) {
|
|
@@ -242,19 +275,41 @@ class Executor {
|
|
|
242
275
|
if (optimisticUpdates !== null) {
|
|
243
276
|
this._optimisticUpdates = null;
|
|
244
277
|
optimisticUpdates.forEach(update =>
|
|
245
|
-
this.
|
|
278
|
+
this._getPublishQueueAndSaveActor().revertUpdate(update),
|
|
246
279
|
);
|
|
247
280
|
// OK: run revert on cancel
|
|
248
|
-
this.
|
|
281
|
+
this._runPublishQueue();
|
|
249
282
|
}
|
|
250
283
|
this._incrementalResults.clear();
|
|
251
|
-
this.
|
|
252
|
-
|
|
253
|
-
this.
|
|
254
|
-
this._retainDisposable = null;
|
|
284
|
+
if (this._asyncStoreUpdateDisposable != null) {
|
|
285
|
+
this._asyncStoreUpdateDisposable.dispose();
|
|
286
|
+
this._asyncStoreUpdateDisposable = null;
|
|
255
287
|
}
|
|
288
|
+
this._completeFns = [];
|
|
289
|
+
this._completeOperationTracker();
|
|
290
|
+
this._disposeRetainedData();
|
|
256
291
|
}
|
|
257
292
|
|
|
293
|
+
_deserializeReactFlightPayloadWithLogging = (
|
|
294
|
+
tree: ReactFlightServerTree,
|
|
295
|
+
): ReactFlightClientResponse => {
|
|
296
|
+
const reactFlightPayloadDeserializer = this._reactFlightPayloadDeserializer;
|
|
297
|
+
invariant(
|
|
298
|
+
typeof reactFlightPayloadDeserializer === 'function',
|
|
299
|
+
'OperationExecutor: Expected reactFlightPayloadDeserializer to be available when calling _deserializeReactFlightPayloadWithLogging.',
|
|
300
|
+
);
|
|
301
|
+
const [duration, result] = withDuration(() => {
|
|
302
|
+
return reactFlightPayloadDeserializer(tree);
|
|
303
|
+
});
|
|
304
|
+
this._log({
|
|
305
|
+
name: 'execute.flight.payload_deserialize',
|
|
306
|
+
executeId: this._executeId,
|
|
307
|
+
operationName: this._operation.request.node.params.name,
|
|
308
|
+
duration,
|
|
309
|
+
});
|
|
310
|
+
return result;
|
|
311
|
+
};
|
|
312
|
+
|
|
258
313
|
_updateActiveState(): void {
|
|
259
314
|
let activeState;
|
|
260
315
|
switch (this._state) {
|
|
@@ -314,12 +369,21 @@ class Executor {
|
|
|
314
369
|
if (this._subscriptions.size === 0) {
|
|
315
370
|
this.cancel();
|
|
316
371
|
this._sink.complete();
|
|
372
|
+
this._log({
|
|
373
|
+
name: 'execute.complete',
|
|
374
|
+
executeId: this._executeId,
|
|
375
|
+
});
|
|
317
376
|
}
|
|
318
377
|
}
|
|
319
378
|
|
|
320
379
|
_error(error: Error): void {
|
|
321
380
|
this.cancel();
|
|
322
381
|
this._sink.error(error);
|
|
382
|
+
this._log({
|
|
383
|
+
name: 'execute.error',
|
|
384
|
+
executeId: this._executeId,
|
|
385
|
+
error,
|
|
386
|
+
});
|
|
323
387
|
}
|
|
324
388
|
|
|
325
389
|
_start(id: number, subscription: Subscription): void {
|
|
@@ -330,8 +394,16 @@ class Executor {
|
|
|
330
394
|
// Handle a raw GraphQL response.
|
|
331
395
|
_next(_id: number, response: GraphQLResponse): void {
|
|
332
396
|
this._schedule(() => {
|
|
333
|
-
|
|
334
|
-
|
|
397
|
+
const [duration] = withDuration(() => {
|
|
398
|
+
this._handleNext(response);
|
|
399
|
+
this._maybeCompleteSubscriptionOperationTracking();
|
|
400
|
+
});
|
|
401
|
+
this._log({
|
|
402
|
+
name: 'execute.next',
|
|
403
|
+
executeId: this._executeId,
|
|
404
|
+
response,
|
|
405
|
+
duration,
|
|
406
|
+
});
|
|
335
407
|
});
|
|
336
408
|
}
|
|
337
409
|
|
|
@@ -395,7 +467,10 @@ class Executor {
|
|
|
395
467
|
responsePart => responsePart.extensions?.isOptimistic === true,
|
|
396
468
|
)
|
|
397
469
|
) {
|
|
398
|
-
invariant(
|
|
470
|
+
invariant(
|
|
471
|
+
false,
|
|
472
|
+
'OperationExecutor: Optimistic responses cannot be batched.',
|
|
473
|
+
);
|
|
399
474
|
}
|
|
400
475
|
return false;
|
|
401
476
|
}
|
|
@@ -423,6 +498,7 @@ class Executor {
|
|
|
423
498
|
if (this._state === 'completed') {
|
|
424
499
|
return;
|
|
425
500
|
}
|
|
501
|
+
this._seenActors.clear();
|
|
426
502
|
|
|
427
503
|
const responses = Array.isArray(response) ? response : [response];
|
|
428
504
|
const responsesWithData = this._handleErrorResponse(responses);
|
|
@@ -460,39 +536,39 @@ class Executor {
|
|
|
460
536
|
// with the initial payload followed by some early-to-resolve incremental
|
|
461
537
|
// payloads (although, can that even happen?)
|
|
462
538
|
if (hasNonIncrementalResponses) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
539
|
+
// For subscriptions, to avoid every new payload from overwriting existing
|
|
540
|
+
// data from previous payloads, assign a unique rootID for every new
|
|
541
|
+
// non-incremental payload.
|
|
542
|
+
if (this._isSubscriptionOperation) {
|
|
543
|
+
const nextID = generateUniqueClientID();
|
|
544
|
+
this._operation = {
|
|
545
|
+
request: this._operation.request,
|
|
546
|
+
fragment: createReaderSelector(
|
|
547
|
+
this._operation.fragment.node,
|
|
548
|
+
nextID,
|
|
549
|
+
this._operation.fragment.variables,
|
|
550
|
+
this._operation.fragment.owner,
|
|
551
|
+
),
|
|
552
|
+
root: createNormalizationSelector(
|
|
553
|
+
this._operation.root.node,
|
|
554
|
+
nextID,
|
|
555
|
+
this._operation.root.variables,
|
|
556
|
+
),
|
|
557
|
+
};
|
|
468
558
|
}
|
|
469
559
|
|
|
560
|
+
const payloadFollowups = this._processResponses(nonIncrementalResponses);
|
|
470
561
|
this._processPayloadFollowups(payloadFollowups);
|
|
471
|
-
|
|
472
|
-
if (!RelayFeatureFlags.ENABLE_BATCHED_STORE_UPDATES) {
|
|
473
|
-
if (this._incrementalPayloadsPending && !this._retainDisposable) {
|
|
474
|
-
this._retainDisposable = this._store.retain(this._operation);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
562
|
}
|
|
478
563
|
|
|
479
564
|
if (incrementalResponses.length > 0) {
|
|
480
565
|
const payloadFollowups = this._processIncrementalResponses(
|
|
481
566
|
incrementalResponses,
|
|
482
567
|
);
|
|
483
|
-
|
|
484
|
-
// For the incremental case, we're only handling follow-up responses
|
|
485
|
-
// for already initiated operation (and we're not passing it to
|
|
486
|
-
// the run(...) call)
|
|
487
|
-
const updatedOwners = this._publishQueue.run();
|
|
488
|
-
this._updateOperationTracker(updatedOwners);
|
|
489
|
-
}
|
|
568
|
+
|
|
490
569
|
this._processPayloadFollowups(payloadFollowups);
|
|
491
570
|
}
|
|
492
|
-
if (
|
|
493
|
-
this._isSubscriptionOperation &&
|
|
494
|
-
RelayFeatureFlags.ENABLE_UNIQUE_SUBSCRIPTION_ROOT
|
|
495
|
-
) {
|
|
571
|
+
if (this._isSubscriptionOperation) {
|
|
496
572
|
// We attach the id to allow the `requestSubscription` to read from the store using
|
|
497
573
|
// the current id in its `onNext` callback
|
|
498
574
|
if (responsesWithData[0].extensions == null) {
|
|
@@ -505,21 +581,20 @@ class Executor {
|
|
|
505
581
|
}
|
|
506
582
|
}
|
|
507
583
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
584
|
+
// OK: run once after each new payload
|
|
585
|
+
// If we have non-incremental responses, we passing `this._operation` to
|
|
586
|
+
// the publish queue here, which will later be passed to the store (via
|
|
587
|
+
// notify) to indicate that this operation caused the store to update
|
|
588
|
+
const updatedOwners = this._runPublishQueue(
|
|
589
|
+
hasNonIncrementalResponses ? this._operation : undefined,
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
if (hasNonIncrementalResponses) {
|
|
593
|
+
if (this._incrementalPayloadsPending) {
|
|
594
|
+
this._retainData();
|
|
520
595
|
}
|
|
521
|
-
this._updateOperationTracker(updatedOwners);
|
|
522
596
|
}
|
|
597
|
+
this._updateOperationTracker(updatedOwners);
|
|
523
598
|
this._sink.next(response);
|
|
524
599
|
}
|
|
525
600
|
|
|
@@ -530,7 +605,7 @@ class Executor {
|
|
|
530
605
|
): void {
|
|
531
606
|
invariant(
|
|
532
607
|
this._optimisticUpdates === null,
|
|
533
|
-
'environment.execute: only support one optimistic response per ' +
|
|
608
|
+
'OperationExecutor: environment.execute: only support one optimistic response per ' +
|
|
534
609
|
'execute.',
|
|
535
610
|
);
|
|
536
611
|
if (response == null && updater == null) {
|
|
@@ -543,9 +618,13 @@ class Executor {
|
|
|
543
618
|
this._operation.root,
|
|
544
619
|
ROOT_TYPE,
|
|
545
620
|
{
|
|
621
|
+
actorIdentifier: this._actorIdentifier,
|
|
546
622
|
getDataID: this._getDataID,
|
|
547
623
|
path: [],
|
|
548
|
-
reactFlightPayloadDeserializer:
|
|
624
|
+
reactFlightPayloadDeserializer:
|
|
625
|
+
this._reactFlightPayloadDeserializer != null
|
|
626
|
+
? this._deserializeReactFlightPayloadWithLogging
|
|
627
|
+
: null,
|
|
549
628
|
reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
|
|
550
629
|
shouldProcessClientComponents: this._shouldProcessClientComponents,
|
|
551
630
|
treatMissingFieldsAsNull,
|
|
@@ -565,7 +644,7 @@ class Executor {
|
|
|
565
644
|
errors: null,
|
|
566
645
|
fieldPayloads: null,
|
|
567
646
|
incrementalPlaceholders: null,
|
|
568
|
-
|
|
647
|
+
followupPayloads: null,
|
|
569
648
|
source: RelayRecordSource.create(),
|
|
570
649
|
isFinal: false,
|
|
571
650
|
},
|
|
@@ -573,61 +652,92 @@ class Executor {
|
|
|
573
652
|
});
|
|
574
653
|
}
|
|
575
654
|
this._optimisticUpdates = optimisticUpdates;
|
|
576
|
-
optimisticUpdates.forEach(update =>
|
|
655
|
+
optimisticUpdates.forEach(update =>
|
|
656
|
+
this._getPublishQueueAndSaveActor().applyUpdate(update),
|
|
657
|
+
);
|
|
577
658
|
// OK: only called on construction and when receiving an optimistic payload from network,
|
|
578
659
|
// which doesn't fall-through to the regular next() handling
|
|
579
|
-
this.
|
|
660
|
+
this._runPublishQueue();
|
|
580
661
|
}
|
|
581
662
|
|
|
582
663
|
_processOptimisticFollowups(
|
|
583
664
|
payload: RelayResponsePayload,
|
|
584
665
|
optimisticUpdates: Array<OptimisticUpdate>,
|
|
585
666
|
): void {
|
|
586
|
-
if (payload.
|
|
587
|
-
const
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
667
|
+
if (payload.followupPayloads && payload.followupPayloads.length) {
|
|
668
|
+
const followupPayloads = payload.followupPayloads;
|
|
669
|
+
for (const followupPayload of followupPayloads) {
|
|
670
|
+
switch (followupPayload.kind) {
|
|
671
|
+
case 'ModuleImportPayload':
|
|
672
|
+
const operationLoader = this._expectOperationLoader();
|
|
673
|
+
const operation = operationLoader.get(
|
|
674
|
+
followupPayload.operationReference,
|
|
675
|
+
);
|
|
676
|
+
if (operation == null) {
|
|
677
|
+
this._processAsyncOptimisticModuleImport(followupPayload);
|
|
678
|
+
} else {
|
|
679
|
+
const moduleImportOptimisticUpdates = this._processOptimisticModuleImport(
|
|
680
|
+
operation,
|
|
681
|
+
followupPayload,
|
|
682
|
+
);
|
|
683
|
+
optimisticUpdates.push(...moduleImportOptimisticUpdates);
|
|
684
|
+
}
|
|
685
|
+
break;
|
|
686
|
+
case 'ActorPayload':
|
|
687
|
+
warning(
|
|
688
|
+
false,
|
|
689
|
+
'OperationExecutor: Unexpected optimistic ActorPayload. These updates are not supported.',
|
|
690
|
+
);
|
|
691
|
+
break;
|
|
692
|
+
default:
|
|
693
|
+
(followupPayload: empty);
|
|
694
|
+
invariant(
|
|
695
|
+
false,
|
|
696
|
+
'OperationExecutor: Unexpected followup kind `%s`. when processing optimistic updates.',
|
|
697
|
+
followupPayload.kind,
|
|
698
|
+
);
|
|
609
699
|
}
|
|
610
700
|
}
|
|
611
701
|
}
|
|
612
702
|
}
|
|
613
703
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
704
|
+
/**
|
|
705
|
+
* Normalize Data for @module payload, and actor-specific payload
|
|
706
|
+
*/
|
|
707
|
+
_normalizeFollowupPayload(
|
|
708
|
+
followupPayload: FollowupPayload,
|
|
709
|
+
normalizationNode: NormalizationSelectableNode,
|
|
617
710
|
) {
|
|
711
|
+
let variables;
|
|
712
|
+
if (
|
|
713
|
+
normalizationNode.kind === 'SplitOperation' &&
|
|
714
|
+
followupPayload.kind === 'ModuleImportPayload'
|
|
715
|
+
) {
|
|
716
|
+
variables = getLocalVariables(
|
|
717
|
+
followupPayload.variables,
|
|
718
|
+
normalizationNode.argumentDefinitions,
|
|
719
|
+
followupPayload.args,
|
|
720
|
+
);
|
|
721
|
+
} else {
|
|
722
|
+
variables = followupPayload.variables;
|
|
723
|
+
}
|
|
618
724
|
const selector = createNormalizationSelector(
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
725
|
+
normalizationNode,
|
|
726
|
+
followupPayload.dataID,
|
|
727
|
+
variables,
|
|
622
728
|
);
|
|
623
729
|
return normalizeResponse(
|
|
624
|
-
{data:
|
|
730
|
+
{data: followupPayload.data},
|
|
625
731
|
selector,
|
|
626
|
-
|
|
732
|
+
followupPayload.typeName,
|
|
627
733
|
{
|
|
734
|
+
actorIdentifier: this._actorIdentifier,
|
|
628
735
|
getDataID: this._getDataID,
|
|
629
|
-
path:
|
|
630
|
-
reactFlightPayloadDeserializer:
|
|
736
|
+
path: followupPayload.path,
|
|
737
|
+
reactFlightPayloadDeserializer:
|
|
738
|
+
this._reactFlightPayloadDeserializer != null
|
|
739
|
+
? this._deserializeReactFlightPayloadWithLogging
|
|
740
|
+
: null,
|
|
631
741
|
reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
|
|
632
742
|
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
|
|
633
743
|
shouldProcessClientComponents: this._shouldProcessClientComponents,
|
|
@@ -641,7 +751,7 @@ class Executor {
|
|
|
641
751
|
): $ReadOnlyArray<OptimisticUpdate> {
|
|
642
752
|
const operation = getOperation(normalizationRootNode);
|
|
643
753
|
const optimisticUpdates = [];
|
|
644
|
-
const modulePayload = this.
|
|
754
|
+
const modulePayload = this._normalizeFollowupPayload(
|
|
645
755
|
moduleImportPayload,
|
|
646
756
|
operation,
|
|
647
757
|
);
|
|
@@ -656,10 +766,9 @@ class Executor {
|
|
|
656
766
|
}
|
|
657
767
|
|
|
658
768
|
_processAsyncOptimisticModuleImport(
|
|
659
|
-
operationLoader: OperationLoader,
|
|
660
769
|
moduleImportPayload: ModuleImportPayload,
|
|
661
770
|
): void {
|
|
662
|
-
|
|
771
|
+
this._expectOperationLoader()
|
|
663
772
|
.load(moduleImportPayload.operationReference)
|
|
664
773
|
.then(operation => {
|
|
665
774
|
if (operation == null || this._state !== 'started') {
|
|
@@ -670,7 +779,7 @@ class Executor {
|
|
|
670
779
|
moduleImportPayload,
|
|
671
780
|
);
|
|
672
781
|
moduleImportOptimisticUpdates.forEach(update =>
|
|
673
|
-
this.
|
|
782
|
+
this._getPublishQueueAndSaveActor().applyUpdate(update),
|
|
674
783
|
);
|
|
675
784
|
if (this._optimisticUpdates == null) {
|
|
676
785
|
warning(
|
|
@@ -682,16 +791,18 @@ class Executor {
|
|
|
682
791
|
} else {
|
|
683
792
|
this._optimisticUpdates.push(...moduleImportOptimisticUpdates);
|
|
684
793
|
// OK: always have to run() after an module import resolves async
|
|
685
|
-
this.
|
|
794
|
+
this._runPublishQueue();
|
|
686
795
|
}
|
|
687
796
|
});
|
|
688
797
|
}
|
|
689
798
|
|
|
690
|
-
_processResponses(
|
|
799
|
+
_processResponses(
|
|
800
|
+
responses: $ReadOnlyArray<GraphQLResponseWithData>,
|
|
801
|
+
): $ReadOnlyArray<RelayResponsePayload> {
|
|
691
802
|
if (this._optimisticUpdates !== null) {
|
|
692
|
-
this._optimisticUpdates.forEach(update =>
|
|
693
|
-
this.
|
|
694
|
-
);
|
|
803
|
+
this._optimisticUpdates.forEach(update => {
|
|
804
|
+
this._getPublishQueueAndSaveActor().revertUpdate(update);
|
|
805
|
+
});
|
|
695
806
|
this._optimisticUpdates = null;
|
|
696
807
|
}
|
|
697
808
|
|
|
@@ -704,19 +815,24 @@ class Executor {
|
|
|
704
815
|
this._operation.root,
|
|
705
816
|
ROOT_TYPE,
|
|
706
817
|
{
|
|
818
|
+
actorIdentifier: this._actorIdentifier,
|
|
707
819
|
getDataID: this._getDataID,
|
|
708
820
|
path: [],
|
|
709
|
-
reactFlightPayloadDeserializer:
|
|
821
|
+
reactFlightPayloadDeserializer:
|
|
822
|
+
this._reactFlightPayloadDeserializer != null
|
|
823
|
+
? this._deserializeReactFlightPayloadWithLogging
|
|
824
|
+
: null,
|
|
710
825
|
reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
|
|
711
826
|
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
|
|
712
827
|
shouldProcessClientComponents: this._shouldProcessClientComponents,
|
|
713
828
|
},
|
|
714
829
|
);
|
|
715
|
-
this.
|
|
830
|
+
this._getPublishQueueAndSaveActor().commitPayload(
|
|
716
831
|
this._operation,
|
|
717
832
|
relayPayload,
|
|
718
833
|
this._updater,
|
|
719
834
|
);
|
|
835
|
+
|
|
720
836
|
return relayPayload;
|
|
721
837
|
});
|
|
722
838
|
}
|
|
@@ -732,30 +848,30 @@ class Executor {
|
|
|
732
848
|
return;
|
|
733
849
|
}
|
|
734
850
|
payloads.forEach(payload => {
|
|
735
|
-
const {incrementalPlaceholders,
|
|
851
|
+
const {incrementalPlaceholders, followupPayloads, isFinal} = payload;
|
|
736
852
|
this._state = isFinal ? 'loading_final' : 'loading_incremental';
|
|
737
853
|
this._updateActiveState();
|
|
738
854
|
if (isFinal) {
|
|
739
855
|
this._incrementalPayloadsPending = false;
|
|
740
856
|
}
|
|
741
|
-
if (
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
moduleImportPayloads.forEach(moduleImportPayload => {
|
|
749
|
-
this._processModuleImportPayload(
|
|
750
|
-
moduleImportPayload,
|
|
751
|
-
operationLoader,
|
|
752
|
-
);
|
|
857
|
+
if (followupPayloads && followupPayloads.length !== 0) {
|
|
858
|
+
followupPayloads.forEach(followupPayload => {
|
|
859
|
+
const prevActorIdentifier = this._actorIdentifier;
|
|
860
|
+
this._actorIdentifier =
|
|
861
|
+
followupPayload.actorIdentifier ?? this._actorIdentifier;
|
|
862
|
+
this._processFollowupPayload(followupPayload);
|
|
863
|
+
this._actorIdentifier = prevActorIdentifier;
|
|
753
864
|
});
|
|
754
865
|
}
|
|
866
|
+
|
|
755
867
|
if (incrementalPlaceholders && incrementalPlaceholders.length !== 0) {
|
|
756
868
|
this._incrementalPayloadsPending = this._state !== 'loading_final';
|
|
757
869
|
incrementalPlaceholders.forEach(incrementalPlaceholder => {
|
|
870
|
+
const prevActorIdentifier = this._actorIdentifier;
|
|
871
|
+
this._actorIdentifier =
|
|
872
|
+
incrementalPlaceholder.actorIdentifier ?? this._actorIdentifier;
|
|
758
873
|
this._processIncrementalPlaceholder(payload, incrementalPlaceholder);
|
|
874
|
+
this._actorIdentifier = prevActorIdentifier;
|
|
759
875
|
});
|
|
760
876
|
|
|
761
877
|
if (this._isClientPayload || this._state === 'loading_final') {
|
|
@@ -787,10 +903,6 @@ class Executor {
|
|
|
787
903
|
}
|
|
788
904
|
});
|
|
789
905
|
if (relayPayloads.length > 0) {
|
|
790
|
-
if (!RelayFeatureFlags.ENABLE_BATCHED_STORE_UPDATES) {
|
|
791
|
-
const updatedOwners = this._publishQueue.run();
|
|
792
|
-
this._updateOperationTracker(updatedOwners);
|
|
793
|
-
}
|
|
794
906
|
this._processPayloadFollowups(relayPayloads);
|
|
795
907
|
}
|
|
796
908
|
}
|
|
@@ -808,23 +920,6 @@ class Executor {
|
|
|
808
920
|
) {
|
|
809
921
|
this._completeOperationTracker();
|
|
810
922
|
}
|
|
811
|
-
if (RelayFeatureFlags.ENABLE_UNIQUE_SUBSCRIPTION_ROOT) {
|
|
812
|
-
const nextID = generateUniqueClientID();
|
|
813
|
-
this._operation = {
|
|
814
|
-
request: this._operation.request,
|
|
815
|
-
fragment: createReaderSelector(
|
|
816
|
-
this._operation.fragment.node,
|
|
817
|
-
nextID,
|
|
818
|
-
this._operation.fragment.variables,
|
|
819
|
-
this._operation.fragment.owner,
|
|
820
|
-
),
|
|
821
|
-
root: createNormalizationSelector(
|
|
822
|
-
this._operation.root.node,
|
|
823
|
-
nextID,
|
|
824
|
-
this._operation.root.variables,
|
|
825
|
-
),
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
923
|
}
|
|
829
924
|
|
|
830
925
|
/**
|
|
@@ -834,78 +929,154 @@ class Executor {
|
|
|
834
929
|
* defer, stream, etc); these are handled by calling
|
|
835
930
|
* `_processPayloadFollowups()`.
|
|
836
931
|
*/
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
932
|
+
_processFollowupPayload(followupPayload: FollowupPayload): void {
|
|
933
|
+
switch (followupPayload.kind) {
|
|
934
|
+
case 'ModuleImportPayload':
|
|
935
|
+
const operationLoader = this._expectOperationLoader();
|
|
936
|
+
const node = operationLoader.get(followupPayload.operationReference);
|
|
937
|
+
if (node != null) {
|
|
938
|
+
// If the operation module is available synchronously, normalize the
|
|
939
|
+
// data synchronously.
|
|
940
|
+
this._processFollowupPayloadWithNormalizationNode(
|
|
941
|
+
followupPayload,
|
|
942
|
+
getOperation(node),
|
|
943
|
+
);
|
|
944
|
+
} else {
|
|
945
|
+
// Otherwise load the operation module and schedule a task to normalize
|
|
946
|
+
// the data when the module is available.
|
|
947
|
+
const id = this._nextSubscriptionId++;
|
|
948
|
+
this._pendingModulePayloadsCount++;
|
|
949
|
+
|
|
950
|
+
const decrementPendingCount = () => {
|
|
951
|
+
this._pendingModulePayloadsCount--;
|
|
952
|
+
this._maybeCompleteSubscriptionOperationTracking();
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
// Observable.from(operationLoader.load()) wouldn't catch synchronous
|
|
956
|
+
// errors thrown by the load function, which is user-defined. Guard
|
|
957
|
+
// against that with Observable.from(new Promise(<work>)).
|
|
958
|
+
const networkObservable = RelayObservable.from(
|
|
959
|
+
new Promise((resolve, reject) => {
|
|
960
|
+
operationLoader
|
|
961
|
+
.load(followupPayload.operationReference)
|
|
962
|
+
.then(resolve, reject);
|
|
963
|
+
}),
|
|
964
|
+
);
|
|
965
|
+
RelayObservable.create(sink => {
|
|
966
|
+
let cancellationToken;
|
|
967
|
+
const subscription = networkObservable.subscribe({
|
|
968
|
+
next: (loadedNode: ?NormalizationRootNode) => {
|
|
969
|
+
if (loadedNode != null) {
|
|
970
|
+
const publishModuleImportPayload = () => {
|
|
971
|
+
try {
|
|
972
|
+
const operation = getOperation(loadedNode);
|
|
973
|
+
const batchAsyncModuleUpdatesFN =
|
|
974
|
+
RelayFeatureFlags.BATCH_ASYNC_MODULE_UPDATES_FN;
|
|
975
|
+
const shouldScheduleAsyncStoreUpdate =
|
|
976
|
+
batchAsyncModuleUpdatesFN != null &&
|
|
977
|
+
this._pendingModulePayloadsCount > 1;
|
|
978
|
+
const [duration] = withDuration(() => {
|
|
979
|
+
this._handleFollowupPayload(followupPayload, operation);
|
|
980
|
+
// OK: always have to run after an async module import resolves
|
|
981
|
+
if (shouldScheduleAsyncStoreUpdate) {
|
|
982
|
+
this._scheduleAsyncStoreUpdate(
|
|
983
|
+
// $FlowFixMe[incompatible-call] `shouldScheduleAsyncStoreUpdate` check should cover `null` case
|
|
984
|
+
batchAsyncModuleUpdatesFN,
|
|
985
|
+
sink.complete,
|
|
986
|
+
);
|
|
987
|
+
} else {
|
|
988
|
+
const updatedOwners = this._runPublishQueue();
|
|
989
|
+
this._updateOperationTracker(updatedOwners);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
this._log({
|
|
993
|
+
name: 'execute.async.module',
|
|
994
|
+
executeId: this._executeId,
|
|
995
|
+
operationName: operation.name,
|
|
996
|
+
duration,
|
|
997
|
+
});
|
|
998
|
+
if (!shouldScheduleAsyncStoreUpdate) {
|
|
999
|
+
sink.complete();
|
|
1000
|
+
}
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
sink.error(error);
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
const scheduler = this._scheduler;
|
|
1006
|
+
if (scheduler == null) {
|
|
1007
|
+
publishModuleImportPayload();
|
|
1008
|
+
} else {
|
|
1009
|
+
cancellationToken = scheduler.schedule(
|
|
1010
|
+
publishModuleImportPayload,
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
} else {
|
|
1014
|
+
sink.complete();
|
|
1015
|
+
}
|
|
1016
|
+
},
|
|
1017
|
+
error: sink.error,
|
|
879
1018
|
});
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1019
|
+
return () => {
|
|
1020
|
+
subscription.unsubscribe();
|
|
1021
|
+
if (this._scheduler != null && cancellationToken != null) {
|
|
1022
|
+
this._scheduler.cancel(cancellationToken);
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
}).subscribe({
|
|
1026
|
+
complete: () => {
|
|
1027
|
+
this._complete(id);
|
|
1028
|
+
decrementPendingCount();
|
|
1029
|
+
},
|
|
1030
|
+
error: error => {
|
|
1031
|
+
this._error(error);
|
|
1032
|
+
decrementPendingCount();
|
|
1033
|
+
},
|
|
1034
|
+
start: subscription => this._start(id, subscription),
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
break;
|
|
1038
|
+
case 'ActorPayload':
|
|
1039
|
+
this._processFollowupPayloadWithNormalizationNode(
|
|
1040
|
+
followupPayload,
|
|
1041
|
+
followupPayload.node,
|
|
1042
|
+
);
|
|
1043
|
+
break;
|
|
1044
|
+
default:
|
|
1045
|
+
(followupPayload: empty);
|
|
1046
|
+
invariant(
|
|
1047
|
+
false,
|
|
1048
|
+
'OperationExecutor: Unexpected followup kind `%s`.',
|
|
1049
|
+
followupPayload.kind,
|
|
1050
|
+
);
|
|
893
1051
|
}
|
|
894
1052
|
}
|
|
895
1053
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1054
|
+
_processFollowupPayloadWithNormalizationNode(
|
|
1055
|
+
followupPayload: FollowupPayload,
|
|
1056
|
+
normalizationNode:
|
|
1057
|
+
| NormalizationSplitOperation
|
|
1058
|
+
| NormalizationOperation
|
|
1059
|
+
| NormalizationLinkedField,
|
|
1060
|
+
) {
|
|
1061
|
+
this._handleFollowupPayload(followupPayload, normalizationNode);
|
|
1062
|
+
this._maybeCompleteSubscriptionOperationTracking();
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
_handleFollowupPayload(
|
|
1066
|
+
followupPayload: FollowupPayload,
|
|
1067
|
+
normalizationNode:
|
|
1068
|
+
| NormalizationSplitOperation
|
|
1069
|
+
| NormalizationOperation
|
|
1070
|
+
| NormalizationLinkedField,
|
|
899
1071
|
): void {
|
|
900
|
-
const relayPayload = this.
|
|
901
|
-
|
|
902
|
-
|
|
1072
|
+
const relayPayload = this._normalizeFollowupPayload(
|
|
1073
|
+
followupPayload,
|
|
1074
|
+
normalizationNode,
|
|
1075
|
+
);
|
|
1076
|
+
this._getPublishQueueAndSaveActor().commitPayload(
|
|
1077
|
+
this._operation,
|
|
1078
|
+
relayPayload,
|
|
903
1079
|
);
|
|
904
|
-
this._publishQueue.commitPayload(this._operation, relayPayload);
|
|
905
|
-
if (!RelayFeatureFlags.ENABLE_BATCHED_STORE_UPDATES) {
|
|
906
|
-
const updatedOwners = this._publishQueue.run();
|
|
907
|
-
this._updateOperationTracker(updatedOwners);
|
|
908
|
-
}
|
|
909
1080
|
this._processPayloadFollowups([relayPayload]);
|
|
910
1081
|
}
|
|
911
1082
|
|
|
@@ -951,7 +1122,7 @@ class Executor {
|
|
|
951
1122
|
(placeholder: empty);
|
|
952
1123
|
invariant(
|
|
953
1124
|
false,
|
|
954
|
-
'Unsupported incremental placeholder kind `%s`.',
|
|
1125
|
+
'OperationExecutor: Unsupported incremental placeholder kind `%s`.',
|
|
955
1126
|
placeholder.kind,
|
|
956
1127
|
);
|
|
957
1128
|
}
|
|
@@ -975,7 +1146,7 @@ class Executor {
|
|
|
975
1146
|
// exist.
|
|
976
1147
|
invariant(
|
|
977
1148
|
parentRecord != null,
|
|
978
|
-
'
|
|
1149
|
+
'OperationExecutor: Expected record `%s` to exist.',
|
|
979
1150
|
parentID,
|
|
980
1151
|
);
|
|
981
1152
|
let nextParentRecord;
|
|
@@ -1004,16 +1175,13 @@ class Executor {
|
|
|
1004
1175
|
record: nextParentRecord,
|
|
1005
1176
|
fieldPayloads: nextParentPayloads,
|
|
1006
1177
|
});
|
|
1178
|
+
|
|
1007
1179
|
// If there were any queued responses, process them now that placeholders
|
|
1008
1180
|
// are in place
|
|
1009
1181
|
if (pendingResponses != null) {
|
|
1010
1182
|
const payloadFollowups = this._processIncrementalResponses(
|
|
1011
1183
|
pendingResponses,
|
|
1012
1184
|
);
|
|
1013
|
-
if (!RelayFeatureFlags.ENABLE_BATCHED_STORE_UPDATES) {
|
|
1014
|
-
const updatedOwners = this._publishQueue.run();
|
|
1015
|
-
this._updateOperationTracker(updatedOwners);
|
|
1016
|
-
}
|
|
1017
1185
|
this._processPayloadFollowups(payloadFollowups);
|
|
1018
1186
|
}
|
|
1019
1187
|
}
|
|
@@ -1049,7 +1217,7 @@ class Executor {
|
|
|
1049
1217
|
const placeholder = resultForPath.placeholder;
|
|
1050
1218
|
invariant(
|
|
1051
1219
|
placeholder.kind === 'defer',
|
|
1052
|
-
'
|
|
1220
|
+
'OperationExecutor: Expected data for path `%s` for label `%s` ' +
|
|
1053
1221
|
'to be data for @defer, was `@%s`.',
|
|
1054
1222
|
pathKey,
|
|
1055
1223
|
label,
|
|
@@ -1079,7 +1247,7 @@ class Executor {
|
|
|
1079
1247
|
const placeholder = resultForPath.placeholder;
|
|
1080
1248
|
invariant(
|
|
1081
1249
|
placeholder.kind === 'stream',
|
|
1082
|
-
'
|
|
1250
|
+
'OperationExecutor: Expected data for path `%s` for label `%s` ' +
|
|
1083
1251
|
'to be data for @stream, was `@%s`.',
|
|
1084
1252
|
pathKey,
|
|
1085
1253
|
label,
|
|
@@ -1100,27 +1268,37 @@ class Executor {
|
|
|
1100
1268
|
response: GraphQLResponseWithData,
|
|
1101
1269
|
): RelayResponsePayload {
|
|
1102
1270
|
const {dataID: parentID} = placeholder.selector;
|
|
1271
|
+
const prevActorIdentifier = this._actorIdentifier;
|
|
1272
|
+
this._actorIdentifier =
|
|
1273
|
+
placeholder.actorIdentifier ?? this._actorIdentifier;
|
|
1103
1274
|
const relayPayload = normalizeResponse(
|
|
1104
1275
|
response,
|
|
1105
1276
|
placeholder.selector,
|
|
1106
1277
|
placeholder.typeName,
|
|
1107
1278
|
{
|
|
1279
|
+
actorIdentifier: this._actorIdentifier,
|
|
1108
1280
|
getDataID: this._getDataID,
|
|
1109
1281
|
path: placeholder.path,
|
|
1110
|
-
reactFlightPayloadDeserializer:
|
|
1282
|
+
reactFlightPayloadDeserializer:
|
|
1283
|
+
this._reactFlightPayloadDeserializer != null
|
|
1284
|
+
? this._deserializeReactFlightPayloadWithLogging
|
|
1285
|
+
: null,
|
|
1111
1286
|
reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
|
|
1112
1287
|
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
|
|
1113
1288
|
shouldProcessClientComponents: this._shouldProcessClientComponents,
|
|
1114
1289
|
},
|
|
1115
1290
|
);
|
|
1116
|
-
this.
|
|
1291
|
+
this._getPublishQueueAndSaveActor().commitPayload(
|
|
1292
|
+
this._operation,
|
|
1293
|
+
relayPayload,
|
|
1294
|
+
);
|
|
1117
1295
|
|
|
1118
1296
|
// Load the version of the parent record from which this incremental data
|
|
1119
1297
|
// was derived
|
|
1120
1298
|
const parentEntry = this._source.get(parentID);
|
|
1121
1299
|
invariant(
|
|
1122
1300
|
parentEntry != null,
|
|
1123
|
-
'
|
|
1301
|
+
'OperationExecutor: Expected the parent record `%s` for @defer ' +
|
|
1124
1302
|
'data to exist.',
|
|
1125
1303
|
parentID,
|
|
1126
1304
|
);
|
|
@@ -1130,15 +1308,17 @@ class Executor {
|
|
|
1130
1308
|
errors: null,
|
|
1131
1309
|
fieldPayloads,
|
|
1132
1310
|
incrementalPlaceholders: null,
|
|
1133
|
-
|
|
1311
|
+
followupPayloads: null,
|
|
1134
1312
|
source: RelayRecordSource.create(),
|
|
1135
1313
|
isFinal: response.extensions?.is_final === true,
|
|
1136
1314
|
};
|
|
1137
|
-
this.
|
|
1315
|
+
this._getPublishQueueAndSaveActor().commitPayload(
|
|
1138
1316
|
this._operation,
|
|
1139
1317
|
handleFieldsRelayPayload,
|
|
1140
1318
|
);
|
|
1141
1319
|
}
|
|
1320
|
+
|
|
1321
|
+
this._actorIdentifier = prevActorIdentifier;
|
|
1142
1322
|
return relayPayload;
|
|
1143
1323
|
}
|
|
1144
1324
|
|
|
@@ -1151,12 +1331,14 @@ class Executor {
|
|
|
1151
1331
|
placeholder: StreamPlaceholder,
|
|
1152
1332
|
response: GraphQLResponseWithData,
|
|
1153
1333
|
): RelayResponsePayload {
|
|
1154
|
-
const {parentID, node, variables} = placeholder;
|
|
1334
|
+
const {parentID, node, variables, actorIdentifier} = placeholder;
|
|
1335
|
+
const prevActorIdentifier = this._actorIdentifier;
|
|
1336
|
+
this._actorIdentifier = actorIdentifier ?? this._actorIdentifier;
|
|
1155
1337
|
// Find the LinkedField where @stream was applied
|
|
1156
1338
|
const field = node.selections[0];
|
|
1157
1339
|
invariant(
|
|
1158
1340
|
field != null && field.kind === 'LinkedField' && field.plural === true,
|
|
1159
|
-
'
|
|
1341
|
+
'OperationExecutor: Expected @stream to be used on a plural field.',
|
|
1160
1342
|
);
|
|
1161
1343
|
const {
|
|
1162
1344
|
fieldPayloads,
|
|
@@ -1176,34 +1358,38 @@ class Executor {
|
|
|
1176
1358
|
// Publish the new item and update the parent record to set
|
|
1177
1359
|
// field[index] = item *if* the parent record hasn't been concurrently
|
|
1178
1360
|
// modified.
|
|
1179
|
-
this.
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1361
|
+
this._getPublishQueueAndSaveActor().commitPayload(
|
|
1362
|
+
this._operation,
|
|
1363
|
+
relayPayload,
|
|
1364
|
+
store => {
|
|
1365
|
+
const currentParentRecord = store.get(parentID);
|
|
1366
|
+
if (currentParentRecord == null) {
|
|
1367
|
+
// parent has since been deleted, stream data is stale
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
const currentItems = currentParentRecord.getLinkedRecords(storageKey);
|
|
1371
|
+
if (currentItems == null) {
|
|
1372
|
+
// field has since been deleted, stream data is stale
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
if (
|
|
1376
|
+
currentItems.length !== prevIDs.length ||
|
|
1377
|
+
currentItems.some(
|
|
1378
|
+
(currentItem, index) =>
|
|
1379
|
+
prevIDs[index] !== (currentItem && currentItem.getDataID()),
|
|
1380
|
+
)
|
|
1381
|
+
) {
|
|
1382
|
+
// field has been modified by something other than this query,
|
|
1383
|
+
// stream data is stale
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
// parent.field has not been concurrently modified:
|
|
1387
|
+
// update `parent.field[index] = item`
|
|
1388
|
+
const nextItems = [...currentItems];
|
|
1389
|
+
nextItems[itemIndex] = store.get(itemID);
|
|
1390
|
+
currentParentRecord.setLinkedRecords(nextItems, storageKey);
|
|
1391
|
+
},
|
|
1392
|
+
);
|
|
1207
1393
|
|
|
1208
1394
|
// Now that the parent record has been updated to include the new item,
|
|
1209
1395
|
// also update any handle fields that are derived from the parent record.
|
|
@@ -1212,15 +1398,17 @@ class Executor {
|
|
|
1212
1398
|
errors: null,
|
|
1213
1399
|
fieldPayloads,
|
|
1214
1400
|
incrementalPlaceholders: null,
|
|
1215
|
-
|
|
1401
|
+
followupPayloads: null,
|
|
1216
1402
|
source: RelayRecordSource.create(),
|
|
1217
1403
|
isFinal: false,
|
|
1218
1404
|
};
|
|
1219
|
-
this.
|
|
1405
|
+
this._getPublishQueueAndSaveActor().commitPayload(
|
|
1220
1406
|
this._operation,
|
|
1221
1407
|
handleFieldsRelayPayload,
|
|
1222
1408
|
);
|
|
1223
1409
|
}
|
|
1410
|
+
|
|
1411
|
+
this._actorIdentifier = prevActorIdentifier;
|
|
1224
1412
|
return relayPayload;
|
|
1225
1413
|
}
|
|
1226
1414
|
|
|
@@ -1242,7 +1430,7 @@ class Executor {
|
|
|
1242
1430
|
const {data} = response;
|
|
1243
1431
|
invariant(
|
|
1244
1432
|
typeof data === 'object',
|
|
1245
|
-
'
|
|
1433
|
+
'OperationExecutor: Expected the GraphQL @stream payload `data` ' +
|
|
1246
1434
|
'value to be an object.',
|
|
1247
1435
|
);
|
|
1248
1436
|
const responseKey = field.alias ?? field.name;
|
|
@@ -1253,7 +1441,7 @@ class Executor {
|
|
|
1253
1441
|
const parentEntry = this._source.get(parentID);
|
|
1254
1442
|
invariant(
|
|
1255
1443
|
parentEntry != null,
|
|
1256
|
-
'
|
|
1444
|
+
'OperationExecutor: Expected the parent record `%s` for @stream ' +
|
|
1257
1445
|
'data to exist.',
|
|
1258
1446
|
parentID,
|
|
1259
1447
|
);
|
|
@@ -1268,7 +1456,7 @@ class Executor {
|
|
|
1268
1456
|
);
|
|
1269
1457
|
invariant(
|
|
1270
1458
|
prevIDs != null,
|
|
1271
|
-
'
|
|
1459
|
+
'OperationExecutor: Expected record `%s` to have fetched field ' +
|
|
1272
1460
|
'`%s` with @stream.',
|
|
1273
1461
|
parentID,
|
|
1274
1462
|
field.name,
|
|
@@ -1279,7 +1467,7 @@ class Executor {
|
|
|
1279
1467
|
const itemIndex = parseInt(finalPathEntry, 10);
|
|
1280
1468
|
invariant(
|
|
1281
1469
|
itemIndex === finalPathEntry && itemIndex >= 0,
|
|
1282
|
-
'
|
|
1470
|
+
'OperationExecutor: Expected path for @stream to end in a ' +
|
|
1283
1471
|
'positive integer index, got `%s`',
|
|
1284
1472
|
finalPathEntry,
|
|
1285
1473
|
);
|
|
@@ -1287,7 +1475,7 @@ class Executor {
|
|
|
1287
1475
|
const typeName = field.concreteType ?? data[TYPENAME_KEY];
|
|
1288
1476
|
invariant(
|
|
1289
1477
|
typeof typeName === 'string',
|
|
1290
|
-
'
|
|
1478
|
+
'OperationExecutor: Expected @stream field `%s` to have a ' +
|
|
1291
1479
|
'__typename.',
|
|
1292
1480
|
field.name,
|
|
1293
1481
|
);
|
|
@@ -1302,7 +1490,7 @@ class Executor {
|
|
|
1302
1490
|
generateClientID(parentID, storageKey, itemIndex);
|
|
1303
1491
|
invariant(
|
|
1304
1492
|
typeof itemID === 'string',
|
|
1305
|
-
'
|
|
1493
|
+
'OperationExecutor: Expected id of elements of field `%s` to ' +
|
|
1306
1494
|
'be strings.',
|
|
1307
1495
|
storageKey,
|
|
1308
1496
|
);
|
|
@@ -1322,9 +1510,13 @@ class Executor {
|
|
|
1322
1510
|
fieldPayloads,
|
|
1323
1511
|
});
|
|
1324
1512
|
const relayPayload = normalizeResponse(response, selector, typeName, {
|
|
1513
|
+
actorIdentifier: this._actorIdentifier,
|
|
1325
1514
|
getDataID: this._getDataID,
|
|
1326
1515
|
path: [...normalizationPath, responseKey, String(itemIndex)],
|
|
1327
|
-
reactFlightPayloadDeserializer:
|
|
1516
|
+
reactFlightPayloadDeserializer:
|
|
1517
|
+
this._reactFlightPayloadDeserializer != null
|
|
1518
|
+
? this._deserializeReactFlightPayloadWithLogging
|
|
1519
|
+
: null,
|
|
1328
1520
|
reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
|
|
1329
1521
|
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
|
|
1330
1522
|
shouldProcessClientComponents: this._shouldProcessClientComponents,
|
|
@@ -1339,6 +1531,25 @@ class Executor {
|
|
|
1339
1531
|
};
|
|
1340
1532
|
}
|
|
1341
1533
|
|
|
1534
|
+
_scheduleAsyncStoreUpdate(
|
|
1535
|
+
scheduleFn: (() => void) => Disposable,
|
|
1536
|
+
completeFn: () => void,
|
|
1537
|
+
): void {
|
|
1538
|
+
this._completeFns.push(completeFn);
|
|
1539
|
+
if (this._asyncStoreUpdateDisposable != null) {
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
this._asyncStoreUpdateDisposable = scheduleFn(() => {
|
|
1543
|
+
this._asyncStoreUpdateDisposable = null;
|
|
1544
|
+
const updatedOwners = this._runPublishQueue();
|
|
1545
|
+
this._updateOperationTracker(updatedOwners);
|
|
1546
|
+
for (const complete of this._completeFns) {
|
|
1547
|
+
complete();
|
|
1548
|
+
}
|
|
1549
|
+
this._completeFns = [];
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1342
1553
|
_updateOperationTracker(
|
|
1343
1554
|
updatedOwners: ?$ReadOnlyArray<RequestDescriptor>,
|
|
1344
1555
|
): void {
|
|
@@ -1353,6 +1564,58 @@ class Executor {
|
|
|
1353
1564
|
_completeOperationTracker() {
|
|
1354
1565
|
this._operationTracker.complete(this._operation.request);
|
|
1355
1566
|
}
|
|
1567
|
+
|
|
1568
|
+
_getPublishQueueAndSaveActor(): PublishQueue {
|
|
1569
|
+
this._seenActors.add(this._actorIdentifier);
|
|
1570
|
+
return this._getPublishQueue(this._actorIdentifier);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
_getActorsToVisit(): $ReadOnlySet<ActorIdentifier> {
|
|
1574
|
+
if (this._seenActors.size === 0) {
|
|
1575
|
+
return new Set([this._actorIdentifier]);
|
|
1576
|
+
} else {
|
|
1577
|
+
return this._seenActors;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
_runPublishQueue(
|
|
1582
|
+
operation?: OperationDescriptor,
|
|
1583
|
+
): $ReadOnlyArray<RequestDescriptor> {
|
|
1584
|
+
const updatedOwners = new Set();
|
|
1585
|
+
for (const actorIdentifier of this._getActorsToVisit()) {
|
|
1586
|
+
const owners = this._getPublishQueue(actorIdentifier).run(operation);
|
|
1587
|
+
owners.forEach(owner => updatedOwners.add(owner));
|
|
1588
|
+
}
|
|
1589
|
+
return Array.from(updatedOwners);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
_retainData() {
|
|
1593
|
+
for (const actorIdentifier of this._getActorsToVisit()) {
|
|
1594
|
+
if (!this._retainDisposables.has(actorIdentifier)) {
|
|
1595
|
+
this._retainDisposables.set(
|
|
1596
|
+
actorIdentifier,
|
|
1597
|
+
this._getStore(actorIdentifier).retain(this._operation),
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
_disposeRetainedData() {
|
|
1604
|
+
for (const disposable of this._retainDisposables.values()) {
|
|
1605
|
+
disposable.dispose();
|
|
1606
|
+
}
|
|
1607
|
+
this._retainDisposables.clear();
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
_expectOperationLoader(): OperationLoader {
|
|
1611
|
+
const operationLoader = this._operationLoader;
|
|
1612
|
+
invariant(
|
|
1613
|
+
operationLoader,
|
|
1614
|
+
'OperationExecutor: Expected an operationLoader to be ' +
|
|
1615
|
+
'configured when using `@match`.',
|
|
1616
|
+
);
|
|
1617
|
+
return operationLoader;
|
|
1618
|
+
}
|
|
1356
1619
|
}
|
|
1357
1620
|
|
|
1358
1621
|
function partitionGraphQLResponses(
|