relay-runtime 18.2.0 → 20.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/experimental.js +1 -1
- package/experimental.js.flow +8 -6
- package/index.js +1 -1
- package/index.js.flow +3 -0
- package/lib/experimental.js +5 -2
- package/lib/index.js +3 -0
- package/lib/multi-actor-environment/ActorSpecificEnvironment.js +1 -1
- package/lib/mutations/RelayRecordSourceProxy.js +2 -1
- package/lib/mutations/createUpdatableProxy.js +1 -1
- package/lib/mutations/validateMutation.js +2 -2
- package/lib/network/RelayObservable.js +1 -3
- package/lib/network/wrapNetworkWithLogObserver.js +2 -2
- package/lib/query/fetchQuery.js +1 -1
- package/lib/store/DataChecker.js +12 -8
- package/lib/store/OperationExecutor.js +93 -43
- package/lib/store/RelayModernEnvironment.js +13 -4
- package/lib/store/RelayModernFragmentSpecResolver.js +4 -4
- package/lib/store/RelayModernStore.js +49 -24
- package/lib/store/RelayPublishQueue.js +11 -15
- package/lib/store/RelayReader.js +134 -151
- package/lib/store/RelayReferenceMarker.js +14 -7
- package/lib/store/RelayResponseNormalizer.js +57 -31
- package/lib/store/RelayStoreSubscriptions.js +2 -2
- package/lib/store/RelayStoreUtils.js +8 -0
- package/lib/store/ResolverFragments.js +2 -2
- package/lib/store/createRelayLoggingContext.js +17 -0
- package/lib/store/generateTypenamePrefixedDataID.js +9 -0
- package/lib/store/live-resolvers/LiveResolverCache.js +4 -2
- package/lib/store/live-resolvers/resolverDataInjector.js +4 -4
- package/lib/store/normalizeResponse.js +2 -2
- package/lib/store/observeFragmentExperimental.js +60 -13
- package/lib/store/observeQueryExperimental.js +21 -0
- package/lib/util/RelayFeatureFlags.js +7 -1
- package/lib/util/handlePotentialSnapshotErrors.js +11 -8
- package/multi-actor-environment/ActorSpecificEnvironment.js.flow +1 -0
- package/mutations/RelayRecordSourceProxy.js.flow +4 -0
- package/mutations/createUpdatableProxy.js.flow +1 -1
- package/mutations/validateMutation.js.flow +3 -3
- package/network/RelayNetworkTypes.js.flow +3 -0
- package/network/RelayObservable.js.flow +1 -5
- package/network/wrapNetworkWithLogObserver.js.flow +19 -1
- package/package.json +1 -1
- package/query/fetchQuery.js.flow +1 -1
- package/store/DataChecker.js.flow +16 -4
- package/store/OperationExecutor.js.flow +101 -15
- package/store/RelayExperimentalGraphResponseTransform.js.flow +4 -4
- package/store/RelayModernEnvironment.js.flow +22 -6
- package/store/RelayModernFragmentSpecResolver.js.flow +6 -6
- package/store/RelayModernSelector.js.flow +2 -0
- package/store/RelayModernStore.js.flow +86 -27
- package/store/RelayPublishQueue.js.flow +32 -21
- package/store/RelayReader.js.flow +168 -97
- package/store/RelayReferenceMarker.js.flow +15 -5
- package/store/RelayResponseNormalizer.js.flow +104 -69
- package/store/RelayStoreSubscriptions.js.flow +2 -2
- package/store/RelayStoreTypes.js.flow +34 -4
- package/store/RelayStoreUtils.js.flow +29 -0
- package/store/ResolverCache.js.flow +2 -2
- package/store/ResolverFragments.js.flow +5 -3
- package/store/StoreInspector.js.flow +5 -0
- package/store/createRelayContext.js.flow +3 -2
- package/store/createRelayLoggingContext.js.flow +46 -0
- package/store/generateTypenamePrefixedDataID.js.flow +25 -0
- package/store/live-resolvers/LiveResolverCache.js.flow +7 -2
- package/store/live-resolvers/resolverDataInjector.js.flow +10 -6
- package/store/normalizeResponse.js.flow +2 -0
- package/store/observeFragmentExperimental.js.flow +82 -28
- package/store/observeQueryExperimental.js.flow +61 -0
- package/store/waitForFragmentExperimental.js.flow +4 -3
- package/util/NormalizationNode.js.flow +2 -1
- package/util/RelayConcreteNode.js.flow +2 -0
- package/util/RelayError.js.flow +1 -0
- package/util/RelayFeatureFlags.js.flow +28 -0
- package/util/RelayRuntimeTypes.js.flow +6 -3
- package/util/getPaginationVariables.js.flow +2 -0
- package/util/handlePotentialSnapshotErrors.js.flow +23 -11
- package/util/registerEnvironmentWithDevTools.js.flow +4 -2
- package/util/withProvidedVariables.js.flow +1 -0
- package/util/withStartAndDuration.js.flow +3 -0
- package/relay-runtime-experimental.js +0 -4
- package/relay-runtime-experimental.min.js +0 -9
- package/relay-runtime.js +0 -4
- package/relay-runtime.min.js +0 -9
|
@@ -35,8 +35,8 @@ import type {DataID, Variables} from '../util/RelayRuntimeTypes';
|
|
|
35
35
|
import type {
|
|
36
36
|
ClientEdgeTraversalInfo,
|
|
37
37
|
DataIDSet,
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
FieldError,
|
|
39
|
+
FieldErrors,
|
|
40
40
|
MissingClientEdgeRequestInfo,
|
|
41
41
|
Record,
|
|
42
42
|
RecordSource,
|
|
@@ -49,6 +49,7 @@ import type {
|
|
|
49
49
|
import type {Arguments} from './RelayStoreUtils';
|
|
50
50
|
import type {EvaluationResult, ResolverCache} from './ResolverCache';
|
|
51
51
|
|
|
52
|
+
const RelayFeatureFlags = require('../util/RelayFeatureFlags');
|
|
52
53
|
const {
|
|
53
54
|
isSuspenseSentinel,
|
|
54
55
|
} = require('./live-resolvers/LiveResolverSuspenseSentinel');
|
|
@@ -98,7 +99,7 @@ class RelayReader {
|
|
|
98
99
|
_missingClientEdges: Array<MissingClientEdgeRequestInfo>;
|
|
99
100
|
_missingLiveResolverFields: Array<DataID>;
|
|
100
101
|
_isWithinUnmatchedTypeRefinement: boolean;
|
|
101
|
-
|
|
102
|
+
_fieldErrors: ?FieldErrors;
|
|
102
103
|
_owner: RequestDescriptor;
|
|
103
104
|
// Exec time resolvers are run before reaching the Relay store so the store already contains
|
|
104
105
|
// the normalized data; the same as if the data were sent from the server. However, since a
|
|
@@ -128,10 +129,13 @@ class RelayReader {
|
|
|
128
129
|
this._missingLiveResolverFields = [];
|
|
129
130
|
this._isMissingData = false;
|
|
130
131
|
this._isWithinUnmatchedTypeRefinement = false;
|
|
131
|
-
this.
|
|
132
|
+
this._fieldErrors = null;
|
|
132
133
|
this._owner = selector.owner;
|
|
133
134
|
this._useExecTimeResolvers =
|
|
134
|
-
this._owner.node.operation.use_exec_time_resolvers ??
|
|
135
|
+
this._owner.node.operation.use_exec_time_resolvers ??
|
|
136
|
+
this._owner.node.operation.exec_time_resolvers_enabled_provider?.get() ===
|
|
137
|
+
true ??
|
|
138
|
+
false;
|
|
135
139
|
this._recordSource = recordSource;
|
|
136
140
|
this._seenRecords = new Set();
|
|
137
141
|
this._selector = selector;
|
|
@@ -205,11 +209,11 @@ class RelayReader {
|
|
|
205
209
|
missingLiveResolverFields: this._missingLiveResolverFields,
|
|
206
210
|
seenRecords: this._seenRecords,
|
|
207
211
|
selector: this._selector,
|
|
208
|
-
|
|
212
|
+
fieldErrors: this._fieldErrors,
|
|
209
213
|
};
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
|
|
216
|
+
_maybeAddFieldErrors(record: Record, storageKey: string): void {
|
|
213
217
|
const errors = RelayModernRecord.getErrors(record, storageKey);
|
|
214
218
|
|
|
215
219
|
if (errors == null) {
|
|
@@ -217,17 +221,21 @@ class RelayReader {
|
|
|
217
221
|
}
|
|
218
222
|
const owner = this._fragmentName;
|
|
219
223
|
|
|
220
|
-
if (this.
|
|
221
|
-
this.
|
|
224
|
+
if (this._fieldErrors == null) {
|
|
225
|
+
this._fieldErrors = [];
|
|
222
226
|
}
|
|
223
|
-
for (
|
|
224
|
-
|
|
227
|
+
for (let i = 0; i < errors.length; i++) {
|
|
228
|
+
const error = errors[i];
|
|
229
|
+
this._fieldErrors.push({
|
|
225
230
|
kind: 'relay_field_payload.error',
|
|
226
231
|
owner,
|
|
227
232
|
fieldPath: (error.path ?? []).join('.'),
|
|
228
233
|
error,
|
|
229
234
|
shouldThrow: this._selector.node.metadata?.throwOnFieldError ?? false,
|
|
230
235
|
handled: false,
|
|
236
|
+
// the uiContext is always undefined here.
|
|
237
|
+
// the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
|
|
238
|
+
uiContext: undefined,
|
|
231
239
|
});
|
|
232
240
|
}
|
|
233
241
|
}
|
|
@@ -236,22 +244,32 @@ class RelayReader {
|
|
|
236
244
|
if (this._isWithinUnmatchedTypeRefinement) {
|
|
237
245
|
return;
|
|
238
246
|
}
|
|
239
|
-
if (this.
|
|
240
|
-
this.
|
|
247
|
+
if (this._fieldErrors == null) {
|
|
248
|
+
this._fieldErrors = [];
|
|
241
249
|
}
|
|
242
250
|
|
|
243
251
|
// we will add the path later
|
|
244
252
|
const owner = this._fragmentName;
|
|
245
253
|
|
|
246
|
-
this.
|
|
254
|
+
this._fieldErrors.push(
|
|
247
255
|
this._selector.node.metadata?.throwOnFieldError ?? false
|
|
248
256
|
? {
|
|
249
257
|
kind: 'missing_expected_data.throw',
|
|
250
258
|
owner,
|
|
251
259
|
fieldPath: fieldName,
|
|
252
260
|
handled: false,
|
|
261
|
+
// the uiContext is always undefined here.
|
|
262
|
+
// the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
|
|
263
|
+
uiContext: undefined,
|
|
253
264
|
}
|
|
254
|
-
: {
|
|
265
|
+
: {
|
|
266
|
+
kind: 'missing_expected_data.log',
|
|
267
|
+
owner,
|
|
268
|
+
fieldPath: fieldName,
|
|
269
|
+
// the uiContext is always undefined here.
|
|
270
|
+
// the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
|
|
271
|
+
uiContext: undefined,
|
|
272
|
+
},
|
|
255
273
|
);
|
|
256
274
|
|
|
257
275
|
this._isMissingData = true;
|
|
@@ -282,6 +300,7 @@ class RelayReader {
|
|
|
282
300
|
if (record === undefined) {
|
|
283
301
|
this._markDataAsMissing('<record>');
|
|
284
302
|
}
|
|
303
|
+
// $FlowFixMe[incompatible-return]
|
|
285
304
|
return record;
|
|
286
305
|
}
|
|
287
306
|
const data = prevData || {};
|
|
@@ -308,8 +327,8 @@ class RelayReader {
|
|
|
308
327
|
}
|
|
309
328
|
const owner = this._fragmentName;
|
|
310
329
|
|
|
311
|
-
if (this.
|
|
312
|
-
this.
|
|
330
|
+
if (this._fieldErrors == null) {
|
|
331
|
+
this._fieldErrors = [];
|
|
313
332
|
}
|
|
314
333
|
|
|
315
334
|
let fieldName: string;
|
|
@@ -322,18 +341,24 @@ class RelayReader {
|
|
|
322
341
|
|
|
323
342
|
switch (selection.action) {
|
|
324
343
|
case 'THROW':
|
|
325
|
-
this.
|
|
344
|
+
this._fieldErrors.push({
|
|
326
345
|
kind: 'missing_required_field.throw',
|
|
327
346
|
fieldPath: fieldName,
|
|
328
347
|
owner,
|
|
329
348
|
handled: false,
|
|
349
|
+
// the uiContext is always undefined here.
|
|
350
|
+
// the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
|
|
351
|
+
uiContext: undefined,
|
|
330
352
|
});
|
|
331
353
|
return;
|
|
332
354
|
case 'LOG':
|
|
333
|
-
this.
|
|
355
|
+
this._fieldErrors.push({
|
|
334
356
|
kind: 'missing_required_field.log',
|
|
335
357
|
fieldPath: fieldName,
|
|
336
358
|
owner,
|
|
359
|
+
// the uiContext is always undefined here.
|
|
360
|
+
// the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
|
|
361
|
+
uiContext: undefined,
|
|
337
362
|
});
|
|
338
363
|
return;
|
|
339
364
|
default:
|
|
@@ -361,7 +386,7 @@ class RelayReader {
|
|
|
361
386
|
* any fields within them.
|
|
362
387
|
*
|
|
363
388
|
* 1. Before traversing into the selection(s) marked as `@catch`, the caller
|
|
364
|
-
* stores the previous field errors (`this.
|
|
389
|
+
* stores the previous field errors (`this._fieldErrors`) in a
|
|
365
390
|
* variable.
|
|
366
391
|
* 2. After traversing into the selection(s) marked as `@catch`, the caller
|
|
367
392
|
* calls this method with the resulting value, the `to` value from the
|
|
@@ -376,7 +401,7 @@ class RelayReader {
|
|
|
376
401
|
_catchErrors<T>(
|
|
377
402
|
_value: T,
|
|
378
403
|
to: CatchFieldTo,
|
|
379
|
-
previousResponseFields: ?
|
|
404
|
+
previousResponseFields: ?FieldErrors,
|
|
380
405
|
): ?T | Result<T, mixed> {
|
|
381
406
|
let value: T | null | Result<T, mixed> = _value;
|
|
382
407
|
switch (to) {
|
|
@@ -384,10 +409,7 @@ class RelayReader {
|
|
|
384
409
|
value = this._asResult(_value);
|
|
385
410
|
break;
|
|
386
411
|
case 'NULL':
|
|
387
|
-
if (
|
|
388
|
-
this._errorResponseFields != null &&
|
|
389
|
-
this._errorResponseFields.length > 0
|
|
390
|
-
) {
|
|
412
|
+
if (this._fieldErrors != null && this._fieldErrors.length > 0) {
|
|
391
413
|
value = null;
|
|
392
414
|
}
|
|
393
415
|
break;
|
|
@@ -395,22 +417,22 @@ class RelayReader {
|
|
|
395
417
|
(to: empty);
|
|
396
418
|
}
|
|
397
419
|
|
|
398
|
-
const
|
|
420
|
+
const childrenFieldErrors = this._fieldErrors;
|
|
399
421
|
|
|
400
|
-
this.
|
|
422
|
+
this._fieldErrors = previousResponseFields;
|
|
401
423
|
|
|
402
424
|
// Merge any errors encountered within the @catch with the previous field
|
|
403
425
|
// errors, but mark them as "handled" first.
|
|
404
|
-
if (
|
|
405
|
-
if (this.
|
|
406
|
-
this.
|
|
426
|
+
if (childrenFieldErrors != null) {
|
|
427
|
+
if (this._fieldErrors == null) {
|
|
428
|
+
this._fieldErrors = [];
|
|
407
429
|
}
|
|
408
|
-
for (let i = 0; i <
|
|
430
|
+
for (let i = 0; i < childrenFieldErrors.length; i++) {
|
|
409
431
|
// We mark any errors encountered within the @catch as "handled"
|
|
410
432
|
// to ensure that they don't cause the reader to throw, but can
|
|
411
433
|
// still be logged.
|
|
412
|
-
this.
|
|
413
|
-
markFieldErrorHasHandled(
|
|
434
|
+
this._fieldErrors.push(
|
|
435
|
+
markFieldErrorHasHandled(childrenFieldErrors[i]),
|
|
414
436
|
);
|
|
415
437
|
}
|
|
416
438
|
}
|
|
@@ -419,21 +441,18 @@ class RelayReader {
|
|
|
419
441
|
|
|
420
442
|
/**
|
|
421
443
|
* Convert a value into a Result object based on the presence of errors in the
|
|
422
|
-
* `this.
|
|
444
|
+
* `this._fieldErrors` array.
|
|
423
445
|
*
|
|
424
446
|
* **Note**: This method does _not_ mark errors as handled. It is the caller's
|
|
425
447
|
* responsibility to ensure that errors are marked as handled.
|
|
426
448
|
*/
|
|
427
449
|
_asResult<T>(value: T): Result<T, mixed> {
|
|
428
|
-
if (
|
|
429
|
-
this._errorResponseFields == null ||
|
|
430
|
-
this._errorResponseFields.length === 0
|
|
431
|
-
) {
|
|
450
|
+
if (this._fieldErrors == null || this._fieldErrors.length === 0) {
|
|
432
451
|
return {ok: true, value};
|
|
433
452
|
}
|
|
434
453
|
|
|
435
454
|
// TODO: Should we be hiding log level events here?
|
|
436
|
-
const errors = this.
|
|
455
|
+
const errors = this._fieldErrors
|
|
437
456
|
.map(error => {
|
|
438
457
|
switch (error.kind) {
|
|
439
458
|
case 'relay_field_payload.error':
|
|
@@ -461,7 +480,7 @@ class RelayReader {
|
|
|
461
480
|
(error.kind: empty);
|
|
462
481
|
invariant(
|
|
463
482
|
false,
|
|
464
|
-
'Unexpected error
|
|
483
|
+
'Unexpected error fieldError kind: %s',
|
|
465
484
|
error.kind,
|
|
466
485
|
);
|
|
467
486
|
}
|
|
@@ -491,9 +510,9 @@ class RelayReader {
|
|
|
491
510
|
}
|
|
492
511
|
break;
|
|
493
512
|
case 'CatchField': {
|
|
494
|
-
const previousResponseFields = this.
|
|
513
|
+
const previousResponseFields = this._fieldErrors;
|
|
495
514
|
|
|
496
|
-
this.
|
|
515
|
+
this._fieldErrors = null;
|
|
497
516
|
|
|
498
517
|
const catchFieldValue = this._readClientSideDirectiveField(
|
|
499
518
|
selection,
|
|
@@ -662,13 +681,31 @@ class RelayReader {
|
|
|
662
681
|
} else {
|
|
663
682
|
return this._readLink(selection.field, record, data);
|
|
664
683
|
}
|
|
684
|
+
|
|
665
685
|
case 'RelayResolver':
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
686
|
+
case 'RelayLiveResolver': {
|
|
687
|
+
if (this._useExecTimeResolvers) {
|
|
688
|
+
return this._readScalar(selection.field, record, data);
|
|
689
|
+
} else {
|
|
690
|
+
return this._readResolverField(selection.field, record, data);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
669
693
|
case 'ClientEdgeToClientObject':
|
|
670
694
|
case 'ClientEdgeToServerObject':
|
|
671
|
-
|
|
695
|
+
if (
|
|
696
|
+
this._useExecTimeResolvers &&
|
|
697
|
+
(selection.field.backingField.kind === 'RelayResolver' ||
|
|
698
|
+
selection.field.backingField.kind === 'RelayLiveResolver')
|
|
699
|
+
) {
|
|
700
|
+
const {field} = selection;
|
|
701
|
+
if (field.linkedField.plural) {
|
|
702
|
+
return this._readPluralLink(field.linkedField, record, data);
|
|
703
|
+
} else {
|
|
704
|
+
return this._readLink(field.linkedField, record, data);
|
|
705
|
+
}
|
|
706
|
+
} else {
|
|
707
|
+
return this._readClientEdge(selection.field, record, data);
|
|
708
|
+
}
|
|
672
709
|
case 'AliasedInlineFragmentSpread':
|
|
673
710
|
return this._readAliasedInlineFragment(selection.field, record, data);
|
|
674
711
|
default:
|
|
@@ -687,8 +724,8 @@ class RelayReader {
|
|
|
687
724
|
data: SelectorData,
|
|
688
725
|
): mixed {
|
|
689
726
|
const parentRecordID = RelayModernRecord.getDataID(record);
|
|
690
|
-
const prevErrors = this.
|
|
691
|
-
this.
|
|
727
|
+
const prevErrors = this._fieldErrors;
|
|
728
|
+
this._fieldErrors = null;
|
|
692
729
|
const result = this._readResolverFieldImpl(field, parentRecordID);
|
|
693
730
|
|
|
694
731
|
const fieldName = field.alias ?? field.name;
|
|
@@ -726,7 +763,7 @@ class RelayReader {
|
|
|
726
763
|
return {
|
|
727
764
|
data: snapshot.data,
|
|
728
765
|
isMissingData: snapshot.isMissingData,
|
|
729
|
-
|
|
766
|
+
fieldErrors: snapshot.fieldErrors,
|
|
730
767
|
};
|
|
731
768
|
}
|
|
732
769
|
|
|
@@ -739,7 +776,7 @@ class RelayReader {
|
|
|
739
776
|
return {
|
|
740
777
|
data: snapshot.data,
|
|
741
778
|
isMissingData: snapshot.isMissingData,
|
|
742
|
-
|
|
779
|
+
fieldErrors: snapshot.fieldErrors,
|
|
743
780
|
};
|
|
744
781
|
};
|
|
745
782
|
|
|
@@ -751,7 +788,7 @@ class RelayReader {
|
|
|
751
788
|
// `getResolverValue`) and converted into an error object.
|
|
752
789
|
const evaluate = (): EvaluationResult<mixed> => {
|
|
753
790
|
if (fragment != null) {
|
|
754
|
-
const key = {
|
|
791
|
+
const key: SelectorData = {
|
|
755
792
|
__id: parentRecordID,
|
|
756
793
|
__fragmentOwner: this._owner,
|
|
757
794
|
__fragments: {
|
|
@@ -760,6 +797,14 @@ class RelayReader {
|
|
|
760
797
|
: {},
|
|
761
798
|
},
|
|
762
799
|
};
|
|
800
|
+
if (
|
|
801
|
+
this._clientEdgeTraversalPath.length > 0 &&
|
|
802
|
+
this._clientEdgeTraversalPath[
|
|
803
|
+
this._clientEdgeTraversalPath.length - 1
|
|
804
|
+
] !== null
|
|
805
|
+
) {
|
|
806
|
+
key[CLIENT_EDGE_TRAVERSAL_PATH] = [...this._clientEdgeTraversalPath];
|
|
807
|
+
}
|
|
763
808
|
const resolverContext = {getDataForResolverFragment};
|
|
764
809
|
return withResolverContext(resolverContext, () => {
|
|
765
810
|
const [resolverResult, resolverError] = getResolverValue(
|
|
@@ -824,7 +869,8 @@ class RelayReader {
|
|
|
824
869
|
// upwards to mimic the behavior of having traversed into that fragment directly.
|
|
825
870
|
if (cachedSnapshot != null) {
|
|
826
871
|
if (cachedSnapshot.missingClientEdges != null) {
|
|
827
|
-
for (
|
|
872
|
+
for (let i = 0; i < cachedSnapshot.missingClientEdges.length; i++) {
|
|
873
|
+
const missing = cachedSnapshot.missingClientEdges[i];
|
|
828
874
|
this._missingClientEdges.push(missing);
|
|
829
875
|
}
|
|
830
876
|
}
|
|
@@ -833,27 +879,34 @@ class RelayReader {
|
|
|
833
879
|
this._isMissingData ||
|
|
834
880
|
cachedSnapshot.missingLiveResolverFields.length > 0;
|
|
835
881
|
|
|
836
|
-
for (
|
|
882
|
+
for (
|
|
883
|
+
let i = 0;
|
|
884
|
+
i < cachedSnapshot.missingLiveResolverFields.length;
|
|
885
|
+
i++
|
|
886
|
+
) {
|
|
887
|
+
const missingResolverField =
|
|
888
|
+
cachedSnapshot.missingLiveResolverFields[i];
|
|
837
889
|
this._missingLiveResolverFields.push(missingResolverField);
|
|
838
890
|
}
|
|
839
891
|
}
|
|
840
|
-
if (cachedSnapshot.
|
|
841
|
-
if (this.
|
|
842
|
-
this.
|
|
892
|
+
if (cachedSnapshot.fieldErrors != null) {
|
|
893
|
+
if (this._fieldErrors == null) {
|
|
894
|
+
this._fieldErrors = [];
|
|
843
895
|
}
|
|
844
|
-
for (
|
|
896
|
+
for (let i = 0; i < cachedSnapshot.fieldErrors.length; i++) {
|
|
897
|
+
const error = cachedSnapshot.fieldErrors[i];
|
|
845
898
|
if (this._selector.node.metadata?.throwOnFieldError === true) {
|
|
846
899
|
// If this fragment is @throwOnFieldError, any destructive error
|
|
847
900
|
// encountered inside a resolver's fragment is equivilent to the
|
|
848
901
|
// resolver field having a field error, and we want that to cause this
|
|
849
902
|
// fragment to throw. So, we propagate all errors as is.
|
|
850
|
-
this.
|
|
903
|
+
this._fieldErrors.push(error);
|
|
851
904
|
} else {
|
|
852
905
|
// If this fragment is _not_ @throwOnFieldError, we will simply
|
|
853
906
|
// accept that any destructive errors encountered in the resolver's
|
|
854
907
|
// root fragment will cause the resolver to return null, and well
|
|
855
908
|
// pass the errors along to the logger marked as "handled".
|
|
856
|
-
this.
|
|
909
|
+
this._fieldErrors.push(markFieldErrorHasHandled(error));
|
|
857
910
|
}
|
|
858
911
|
}
|
|
859
912
|
}
|
|
@@ -864,18 +917,21 @@ class RelayReader {
|
|
|
864
917
|
// the errors can be attached to this read's snapshot. This allows the error
|
|
865
918
|
// to be logged.
|
|
866
919
|
if (resolverError) {
|
|
867
|
-
const errorEvent = {
|
|
920
|
+
const errorEvent: FieldError = {
|
|
868
921
|
kind: 'relay_resolver.error',
|
|
869
922
|
fieldPath,
|
|
870
923
|
owner: this._fragmentName,
|
|
871
924
|
error: resolverError,
|
|
872
925
|
shouldThrow: this._selector.node.metadata?.throwOnFieldError ?? false,
|
|
873
926
|
handled: false,
|
|
927
|
+
// the uiContext is always undefined here.
|
|
928
|
+
// the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
|
|
929
|
+
uiContext: undefined,
|
|
874
930
|
};
|
|
875
|
-
if (this.
|
|
876
|
-
this.
|
|
931
|
+
if (this._fieldErrors == null) {
|
|
932
|
+
this._fieldErrors = [errorEvent];
|
|
877
933
|
} else {
|
|
878
|
-
this.
|
|
934
|
+
this._fieldErrors.push(errorEvent);
|
|
879
935
|
}
|
|
880
936
|
}
|
|
881
937
|
|
|
@@ -896,9 +952,11 @@ class RelayReader {
|
|
|
896
952
|
this._missingLiveResolverFields.push(suspenseID);
|
|
897
953
|
}
|
|
898
954
|
if (updatedDataIDs != null) {
|
|
899
|
-
|
|
955
|
+
// Iterating a Set with for of is okay
|
|
956
|
+
// eslint-disable-next-line relay-internal/no-for-of-loops
|
|
957
|
+
updatedDataIDs.forEach(recordID => {
|
|
900
958
|
this._updatedDataIDs.add(recordID);
|
|
901
|
-
}
|
|
959
|
+
});
|
|
902
960
|
}
|
|
903
961
|
}
|
|
904
962
|
|
|
@@ -1068,8 +1126,8 @@ class RelayReader {
|
|
|
1068
1126
|
RelayModernRecord.getDataID(record),
|
|
1069
1127
|
prevData,
|
|
1070
1128
|
);
|
|
1071
|
-
const prevErrors = this.
|
|
1072
|
-
this.
|
|
1129
|
+
const prevErrors = this._fieldErrors;
|
|
1130
|
+
this._fieldErrors = null;
|
|
1073
1131
|
const edgeValue = this._traverse(
|
|
1074
1132
|
field.linkedField,
|
|
1075
1133
|
storeID,
|
|
@@ -1091,8 +1149,13 @@ class RelayReader {
|
|
|
1091
1149
|
const fieldName = field.alias ?? field.name;
|
|
1092
1150
|
const storageKey = getStorageKey(field, this._variables);
|
|
1093
1151
|
const value = RelayModernRecord.getValue(record, storageKey);
|
|
1094
|
-
if (
|
|
1095
|
-
|
|
1152
|
+
if (
|
|
1153
|
+
value === null ||
|
|
1154
|
+
(RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
|
|
1155
|
+
Array.isArray(value) &&
|
|
1156
|
+
value.length === 0)
|
|
1157
|
+
) {
|
|
1158
|
+
this._maybeAddFieldErrors(record, storageKey);
|
|
1096
1159
|
} else if (value === undefined) {
|
|
1097
1160
|
this._markDataAsMissing(fieldName);
|
|
1098
1161
|
}
|
|
@@ -1111,7 +1174,7 @@ class RelayReader {
|
|
|
1111
1174
|
if (linkedID == null) {
|
|
1112
1175
|
data[fieldName] = linkedID;
|
|
1113
1176
|
if (linkedID === null) {
|
|
1114
|
-
this.
|
|
1177
|
+
this._maybeAddFieldErrors(record, storageKey);
|
|
1115
1178
|
} else if (linkedID === undefined) {
|
|
1116
1179
|
this._markDataAsMissing(fieldName);
|
|
1117
1180
|
}
|
|
@@ -1128,8 +1191,8 @@ class RelayReader {
|
|
|
1128
1191
|
RelayModernRecord.getDataID(record),
|
|
1129
1192
|
prevData,
|
|
1130
1193
|
);
|
|
1131
|
-
const prevErrors = this.
|
|
1132
|
-
this.
|
|
1194
|
+
const prevErrors = this._fieldErrors;
|
|
1195
|
+
this._fieldErrors = null;
|
|
1133
1196
|
// $FlowFixMe[incompatible-variance]
|
|
1134
1197
|
const value = this._traverse(field, linkedID, prevData);
|
|
1135
1198
|
|
|
@@ -1139,7 +1202,7 @@ class RelayReader {
|
|
|
1139
1202
|
}
|
|
1140
1203
|
|
|
1141
1204
|
/**
|
|
1142
|
-
* Adds a set of field errors to `this.
|
|
1205
|
+
* Adds a set of field errors to `this._fieldErrors`, ensuring the
|
|
1143
1206
|
* `fieldPath` property of existing field errors are prefixed with the given
|
|
1144
1207
|
* `fieldNameOrIndex`.
|
|
1145
1208
|
*
|
|
@@ -1158,8 +1221,8 @@ class RelayReader {
|
|
|
1158
1221
|
* To achieve this, named field readers must do the following to correctly
|
|
1159
1222
|
* track error filePaths:
|
|
1160
1223
|
*
|
|
1161
|
-
* 1. Stash the value of `this.
|
|
1162
|
-
* 2. Set `this.
|
|
1224
|
+
* 1. Stash the value of `this._fieldErrors` in a local variable
|
|
1225
|
+
* 2. Set `this._fieldErrors` to `null`
|
|
1163
1226
|
* 3. Traverse into the field
|
|
1164
1227
|
* 4. Call this method with the stashed errors and the field's name
|
|
1165
1228
|
*
|
|
@@ -1171,12 +1234,12 @@ class RelayReader {
|
|
|
1171
1234
|
* field error paths.
|
|
1172
1235
|
*/
|
|
1173
1236
|
_prependPreviousErrors(
|
|
1174
|
-
prevErrors: ?Array<
|
|
1237
|
+
prevErrors: ?Array<FieldError>,
|
|
1175
1238
|
fieldNameOrIndex: string | number,
|
|
1176
1239
|
): void {
|
|
1177
|
-
if (this.
|
|
1178
|
-
for (let i = 0; i < this.
|
|
1179
|
-
const event = this.
|
|
1240
|
+
if (this._fieldErrors != null) {
|
|
1241
|
+
for (let i = 0; i < this._fieldErrors.length; i++) {
|
|
1242
|
+
const event = this._fieldErrors[i];
|
|
1180
1243
|
if (
|
|
1181
1244
|
event.owner === this._fragmentName &&
|
|
1182
1245
|
(event.kind === 'missing_expected_data.throw' ||
|
|
@@ -1188,13 +1251,13 @@ class RelayReader {
|
|
|
1188
1251
|
}
|
|
1189
1252
|
}
|
|
1190
1253
|
if (prevErrors != null) {
|
|
1191
|
-
for (let i = this.
|
|
1192
|
-
prevErrors.push(this.
|
|
1254
|
+
for (let i = this._fieldErrors.length - 1; i >= 0; i--) {
|
|
1255
|
+
prevErrors.push(this._fieldErrors[i]);
|
|
1193
1256
|
}
|
|
1194
|
-
this.
|
|
1257
|
+
this._fieldErrors = prevErrors;
|
|
1195
1258
|
}
|
|
1196
1259
|
} else {
|
|
1197
|
-
this.
|
|
1260
|
+
this._fieldErrors = prevErrors;
|
|
1198
1261
|
}
|
|
1199
1262
|
}
|
|
1200
1263
|
|
|
@@ -1215,7 +1278,7 @@ class RelayReader {
|
|
|
1215
1278
|
if (externalRef === undefined) {
|
|
1216
1279
|
this._markDataAsMissing(fieldName);
|
|
1217
1280
|
} else if (externalRef === null) {
|
|
1218
|
-
this.
|
|
1281
|
+
this._maybeAddFieldErrors(record, storageKey);
|
|
1219
1282
|
}
|
|
1220
1283
|
return data[fieldName];
|
|
1221
1284
|
}
|
|
@@ -1243,8 +1306,13 @@ class RelayReader {
|
|
|
1243
1306
|
): ?mixed {
|
|
1244
1307
|
const storageKey = getStorageKey(field, this._variables);
|
|
1245
1308
|
const linkedIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey);
|
|
1246
|
-
if (
|
|
1247
|
-
|
|
1309
|
+
if (
|
|
1310
|
+
linkedIDs === null ||
|
|
1311
|
+
(RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
|
|
1312
|
+
Array.isArray(linkedIDs) &&
|
|
1313
|
+
linkedIDs.length === 0)
|
|
1314
|
+
) {
|
|
1315
|
+
this._maybeAddFieldErrors(record, storageKey);
|
|
1248
1316
|
}
|
|
1249
1317
|
return this._readLinkedIds(field, linkedIDs, record, data);
|
|
1250
1318
|
}
|
|
@@ -1274,8 +1342,8 @@ class RelayReader {
|
|
|
1274
1342
|
RelayModernRecord.getDataID(record),
|
|
1275
1343
|
prevData,
|
|
1276
1344
|
);
|
|
1277
|
-
const prevErrors = this.
|
|
1278
|
-
this.
|
|
1345
|
+
const prevErrors = this._fieldErrors;
|
|
1346
|
+
this._fieldErrors = null;
|
|
1279
1347
|
const linkedArray = prevData || [];
|
|
1280
1348
|
linkedIDs.forEach((linkedID, nextIndex) => {
|
|
1281
1349
|
if (linkedID == null) {
|
|
@@ -1295,8 +1363,8 @@ class RelayReader {
|
|
|
1295
1363
|
RelayModernRecord.getDataID(record),
|
|
1296
1364
|
prevItem,
|
|
1297
1365
|
);
|
|
1298
|
-
const prevErrors = this.
|
|
1299
|
-
this.
|
|
1366
|
+
const prevErrors = this._fieldErrors;
|
|
1367
|
+
this._fieldErrors = null;
|
|
1300
1368
|
// $FlowFixMe[cannot-write]
|
|
1301
1369
|
// $FlowFixMe[incompatible-variance]
|
|
1302
1370
|
linkedArray[nextIndex] = this._traverse(field, linkedID, prevItem);
|
|
@@ -1319,10 +1387,15 @@ class RelayReader {
|
|
|
1319
1387
|
// Determine the component module from the store: if the field is missing
|
|
1320
1388
|
// it means we don't know what component to render the match with.
|
|
1321
1389
|
const componentKey = getModuleComponentKey(moduleImport.documentName);
|
|
1390
|
+
const relayStoreComponent = RelayModernRecord.getValue(
|
|
1391
|
+
record,
|
|
1392
|
+
componentKey,
|
|
1393
|
+
);
|
|
1322
1394
|
// componentModuleProvider is used by Client 3D for read time resolvers.
|
|
1323
1395
|
const component =
|
|
1324
|
-
|
|
1325
|
-
|
|
1396
|
+
relayStoreComponent !== undefined
|
|
1397
|
+
? relayStoreComponent
|
|
1398
|
+
: moduleImport.componentModuleProvider;
|
|
1326
1399
|
if (component == null) {
|
|
1327
1400
|
if (component === undefined) {
|
|
1328
1401
|
this._markDataAsMissing('<module-import>');
|
|
@@ -1366,8 +1439,8 @@ class RelayReader {
|
|
|
1366
1439
|
record: Record,
|
|
1367
1440
|
data: SelectorData,
|
|
1368
1441
|
) {
|
|
1369
|
-
const prevErrors = this.
|
|
1370
|
-
this.
|
|
1442
|
+
const prevErrors = this._fieldErrors;
|
|
1443
|
+
this._fieldErrors = null;
|
|
1371
1444
|
let fieldValue = this._readInlineFragment(
|
|
1372
1445
|
aliasedInlineFragment.fragment,
|
|
1373
1446
|
record,
|
|
@@ -1597,9 +1670,7 @@ class RelayReader {
|
|
|
1597
1670
|
}
|
|
1598
1671
|
}
|
|
1599
1672
|
|
|
1600
|
-
function markFieldErrorHasHandled(
|
|
1601
|
-
event: ErrorResponseField,
|
|
1602
|
-
): ErrorResponseField {
|
|
1673
|
+
function markFieldErrorHasHandled(event: FieldError): FieldError {
|
|
1603
1674
|
switch (event.kind) {
|
|
1604
1675
|
case 'missing_expected_data.throw':
|
|
1605
1676
|
case 'missing_required_field.throw':
|