relay-runtime 0.0.0-main-3064e18c → 0.0.0-main-84218519

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.
@@ -217,7 +217,7 @@ class RelayReader {
217
217
  }
218
218
  }
219
219
 
220
- _markDataAsMissing(): void {
220
+ _markDataAsMissing(fieldName: string): void {
221
221
  if (this._isWithinUnmatchedTypeRefinement) {
222
222
  return;
223
223
  }
@@ -226,7 +226,6 @@ class RelayReader {
226
226
  }
227
227
 
228
228
  // we will add the path later
229
- const fieldPath = '';
230
229
  const owner = this._fragmentName;
231
230
 
232
231
  this._errorResponseFields.push(
@@ -234,10 +233,10 @@ class RelayReader {
234
233
  ? {
235
234
  kind: 'missing_expected_data.throw',
236
235
  owner,
237
- fieldPath,
236
+ fieldPath: fieldName,
238
237
  handled: false,
239
238
  }
240
- : {kind: 'missing_expected_data.log', owner, fieldPath},
239
+ : {kind: 'missing_expected_data.log', owner, fieldPath: fieldName},
241
240
  );
242
241
 
243
242
  this._isMissingData = true;
@@ -266,7 +265,7 @@ class RelayReader {
266
265
  this._seenRecords.add(dataID);
267
266
  if (record == null) {
268
267
  if (record === undefined) {
269
- this._markDataAsMissing();
268
+ this._markDataAsMissing('<record>');
270
269
  }
271
270
  return record;
272
271
  }
@@ -489,12 +488,15 @@ class RelayReader {
489
488
  this._createFragmentPointer(selection, record, data);
490
489
  break;
491
490
  case 'AliasedInlineFragmentSpread': {
491
+ const prevErrors = this._errorResponseFields;
492
+ this._errorResponseFields = null;
492
493
  let fieldValue = this._readInlineFragment(
493
494
  selection.fragment,
494
495
  record,
495
496
  {},
496
497
  true,
497
498
  );
499
+ this._prependPreviousErrors(prevErrors, selection.name);
498
500
  if (fieldValue === false) {
499
501
  fieldValue = null;
500
502
  }
@@ -601,9 +603,12 @@ class RelayReader {
601
603
  data: SelectorData,
602
604
  ): mixed {
603
605
  const parentRecordID = RelayModernRecord.getDataID(record);
606
+ const prevErrors = this._errorResponseFields;
607
+ this._errorResponseFields = null;
604
608
  const result = this._readResolverFieldImpl(field, parentRecordID);
605
609
 
606
610
  const fieldName = field.alias ?? field.name;
611
+ this._prependPreviousErrors(prevErrors, fieldName);
607
612
  data[fieldName] = result;
608
613
  return result;
609
614
  }
@@ -982,12 +987,15 @@ class RelayReader {
982
987
  RelayModernRecord.getDataID(record),
983
988
  prevData,
984
989
  );
990
+ const prevErrors = this._errorResponseFields;
991
+ this._errorResponseFields = null;
985
992
  const edgeValue = this._traverse(
986
993
  field.linkedField,
987
994
  storeID,
988
995
  // $FlowFixMe[incompatible-variance]
989
996
  prevData,
990
997
  );
998
+ this._prependPreviousErrors(prevErrors, fieldName);
991
999
  this._clientEdgeTraversalPath.pop();
992
1000
  data[fieldName] = edgeValue;
993
1001
  return edgeValue;
@@ -1005,7 +1013,7 @@ class RelayReader {
1005
1013
  if (value === null) {
1006
1014
  this._maybeAddErrorResponseFields(record, storageKey);
1007
1015
  } else if (value === undefined) {
1008
- this._markDataAsMissing();
1016
+ this._markDataAsMissing(fieldName);
1009
1017
  }
1010
1018
  data[fieldName] = value;
1011
1019
  return value;
@@ -1024,7 +1032,7 @@ class RelayReader {
1024
1032
  if (linkedID === null) {
1025
1033
  this._maybeAddErrorResponseFields(record, storageKey);
1026
1034
  } else if (linkedID === undefined) {
1027
- this._markDataAsMissing();
1035
+ this._markDataAsMissing(fieldName);
1028
1036
  }
1029
1037
  return linkedID;
1030
1038
  }
@@ -1039,12 +1047,73 @@ class RelayReader {
1039
1047
  RelayModernRecord.getDataID(record),
1040
1048
  prevData,
1041
1049
  );
1050
+ const prevErrors = this._errorResponseFields;
1051
+ this._errorResponseFields = null;
1042
1052
  // $FlowFixMe[incompatible-variance]
1043
1053
  const value = this._traverse(field, linkedID, prevData);
1054
+
1055
+ this._prependPreviousErrors(prevErrors, fieldName);
1044
1056
  data[fieldName] = value;
1045
1057
  return value;
1046
1058
  }
1047
1059
 
1060
+ /**
1061
+ * Adds a set of field errors to `this._errorResponseFields`, ensuring the
1062
+ * `fieldPath` property of existing field errors are prefixed with the given
1063
+ * `fieldNameOrIndex`.
1064
+ *
1065
+ * In order to make field errors maximally useful in logs/errors, we want to
1066
+ * include the path to the field that caused the error. A naive approach would
1067
+ * be to maintain a path property on RelayReader which we push/pop field names
1068
+ * to as we traverse into fields/etc. However, this would be expensive to
1069
+ * maintain, and in the common case where there are no field errors, the work
1070
+ * would go unused.
1071
+ *
1072
+ * Instead, we take a lazy approach where as we exit the recurison into a
1073
+ * field/etc we prepend any errors encountered while traversing that field
1074
+ * with the field name. This is somewhat more expensive in the error case, but
1075
+ * ~free in the common case where there are no errors.
1076
+ *
1077
+ * To achieve this, named field readers must do the following to correctly
1078
+ * track error filePaths:
1079
+ *
1080
+ * 1. Stash the value of `this._errorResponseFields` in a local variable
1081
+ * 2. Set `this._errorResponseFields` to `null`
1082
+ * 3. Traverse into the field
1083
+ * 4. Call this method with the stashed errors and the field's name
1084
+ *
1085
+ * Similarly, when creating field errors, we simply initialize the `fieldPath`
1086
+ * as the direct field name.
1087
+ *
1088
+ * Today we only use this apporach for `missing_expected_data` errors, but we
1089
+ * intend to broaden it to handle all field error paths.
1090
+ */
1091
+ _prependPreviousErrors(
1092
+ prevErrors: ?Array<ErrorResponseField>,
1093
+ fieldNameOrIndex: string | number,
1094
+ ): void {
1095
+ if (this._errorResponseFields != null) {
1096
+ for (let i = 0; i < this._errorResponseFields.length; i++) {
1097
+ const event = this._errorResponseFields[i];
1098
+ if (
1099
+ event.owner === this._fragmentName &&
1100
+ (event.kind === 'missing_expected_data.throw' ||
1101
+ event.kind === 'missing_expected_data.log')
1102
+ ) {
1103
+ event.fieldPath = `${fieldNameOrIndex}.${event.fieldPath}`;
1104
+ }
1105
+ }
1106
+ if (prevErrors != null) {
1107
+ for (let i = this._errorResponseFields.length - 1; i >= 0; i--) {
1108
+ prevErrors.push(this._errorResponseFields[i]);
1109
+ }
1110
+ this._errorResponseFields = prevErrors;
1111
+ }
1112
+ } else {
1113
+ this._errorResponseFields = prevErrors;
1114
+ }
1115
+ }
1116
+
1048
1117
  _readActorChange(
1049
1118
  field: ReaderActorChange,
1050
1119
  record: Record,
@@ -1060,7 +1129,7 @@ class RelayReader {
1060
1129
  if (externalRef == null) {
1061
1130
  data[fieldName] = externalRef;
1062
1131
  if (externalRef === undefined) {
1063
- this._markDataAsMissing();
1132
+ this._markDataAsMissing(fieldName);
1064
1133
  } else if (externalRef === null) {
1065
1134
  this._maybeAddErrorResponseFields(record, storageKey);
1066
1135
  }
@@ -1107,7 +1176,7 @@ class RelayReader {
1107
1176
  if (linkedIDs == null) {
1108
1177
  data[fieldName] = linkedIDs;
1109
1178
  if (linkedIDs === undefined) {
1110
- this._markDataAsMissing();
1179
+ this._markDataAsMissing(fieldName);
1111
1180
  }
1112
1181
  return linkedIDs;
1113
1182
  }
@@ -1121,11 +1190,13 @@ class RelayReader {
1121
1190
  RelayModernRecord.getDataID(record),
1122
1191
  prevData,
1123
1192
  );
1193
+ const prevErrors = this._errorResponseFields;
1194
+ this._errorResponseFields = null;
1124
1195
  const linkedArray = prevData || [];
1125
1196
  linkedIDs.forEach((linkedID, nextIndex) => {
1126
1197
  if (linkedID == null) {
1127
1198
  if (linkedID === undefined) {
1128
- this._markDataAsMissing();
1199
+ this._markDataAsMissing(String(nextIndex));
1129
1200
  }
1130
1201
  // $FlowFixMe[cannot-write]
1131
1202
  linkedArray[nextIndex] = linkedID;
@@ -1140,10 +1211,14 @@ class RelayReader {
1140
1211
  RelayModernRecord.getDataID(record),
1141
1212
  prevItem,
1142
1213
  );
1214
+ const prevErrors = this._errorResponseFields;
1215
+ this._errorResponseFields = null;
1143
1216
  // $FlowFixMe[cannot-write]
1144
1217
  // $FlowFixMe[incompatible-variance]
1145
1218
  linkedArray[nextIndex] = this._traverse(field, linkedID, prevItem);
1219
+ this._prependPreviousErrors(prevErrors, nextIndex);
1146
1220
  });
1221
+ this._prependPreviousErrors(prevErrors, fieldName);
1147
1222
  data[fieldName] = linkedArray;
1148
1223
  return linkedArray;
1149
1224
  }
@@ -1166,7 +1241,7 @@ class RelayReader {
1166
1241
  RelayModernRecord.getValue(record, componentKey);
1167
1242
  if (component == null) {
1168
1243
  if (component === undefined) {
1169
- this._markDataAsMissing();
1244
+ this._markDataAsMissing('<module-import>');
1170
1245
  }
1171
1246
  return;
1172
1247
  }
@@ -1398,7 +1473,7 @@ class RelayReader {
1398
1473
  // fetched the `__is[AbstractType]` flag for this concrete type. In this
1399
1474
  // case we need to report that we are missing data, in case that field is
1400
1475
  // still in flight.
1401
- this._markDataAsMissing();
1476
+ this._markDataAsMissing('<abstract-type-hint>');
1402
1477
  }
1403
1478
  // $FlowFixMe Casting record value
1404
1479
  return implementsInterface;
@@ -1274,7 +1274,7 @@ export type MissingFieldHandler =
1274
1274
  export type MissingExpectedDataLogEvent = {
1275
1275
  +kind: 'missing_expected_data.log',
1276
1276
  +owner: string,
1277
- +fieldPath: string,
1277
+ fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1278
1278
  };
1279
1279
 
1280
1280
  /**
@@ -1300,7 +1300,7 @@ export type MissingExpectedDataLogEvent = {
1300
1300
  export type MissingExpectedDataThrowEvent = {
1301
1301
  +kind: 'missing_expected_data.throw',
1302
1302
  +owner: string,
1303
- +fieldPath: string,
1303
+ fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1304
1304
  +handled: boolean,
1305
1305
  };
1306
1306