relay-runtime 0.0.0-main-98162d36 → 0.0.0-main-47d9d202

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.
@@ -22,16 +22,17 @@ import type {
22
22
  MissingRequiredFields,
23
23
  PluralReaderSelector,
24
24
  RelayContext,
25
+ RelayResolverErrors,
25
26
  SelectorData,
26
27
  SingularReaderSelector,
27
28
  Snapshot,
28
29
  } from './RelayStoreTypes';
29
30
 
30
31
  const getPendingOperationsForFragment = require('../util/getPendingOperationsForFragment');
32
+ const handlePotentialSnapshotErrors = require('../util/handlePotentialSnapshotErrors');
31
33
  const isScalarAndEqual = require('../util/isScalarAndEqual');
32
34
  const recycleNodesInto = require('../util/recycleNodesInto');
33
35
  const RelayFeatureFlags = require('../util/RelayFeatureFlags');
34
- const reportMissingRequiredFields = require('../util/reportMissingRequiredFields');
35
36
  const {createRequestDescriptor} = require('./RelayModernOperationDescriptor');
36
37
  const {
37
38
  areEqualSelectors,
@@ -228,6 +229,7 @@ class SelectorResolver {
228
229
  _environment: IEnvironment;
229
230
  _isMissingData: boolean;
230
231
  _missingRequiredFields: ?MissingRequiredFields;
232
+ _relayResolverErrors: RelayResolverErrors;
231
233
  _rootIsQueryRenderer: boolean;
232
234
  _selector: SingularReaderSelector;
233
235
  _subscription: ?Disposable;
@@ -244,6 +246,7 @@ class SelectorResolver {
244
246
  this._data = snapshot.data;
245
247
  this._isMissingData = snapshot.isMissingData;
246
248
  this._missingRequiredFields = snapshot.missingRequiredFields;
249
+ this._relayResolverErrors = snapshot.relayResolverErrors;
247
250
  this._environment = environment;
248
251
  this._rootIsQueryRenderer = rootIsQueryRenderer;
249
252
  this._selector = selector;
@@ -324,12 +327,11 @@ class SelectorResolver {
324
327
  }
325
328
  }
326
329
  }
327
- if (this._missingRequiredFields != null) {
328
- reportMissingRequiredFields(
329
- this._environment,
330
- this._missingRequiredFields,
331
- );
332
- }
330
+ handlePotentialSnapshotErrors(
331
+ this._environment,
332
+ this._missingRequiredFields,
333
+ this._relayResolverErrors,
334
+ );
333
335
  return this._data;
334
336
  }
335
337
 
@@ -345,6 +347,7 @@ class SelectorResolver {
345
347
  this._data = recycleNodesInto(this._data, snapshot.data);
346
348
  this._isMissingData = snapshot.isMissingData;
347
349
  this._missingRequiredFields = snapshot.missingRequiredFields;
350
+ this._relayResolverErrors = snapshot.relayResolverErrors;
348
351
  this._selector = selector;
349
352
  this._subscription = this._environment.subscribe(snapshot, this._onChange);
350
353
  }
@@ -381,6 +384,7 @@ class SelectorResolver {
381
384
  this._data = snapshot.data;
382
385
  this._isMissingData = snapshot.isMissingData;
383
386
  this._missingRequiredFields = snapshot.missingRequiredFields;
387
+ this._relayResolverErrors = snapshot.relayResolverErrors;
384
388
  this._callback();
385
389
  };
386
390
  }
