relay-runtime 18.2.0 → 19.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 +4 -5
- package/lib/store/OperationExecutor.js +11 -0
- package/lib/store/RelayModernEnvironment.js +13 -4
- package/lib/store/RelayModernFragmentSpecResolver.js +4 -4
- package/lib/store/RelayModernStore.js +43 -21
- package/lib/store/RelayPublishQueue.js +11 -15
- package/lib/store/RelayReader.js +131 -151
- package/lib/store/RelayReferenceMarker.js +3 -4
- package/lib/store/RelayResponseNormalizer.js +47 -26
- 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 +2 -1
- package/lib/store/live-resolvers/resolverDataInjector.js +4 -4
- package/lib/store/observeFragmentExperimental.js +60 -13
- package/lib/store/observeQueryExperimental.js +21 -0
- package/lib/util/RelayFeatureFlags.js +6 -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 +5 -2
- package/store/OperationExecutor.js.flow +12 -1
- 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 +74 -27
- package/store/RelayPublishQueue.js.flow +32 -21
- package/store/RelayReader.js.flow +159 -96
- package/store/RelayReferenceMarker.js.flow +3 -4
- package/store/RelayResponseNormalizer.js.flow +93 -67
- package/store/RelayStoreSubscriptions.js.flow +2 -2
- package/store/RelayStoreTypes.js.flow +33 -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 +2 -1
- package/store/live-resolvers/resolverDataInjector.js.flow +10 -6
- 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 +18 -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
|
|
|
@@ -824,7 +861,8 @@ class RelayReader {
|
|
|
824
861
|
// upwards to mimic the behavior of having traversed into that fragment directly.
|
|
825
862
|
if (cachedSnapshot != null) {
|
|
826
863
|
if (cachedSnapshot.missingClientEdges != null) {
|
|
827
|
-
for (
|
|
864
|
+
for (let i = 0; i < cachedSnapshot.missingClientEdges.length; i++) {
|
|
865
|
+
const missing = cachedSnapshot.missingClientEdges[i];
|
|
828
866
|
this._missingClientEdges.push(missing);
|
|
829
867
|
}
|
|
830
868
|
}
|
|
@@ -833,27 +871,34 @@ class RelayReader {
|
|
|
833
871
|
this._isMissingData ||
|
|
834
872
|
cachedSnapshot.missingLiveResolverFields.length > 0;
|
|
835
873
|
|
|
836
|
-
for (
|
|
874
|
+
for (
|
|
875
|
+
let i = 0;
|
|
876
|
+
i < cachedSnapshot.missingLiveResolverFields.length;
|
|
877
|
+
i++
|
|
878
|
+
) {
|
|
879
|
+
const missingResolverField =
|
|
880
|
+
cachedSnapshot.missingLiveResolverFields[i];
|
|
837
881
|
this._missingLiveResolverFields.push(missingResolverField);
|
|
838
882
|
}
|
|
839
883
|
}
|
|
840
|
-
if (cachedSnapshot.
|
|
841
|
-
if (this.
|
|
842
|
-
this.
|
|
884
|
+
if (cachedSnapshot.fieldErrors != null) {
|
|
885
|
+
if (this._fieldErrors == null) {
|
|
886
|
+
this._fieldErrors = [];
|
|
843
887
|
}
|
|
844
|
-
for (
|
|
888
|
+
for (let i = 0; i < cachedSnapshot.fieldErrors.length; i++) {
|
|
889
|
+
const error = cachedSnapshot.fieldErrors[i];
|
|
845
890
|
if (this._selector.node.metadata?.throwOnFieldError === true) {
|
|
846
891
|
// If this fragment is @throwOnFieldError, any destructive error
|
|
847
892
|
// encountered inside a resolver's fragment is equivilent to the
|
|
848
893
|
// resolver field having a field error, and we want that to cause this
|
|
849
894
|
// fragment to throw. So, we propagate all errors as is.
|
|
850
|
-
this.
|
|
895
|
+
this._fieldErrors.push(error);
|
|
851
896
|
} else {
|
|
852
897
|
// If this fragment is _not_ @throwOnFieldError, we will simply
|
|
853
898
|
// accept that any destructive errors encountered in the resolver's
|
|
854
899
|
// root fragment will cause the resolver to return null, and well
|
|
855
900
|
// pass the errors along to the logger marked as "handled".
|
|
856
|
-
this.
|
|
901
|
+
this._fieldErrors.push(markFieldErrorHasHandled(error));
|
|
857
902
|
}
|
|
858
903
|
}
|
|
859
904
|
}
|
|
@@ -864,18 +909,21 @@ class RelayReader {
|
|
|
864
909
|
// the errors can be attached to this read's snapshot. This allows the error
|
|
865
910
|
// to be logged.
|
|
866
911
|
if (resolverError) {
|
|
867
|
-
const errorEvent = {
|
|
912
|
+
const errorEvent: FieldError = {
|
|
868
913
|
kind: 'relay_resolver.error',
|
|
869
914
|
fieldPath,
|
|
870
915
|
owner: this._fragmentName,
|
|
871
916
|
error: resolverError,
|
|
872
917
|
shouldThrow: this._selector.node.metadata?.throwOnFieldError ?? false,
|
|
873
918
|
handled: false,
|
|
919
|
+
// the uiContext is always undefined here.
|
|
920
|
+
// the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
|
|
921
|
+
uiContext: undefined,
|
|
874
922
|
};
|
|
875
|
-
if (this.
|
|
876
|
-
this.
|
|
923
|
+
if (this._fieldErrors == null) {
|
|
924
|
+
this._fieldErrors = [errorEvent];
|
|
877
925
|
} else {
|
|
878
|
-
this.
|
|
926
|
+
this._fieldErrors.push(errorEvent);
|
|
879
927
|
}
|
|
880
928
|
}
|
|
881
929
|
|
|
@@ -896,9 +944,11 @@ class RelayReader {
|
|
|
896
944
|
this._missingLiveResolverFields.push(suspenseID);
|
|
897
945
|
}
|
|
898
946
|
if (updatedDataIDs != null) {
|
|
899
|
-
|
|
947
|
+
// Iterating a Set with for of is okay
|
|
948
|
+
// eslint-disable-next-line relay-internal/no-for-of-loops
|
|
949
|
+
updatedDataIDs.forEach(recordID => {
|
|
900
950
|
this._updatedDataIDs.add(recordID);
|
|
901
|
-
}
|
|
951
|
+
});
|
|
902
952
|
}
|
|
903
953
|
}
|
|
904
954
|
|
|
@@ -1068,8 +1118,8 @@ class RelayReader {
|
|
|
1068
1118
|
RelayModernRecord.getDataID(record),
|
|
1069
1119
|
prevData,
|
|
1070
1120
|
);
|
|
1071
|
-
const prevErrors = this.
|
|
1072
|
-
this.
|
|
1121
|
+
const prevErrors = this._fieldErrors;
|
|
1122
|
+
this._fieldErrors = null;
|
|
1073
1123
|
const edgeValue = this._traverse(
|
|
1074
1124
|
field.linkedField,
|
|
1075
1125
|
storeID,
|
|
@@ -1091,8 +1141,13 @@ class RelayReader {
|
|
|
1091
1141
|
const fieldName = field.alias ?? field.name;
|
|
1092
1142
|
const storageKey = getStorageKey(field, this._variables);
|
|
1093
1143
|
const value = RelayModernRecord.getValue(record, storageKey);
|
|
1094
|
-
if (
|
|
1095
|
-
|
|
1144
|
+
if (
|
|
1145
|
+
value === null ||
|
|
1146
|
+
(RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
|
|
1147
|
+
Array.isArray(value) &&
|
|
1148
|
+
value.length === 0)
|
|
1149
|
+
) {
|
|
1150
|
+
this._maybeAddFieldErrors(record, storageKey);
|
|
1096
1151
|
} else if (value === undefined) {
|
|
1097
1152
|
this._markDataAsMissing(fieldName);
|
|
1098
1153
|
}
|
|
@@ -1111,7 +1166,7 @@ class RelayReader {
|
|
|
1111
1166
|
if (linkedID == null) {
|
|
1112
1167
|
data[fieldName] = linkedID;
|
|
1113
1168
|
if (linkedID === null) {
|
|
1114
|
-
this.
|
|
1169
|
+
this._maybeAddFieldErrors(record, storageKey);
|
|
1115
1170
|
} else if (linkedID === undefined) {
|
|
1116
1171
|
this._markDataAsMissing(fieldName);
|
|
1117
1172
|
}
|
|
@@ -1128,8 +1183,8 @@ class RelayReader {
|
|
|
1128
1183
|
RelayModernRecord.getDataID(record),
|
|
1129
1184
|
prevData,
|
|
1130
1185
|
);
|
|
1131
|
-
const prevErrors = this.
|
|
1132
|
-
this.
|
|
1186
|
+
const prevErrors = this._fieldErrors;
|
|
1187
|
+
this._fieldErrors = null;
|
|
1133
1188
|
// $FlowFixMe[incompatible-variance]
|
|
1134
1189
|
const value = this._traverse(field, linkedID, prevData);
|
|
1135
1190
|
|
|
@@ -1139,7 +1194,7 @@ class RelayReader {
|
|
|
1139
1194
|
}
|
|
1140
1195
|
|
|
1141
1196
|
/**
|
|
1142
|
-
* Adds a set of field errors to `this.
|
|
1197
|
+
* Adds a set of field errors to `this._fieldErrors`, ensuring the
|
|
1143
1198
|
* `fieldPath` property of existing field errors are prefixed with the given
|
|
1144
1199
|
* `fieldNameOrIndex`.
|
|
1145
1200
|
*
|
|
@@ -1158,8 +1213,8 @@ class RelayReader {
|
|
|
1158
1213
|
* To achieve this, named field readers must do the following to correctly
|
|
1159
1214
|
* track error filePaths:
|
|
1160
1215
|
*
|
|
1161
|
-
* 1. Stash the value of `this.
|
|
1162
|
-
* 2. Set `this.
|
|
1216
|
+
* 1. Stash the value of `this._fieldErrors` in a local variable
|
|
1217
|
+
* 2. Set `this._fieldErrors` to `null`
|
|
1163
1218
|
* 3. Traverse into the field
|
|
1164
1219
|
* 4. Call this method with the stashed errors and the field's name
|
|
1165
1220
|
*
|
|
@@ -1171,12 +1226,12 @@ class RelayReader {
|
|
|
1171
1226
|
* field error paths.
|
|
1172
1227
|
*/
|
|
1173
1228
|
_prependPreviousErrors(
|
|
1174
|
-
prevErrors: ?Array<
|
|
1229
|
+
prevErrors: ?Array<FieldError>,
|
|
1175
1230
|
fieldNameOrIndex: string | number,
|
|
1176
1231
|
): void {
|
|
1177
|
-
if (this.
|
|
1178
|
-
for (let i = 0; i < this.
|
|
1179
|
-
const event = this.
|
|
1232
|
+
if (this._fieldErrors != null) {
|
|
1233
|
+
for (let i = 0; i < this._fieldErrors.length; i++) {
|
|
1234
|
+
const event = this._fieldErrors[i];
|
|
1180
1235
|
if (
|
|
1181
1236
|
event.owner === this._fragmentName &&
|
|
1182
1237
|
(event.kind === 'missing_expected_data.throw' ||
|
|
@@ -1188,13 +1243,13 @@ class RelayReader {
|
|
|
1188
1243
|
}
|
|
1189
1244
|
}
|
|
1190
1245
|
if (prevErrors != null) {
|
|
1191
|
-
for (let i = this.
|
|
1192
|
-
prevErrors.push(this.
|
|
1246
|
+
for (let i = this._fieldErrors.length - 1; i >= 0; i--) {
|
|
1247
|
+
prevErrors.push(this._fieldErrors[i]);
|
|
1193
1248
|
}
|
|
1194
|
-
this.
|
|
1249
|
+
this._fieldErrors = prevErrors;
|
|
1195
1250
|
}
|
|
1196
1251
|
} else {
|
|
1197
|
-
this.
|
|
1252
|
+
this._fieldErrors = prevErrors;
|
|
1198
1253
|
}
|
|
1199
1254
|
}
|
|
1200
1255
|
|
|
@@ -1215,7 +1270,7 @@ class RelayReader {
|
|
|
1215
1270
|
if (externalRef === undefined) {
|
|
1216
1271
|
this._markDataAsMissing(fieldName);
|
|
1217
1272
|
} else if (externalRef === null) {
|
|
1218
|
-
this.
|
|
1273
|
+
this._maybeAddFieldErrors(record, storageKey);
|
|
1219
1274
|
}
|
|
1220
1275
|
return data[fieldName];
|
|
1221
1276
|
}
|
|
@@ -1243,8 +1298,13 @@ class RelayReader {
|
|
|
1243
1298
|
): ?mixed {
|
|
1244
1299
|
const storageKey = getStorageKey(field, this._variables);
|
|
1245
1300
|
const linkedIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey);
|
|
1246
|
-
if (
|
|
1247
|
-
|
|
1301
|
+
if (
|
|
1302
|
+
linkedIDs === null ||
|
|
1303
|
+
(RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
|
|
1304
|
+
Array.isArray(linkedIDs) &&
|
|
1305
|
+
linkedIDs.length === 0)
|
|
1306
|
+
) {
|
|
1307
|
+
this._maybeAddFieldErrors(record, storageKey);
|
|
1248
1308
|
}
|
|
1249
1309
|
return this._readLinkedIds(field, linkedIDs, record, data);
|
|
1250
1310
|
}
|
|
@@ -1274,8 +1334,8 @@ class RelayReader {
|
|
|
1274
1334
|
RelayModernRecord.getDataID(record),
|
|
1275
1335
|
prevData,
|
|
1276
1336
|
);
|
|
1277
|
-
const prevErrors = this.
|
|
1278
|
-
this.
|
|
1337
|
+
const prevErrors = this._fieldErrors;
|
|
1338
|
+
this._fieldErrors = null;
|
|
1279
1339
|
const linkedArray = prevData || [];
|
|
1280
1340
|
linkedIDs.forEach((linkedID, nextIndex) => {
|
|
1281
1341
|
if (linkedID == null) {
|
|
@@ -1295,8 +1355,8 @@ class RelayReader {
|
|
|
1295
1355
|
RelayModernRecord.getDataID(record),
|
|
1296
1356
|
prevItem,
|
|
1297
1357
|
);
|
|
1298
|
-
const prevErrors = this.
|
|
1299
|
-
this.
|
|
1358
|
+
const prevErrors = this._fieldErrors;
|
|
1359
|
+
this._fieldErrors = null;
|
|
1300
1360
|
// $FlowFixMe[cannot-write]
|
|
1301
1361
|
// $FlowFixMe[incompatible-variance]
|
|
1302
1362
|
linkedArray[nextIndex] = this._traverse(field, linkedID, prevItem);
|
|
@@ -1319,10 +1379,15 @@ class RelayReader {
|
|
|
1319
1379
|
// Determine the component module from the store: if the field is missing
|
|
1320
1380
|
// it means we don't know what component to render the match with.
|
|
1321
1381
|
const componentKey = getModuleComponentKey(moduleImport.documentName);
|
|
1382
|
+
const relayStoreComponent = RelayModernRecord.getValue(
|
|
1383
|
+
record,
|
|
1384
|
+
componentKey,
|
|
1385
|
+
);
|
|
1322
1386
|
// componentModuleProvider is used by Client 3D for read time resolvers.
|
|
1323
1387
|
const component =
|
|
1324
|
-
|
|
1325
|
-
|
|
1388
|
+
relayStoreComponent !== undefined
|
|
1389
|
+
? relayStoreComponent
|
|
1390
|
+
: moduleImport.componentModuleProvider;
|
|
1326
1391
|
if (component == null) {
|
|
1327
1392
|
if (component === undefined) {
|
|
1328
1393
|
this._markDataAsMissing('<module-import>');
|
|
@@ -1366,8 +1431,8 @@ class RelayReader {
|
|
|
1366
1431
|
record: Record,
|
|
1367
1432
|
data: SelectorData,
|
|
1368
1433
|
) {
|
|
1369
|
-
const prevErrors = this.
|
|
1370
|
-
this.
|
|
1434
|
+
const prevErrors = this._fieldErrors;
|
|
1435
|
+
this._fieldErrors = null;
|
|
1371
1436
|
let fieldValue = this._readInlineFragment(
|
|
1372
1437
|
aliasedInlineFragment.fragment,
|
|
1373
1438
|
record,
|
|
@@ -1597,9 +1662,7 @@ class RelayReader {
|
|
|
1597
1662
|
}
|
|
1598
1663
|
}
|
|
1599
1664
|
|
|
1600
|
-
function markFieldErrorHasHandled(
|
|
1601
|
-
event: ErrorResponseField,
|
|
1602
|
-
): ErrorResponseField {
|
|
1665
|
+
function markFieldErrorHasHandled(event: FieldError): FieldError {
|
|
1603
1666
|
switch (event.kind) {
|
|
1604
1667
|
case 'missing_expected_data.throw':
|
|
1605
1668
|
case 'missing_required_field.throw':
|
|
@@ -38,7 +38,8 @@ const RelayStoreUtils = require('./RelayStoreUtils');
|
|
|
38
38
|
const {generateTypeID} = require('./TypeID');
|
|
39
39
|
const invariant = require('invariant');
|
|
40
40
|
|
|
41
|
-
const {getStorageKey, getModuleOperationKey} =
|
|
41
|
+
const {getReadTimeResolverStorageKey, getStorageKey, getModuleOperationKey} =
|
|
42
|
+
RelayStoreUtils;
|
|
42
43
|
|
|
43
44
|
function mark(
|
|
44
45
|
recordSource: RecordSource,
|
|
@@ -216,8 +217,6 @@ class RelayReferenceMarker {
|
|
|
216
217
|
this._traverseSelections(selection.fragment.selections, record);
|
|
217
218
|
break;
|
|
218
219
|
case 'RelayResolver':
|
|
219
|
-
this._traverseResolverField(selection, record);
|
|
220
|
-
break;
|
|
221
220
|
case 'RelayLiveResolver':
|
|
222
221
|
this._traverseResolverField(selection, record);
|
|
223
222
|
break;
|
|
@@ -291,7 +290,7 @@ class RelayReferenceMarker {
|
|
|
291
290
|
field: NormalizationResolverField | NormalizationLiveResolverField,
|
|
292
291
|
record: Record,
|
|
293
292
|
): ?DataID {
|
|
294
|
-
const storageKey =
|
|
293
|
+
const storageKey = getReadTimeResolverStorageKey(field, this._variables);
|
|
295
294
|
const dataID = RelayModernRecord.getLinkedRecordID(record, storageKey);
|
|
296
295
|
|
|
297
296
|
// If the resolver value has been created, we should retain it.
|