@@ -35,6 +35,7 @@ import type {
35
35
  MissingRequiredFields,
36
36
  Record,
37
37
  RecordSource,
38
+ RelayResolverErrors,
38
39
  RequestDescriptor,
39
40
  SelectorData,
40
41
  SingularReaderSelector,
@@ -109,6 +110,7 @@ class RelayReader {
109
110
  _selector: SingularReaderSelector;
110
111
  _variables: Variables;
111
112
  _resolverCache: ResolverCache;
113
+ _resolverErrors: RelayResolverErrors;
112
114
  _fragmentName: string;
113
115
 
114
116
  constructor(
@@ -131,6 +133,7 @@ class RelayReader {
131
133
  this._selector = selector;
132
134
  this._variables = selector.variables;
133
135
  this._resolverCache = resolverCache;
136
+ this._resolverErrors = [];
134
137
  this._fragmentName = selector.node.name;
135
138
  }
136
139
 
@@ -204,6 +207,7 @@ class RelayReader {
204
207
  seenRecords: this._seenRecords,
205
208
  selector: this._selector,
206
209
  missingRequiredFields: this._missingRequiredFields,
210
+ relayResolverErrors: this._resolverErrors,
207
211
  };
208
212
  }
209
213
 
@@ -525,6 +529,9 @@ class RelayReader {
525
529
  let fragmentReaderSelector;
526
530
  let fragmentMissingRequiredFields: ?MissingRequiredFields;
527
531
  let previousMissingRequriedFields: ?MissingRequiredFields;
532
+
533
+ let currentResolverErrors: RelayResolverErrors;
534
+ let previousResolverErrors: RelayResolverErrors;
528
535
  const fragmentSeenRecordIDs = new Set();
529
536
 
530
537
  const getDataForResolverFragment = singularReaderSelector => {
@@ -541,12 +548,16 @@ class RelayReader {
541
548
  const resolverFragmentData = {};
542
549
  previousMissingRequriedFields = this._missingRequiredFields;
543
550
  this._missingRequiredFields = null;
551
+
552
+ previousResolverErrors = this._resolverErrors;
553
+ this._resolverErrors = [];
544
554
  this._createInlineDataOrResolverFragmentPointer(
545
555
  singularReaderSelector.node,
546
556
  record,
547
557
  resolverFragmentData,
548
558
  );
549
559
  fragmentMissingRequiredFields = this._missingRequiredFields;
560
+ currentResolverErrors = this._resolverErrors;
550
561
  fragmentValue = resolverFragmentData[FRAGMENTS_KEY]?.[fragment.name];
551
562
  invariant(
552
563
  typeof fragmentValue === 'object' && fragmentValue !== null,
@@ -556,39 +567,56 @@ class RelayReader {
556
567
  } finally {
557
568
  this._seenRecords = existingSeenRecords;
558
569
  this._missingRequiredFields = previousMissingRequriedFields;
570
+ this._resolverErrors = previousResolverErrors;
559
571
  }
560
572
  };
561
573
  const resolverContext = {getDataForResolverFragment};
562
574
 
563
- const [result, seenRecord, missingRequiredFields] =
575
+ const evaluate = () => {
576
+ const key = {
577
+ __id: RelayModernRecord.getDataID(record),
578
+ __fragmentOwner: this._owner,
579
+ __fragments: {
580
+ [fragment.name]: {}, // Arguments to this fragment; not yet supported.
581
+ },
582
+ };
583
+ return withResolverContext(resolverContext, () => {
584
+ let resolverResult = null;
585
+ try {
586
+ // $FlowFixMe[prop-missing] - resolver module's type signature is a lie
587
+ resolverResult = resolverModule(key);
588
+ } catch (e) {
589
+ // `field.path` is typed as nullable while we rollout compiler changes.
590
+ const path = field.path ?? '[UNKNOWN]';
591
+ currentResolverErrors.push({
592
+ field: {path, owner: this._fragmentName},
593
+ error: e,
594
+ });
595
+ }
596
+ return {
597
+ resolverResult,
598
+ errors: currentResolverErrors,
599
+ fragmentValue,
600
+ resolverID,
601
+ seenRecordIDs: fragmentSeenRecordIDs,
602
+ readerSelector: fragmentReaderSelector,
603
+ missingRequiredFields: fragmentMissingRequiredFields,
604
+ };
605
+ });
606
+ };
607
+
608
+ const [result, seenRecord, resolverErrors, missingRequiredFields] =
564
609
  this._resolverCache.readFromCacheOrEvaluate(
565
610
  record,
566
611
  field,
567
612
  this._variables,
568
- () => {
569
- const key = {
570
- __id: RelayModernRecord.getDataID(record),
571
- __fragmentOwner: this._owner,
572
- __fragments: {
573
- [fragment.name]: {}, // Arguments to this fragment; not yet supported.
574
- },
575
- };
576
- return withResolverContext(resolverContext, () => {
577
- // $FlowFixMe[prop-missing] - resolver module's type signature is a lie
578
- const resolverResult = resolverModule(key);
579
- return {
580
- resolverResult,
581
- fragmentValue,
582
- resolverID,
583
- seenRecordIDs: fragmentSeenRecordIDs,
584
- readerSelector: fragmentReaderSelector,
585
- missingRequiredFields: fragmentMissingRequiredFields,
586
- };
587
- });
588
- },
613
+ evaluate,
589
614
  getDataForResolverFragment,
590
615
  );
591
616
 
617
+ for (const resolverError of resolverErrors) {
618
+ this._resolverErrors.push(resolverError);
619
+ }
592
620
  if (missingRequiredFields != null) {
593
621
  this._addMissingRequiredFields(missingRequiredFields);
594
622
  }
@@ -105,6 +105,7 @@ class RelayStoreSubscriptions implements StoreSubscriptions {
105
105
  seenRecords: backup.seenRecords,
106
106
  selector: backup.selector,
107
107
  missingRequiredFields: backup.missingRequiredFields,
108
+ relayResolverErrors: backup.relayResolverErrors,
108
109
  };
109
110
  } else {
110
111
  subscription.stale = true;
@@ -167,6 +168,7 @@ class RelayStoreSubscriptions implements StoreSubscriptions {
167
168
  seenRecords: nextSnapshot.seenRecords,
168
169
  selector: nextSnapshot.selector,
169
170
  missingRequiredFields: nextSnapshot.missingRequiredFields,
171
+ relayResolverErrors: nextSnapshot.relayResolverErrors,
170
172
  }: Snapshot);
171
173
  if (__DEV__) {
172
174
  deepFreeze(nextSnapshot);
@@ -113,14 +113,14 @@ export type NormalizationSelector = {|
113
113
  +variables: Variables,
114
114
  |};
115
115
 
116
- type MissingRequiredField = {|
116
+ type FieldLocation = {|
117
117
  path: string,
118
118
  owner: string,
119
119
  |};
120
120
 
121
121
  export type MissingRequiredFields = $ReadOnly<
122
- | {|action: 'THROW', field: MissingRequiredField|}
123
- | {|action: 'LOG', fields: Array<MissingRequiredField>|},
122
+ | {|action: 'THROW', field: FieldLocation|}
123
+ | {|action: 'LOG', fields: Array<FieldLocation>|},
124
124
  >;
125
125
 
126
126
  export type ClientEdgeTraversalInfo = {|
@@ -136,6 +136,13 @@ export type MissingClientEdgeRequestInfo = {|
136
136
  +clientEdgeDestinationID: DataID,
137
137
  |};
138
138
 
139
+ export type RelayResolverError = {|
140
+ field: FieldLocation,
141
+ error: Error,
142
+ |};
143
+
144
+ export type RelayResolverErrors = Array<RelayResolverError>;
145
+
139
146
  /**
140
147
  * A representation of a selector and its results at a particular point in time.
141
148
  */
@@ -146,6 +153,7 @@ export type Snapshot = {|
146
153
  +seenRecords: DataIDSet,
147
154
  +selector: SingularReaderSelector,
148
155
  +missingRequiredFields: ?MissingRequiredFields,
156
+ +relayResolverErrors: RelayResolverErrors,
149
157
  |};
150
158
 
151
159
  /**
@@ -1073,6 +1081,12 @@ export type RequiredFieldLogger = (
1073
1081
  +kind: 'missing_field.throw',
1074
1082
  +owner: string,
1075
1083
  +fieldPath: string,
1084
+ |}
1085
+ | {|
1086
+ +kind: 'relay_resolver.error',
1087
+ +owner: string,
1088
+ +fieldPath: string,
1089
+ +error: Error,
1076
1090
  |},
1077
1091
  ) => void;
1078
1092
 
@@ -220,6 +220,7 @@ const RelayStoreUtils = {
220
220
  RELAY_RESOLVER_INPUTS_KEY: '__resolverInputValues',
221
221
  RELAY_RESOLVER_READER_SELECTOR_KEY: '__resolverReaderSelector',
222
222
  RELAY_RESOLVER_MISSING_REQUIRED_FIELDS_KEY: '__resolverMissingRequiredFields',
223
+ RELAY_RESOLVER_ERROR_KEY: '__resolverError',
223
224
 
224
225
  formatStorageKey,
225
226
  getArgumentValue,
@@ -18,6 +18,7 @@ import type {
18
18
  MissingRequiredFields,
19
19
  MutableRecordSource,
20
20
  Record,
21
+ RelayResolverErrors,
21
22
  SingularReaderSelector,
22
23
  } from './RelayStoreTypes';
23
24
 
@@ -25,6 +26,7 @@ const recycleNodesInto = require('../util/recycleNodesInto');
25
26
  const {generateClientID} = require('./ClientID');
26
27
  const RelayModernRecord = require('./RelayModernRecord');
27
28
  const {
29
+ RELAY_RESOLVER_ERROR_KEY,
28
30
  RELAY_RESOLVER_INPUTS_KEY,
29
31
  RELAY_RESOLVER_INVALIDATION_KEY,
30
32
  RELAY_RESOLVER_MISSING_REQUIRED_FIELDS_KEY,
@@ -42,6 +44,7 @@ type EvaluationResult<T> = {|
42
44
  resolverID: ResolverID,
43
45
  seenRecordIDs: Set<DataID>,
44
46
  readerSelector: SingularReaderSelector,
47
+ errors: RelayResolverErrors,
45
48
  missingRequiredFields: ?MissingRequiredFields,
46
49
  |};
47
50
 
@@ -52,7 +55,12 @@ export interface ResolverCache {
52
55
  variables: Variables,
53
56
  evaluate: () => EvaluationResult<T>,
54
57
  getDataForResolverFragment: (SingularReaderSelector) => mixed,
55
- ): [T /* Answer */, ?DataID /* Seen record */, ?MissingRequiredFields];
58
+ ): [
59
+ T /* Answer */,
60
+ ?DataID /* Seen record */,
61
+ RelayResolverErrors,
62
+ ?MissingRequiredFields,
63
+ ];
56
64
  invalidateDataIDs(
57
65
  updatedDataIDs: Set<DataID>, // Mutated in place
58
66
  ): void;
@@ -68,9 +76,14 @@ class NoopResolverCache implements ResolverCache {
68
76
  variables: Variables,
69
77
  evaluate: () => EvaluationResult<T>,
70
78
  getDataForResolverFragment: SingularReaderSelector => mixed,
71
- ): [T /* Answer */, ?DataID /* Seen record */, ?MissingRequiredFields] {
72
- const {resolverResult, missingRequiredFields} = evaluate();
73
- return [resolverResult, undefined, missingRequiredFields];
79
+ ): [
80
+ T /* Answer */,
81
+ ?DataID /* Seen record */,
82
+ RelayResolverErrors,
83
+ ?MissingRequiredFields,
84
+ ] {
85
+ const {resolverResult, missingRequiredFields, errors} = evaluate();
86
+ return [resolverResult, undefined, errors, missingRequiredFields];
74
87
  }
75
88
  invalidateDataIDs(updatedDataIDs: Set<DataID>): void {}
76
89
  }
@@ -106,7 +119,12 @@ class RecordResolverCache implements ResolverCache {
106
119
  variables: Variables,
107
120
  evaluate: () => EvaluationResult<T>,
108
121
  getDataForResolverFragment: SingularReaderSelector => mixed,
109
- ): [T /* Answer */, ?DataID /* Seen record */, ?MissingRequiredFields] {
122
+ ): [
123
+ T /* Answer */,
124
+ ?DataID /* Seen record */,
125
+ RelayResolverErrors,
126
+ ?MissingRequiredFields,
127
+ ] {
110
128
  const recordSource = this._getRecordSource();
111
129
  const recordID = RelayModernRecord.getDataID(record);
112
130
 
@@ -142,6 +160,11 @@ class RecordResolverCache implements ResolverCache {
142
160
  RELAY_RESOLVER_MISSING_REQUIRED_FIELDS_KEY,
143
161
  evaluationResult.missingRequiredFields,
144
162
  );
163
+ RelayModernRecord.setValue(
164
+ linkedRecord,
165
+ RELAY_RESOLVER_ERROR_KEY,
166
+ evaluationResult.errors,
167
+ );
145
168
  recordSource.set(linkedID, linkedRecord);
146
169
 
147
170
  // Link the resolver value record to the resolver field of the record being read:
@@ -168,7 +191,10 @@ class RecordResolverCache implements ResolverCache {
168
191
  const missingRequiredFields: ?MissingRequiredFields =
169
192
  // $FlowFixMe[incompatible-type] - casting mixed
170
193
  linkedRecord[RELAY_RESOLVER_MISSING_REQUIRED_FIELDS_KEY];
171
- return [answer, linkedID, missingRequiredFields];
194
+
195
+ // $FlowFixMe[incompatible-type] - casting mixed
196
+ const errors: RelayResolverErrors = linkedRecord[RELAY_RESOLVER_ERROR_KEY];
197
+ return [answer, linkedID, errors, missingRequiredFields];
172
198
  }
173
199
 
174
200
  invalidateDataIDs(
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ * @emails oncall+relay
9
+ * @format
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ import type {
15
+ IEnvironment,
16
+ MissingRequiredFields,
17
+ RelayResolverErrors,
18
+ } from '../store/RelayStoreTypes';
19
+
20
+ function handlePotentialSnapshotErrors(
21
+ environment: IEnvironment,
22
+ missingRequiredFields: ?MissingRequiredFields,
23
+ relayResolverErrors: RelayResolverErrors,
24
+ ) {
25
+ for (const resolverError of relayResolverErrors) {
26
+ environment.requiredFieldLogger({
27
+ kind: 'relay_resolver.error',
28
+ owner: resolverError.field.owner,
29
+ fieldPath: resolverError.field.path,
30
+ error: resolverError.error,
31
+ });
32
+ }
33
+ if (missingRequiredFields != null) {
34
+ switch (missingRequiredFields.action) {
35
+ case 'THROW': {
36
+ const {path, owner} = missingRequiredFields.field;
37
+ // This gives the consumer the chance to throw their own error if they so wish.
38
+ environment.requiredFieldLogger({
39
+ kind: 'missing_field.throw',
40
+ owner,
41
+ fieldPath: path,
42
+ });
43
+ throw new Error(
44
+ `Relay: Missing @required value at path '${path}' in '${owner}'.`,
45
+ );
46
+ }
47
+ case 'LOG':
48
+ missingRequiredFields.fields.forEach(({path, owner}) => {
49
+ environment.requiredFieldLogger({
50
+ kind: 'missing_field.log',
51
+ owner,
52
+ fieldPath: path,
53
+ });
54
+ });
55
+ break;
56
+ default: {
57
+ (missingRequiredFields.action: empty);
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ module.exports = handlePotentialSnapshotErrors;
@@ -1,48 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- *
8
- * @emails oncall+relay
9
- * @format
10
- */
11
- 'use strict';
12
-
13
- function reportMissingRequiredFields(environment, missingRequiredFields) {
14
- switch (missingRequiredFields.action) {
15
- case 'THROW':
16
- {
17
- var _missingRequiredField = missingRequiredFields.field,
18
- path = _missingRequiredField.path,
19
- owner = _missingRequiredField.owner; // This gives the consumer the chance to throw their own error if they so wish.
20
-
21
- environment.requiredFieldLogger({
22
- kind: 'missing_field.throw',
23
- owner: owner,
24
- fieldPath: path
25
- });
26
- throw new Error("Relay: Missing @required value at path '".concat(path, "' in '").concat(owner, "'."));
27
- }
28
-
29
- case 'LOG':
30
- missingRequiredFields.fields.forEach(function (_ref) {
31
- var path = _ref.path,
32
- owner = _ref.owner;
33
- environment.requiredFieldLogger({
34
- kind: 'missing_field.log',
35
- owner: owner,
36
- fieldPath: path
37
- });
38
- });
39
- break;
40
-
41
- default:
42
- {
43
- missingRequiredFields.action;
44
- }
45
- }
46
- }
47
-
48
- module.exports = reportMissingRequiredFields;
@@ -1,51 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @flow
8
- * @emails oncall+relay
9
- * @format
10
- */
11
-
12
- 'use strict';
13
-
14
- import type {
15
- IEnvironment,
16
- MissingRequiredFields,
17
- } from '../store/RelayStoreTypes';
18
-
19
- function reportMissingRequiredFields(
20
- environment: IEnvironment,
21
- missingRequiredFields: MissingRequiredFields,
22
- ) {
23
- switch (missingRequiredFields.action) {
24
- case 'THROW': {
25
- const {path, owner} = missingRequiredFields.field;
26
- // This gives the consumer the chance to throw their own error if they so wish.
27
- environment.requiredFieldLogger({
28
- kind: 'missing_field.throw',
29
- owner,
30
- fieldPath: path,
31
- });
32
- throw new Error(
33
- `Relay: Missing @required value at path '${path}' in '${owner}'.`,
34
- );
35
- }
36
- case 'LOG':
37
- missingRequiredFields.fields.forEach(({path, owner}) => {
38
- environment.requiredFieldLogger({
39
- kind: 'missing_field.log',
40
- owner,
41
- fieldPath: path,
42
- });
43
- });
44
- break;
45
- default: {
46
- (missingRequiredFields.action: empty);
47
- }
48
- }
49
- }
50
-
51
- module.exports = reportMissingRequiredFields;