relay-runtime 0.0.0-main-d112a76d → 0.0.0-main-e186ff01

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Relay v0.0.0-main-d112a76d
2
+ * Relay v0.0.0-main-e186ff01
3
3
  *
4
4
  * Copyright (c) Meta Platforms, Inc. and affiliates.
5
5
  *
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Relay v0.0.0-main-d112a76d
2
+ * Relay v0.0.0-main-e186ff01
3
3
  *
4
4
  * Copyright (c) Meta Platforms, Inc. and affiliates.
5
5
  *
@@ -186,7 +186,7 @@ var RelayReader = /*#__PURE__*/function () {
186
186
  !this._variables.hasOwnProperty(name) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayReader(): Undefined variable `%s`.', name) : invariant(false) : void 0;
187
187
  return this._variables[name];
188
188
  };
189
- _proto._maybeReportUnexpectedNull = function _maybeReportUnexpectedNull(selection) {
189
+ _proto._maybeReportUnexpectedNull = function _maybeReportUnexpectedNull(selection, fieldValue) {
190
190
  if (selection.action === 'NONE') {
191
191
  return;
192
192
  }
@@ -202,10 +202,19 @@ var RelayReader = /*#__PURE__*/function () {
202
202
  var _selection$field$alia;
203
203
  fieldName = (_selection$field$alia = selection.field.alias) !== null && _selection$field$alia !== void 0 ? _selection$field$alia : selection.field.name;
204
204
  }
205
+ var fieldError = null;
206
+ if (fieldValue === null && this._fieldErrors.length > 0) {
207
+ var lastError = this._fieldErrors[this._fieldErrors.length - 1];
208
+ if (lastError.kind === 'relay_field_payload.error') {
209
+ fieldError = lastError.error;
210
+ }
211
+ }
205
212
  switch (selection.action) {
206
213
  case 'THROW':
207
214
  this._fieldErrors.push({
215
+ fieldError: fieldError,
208
216
  fieldPath: fieldName,
217
+ fieldValue: fieldValue,
209
218
  handled: false,
210
219
  kind: 'missing_required_field.throw',
211
220
  owner: owner,
@@ -214,7 +223,9 @@ var RelayReader = /*#__PURE__*/function () {
214
223
  return;
215
224
  case 'LOG':
216
225
  this._fieldErrors.push({
226
+ fieldError: fieldError,
217
227
  fieldPath: fieldName,
228
+ fieldValue: fieldValue,
218
229
  kind: 'missing_required_field.log',
219
230
  owner: owner,
220
231
  uiContext: undefined
@@ -226,7 +237,7 @@ var RelayReader = /*#__PURE__*/function () {
226
237
  };
227
238
  _proto._handleRequiredFieldValue = function _handleRequiredFieldValue(selection, value) {
228
239
  if (value == null) {
229
- this._maybeReportUnexpectedNull(selection);
240
+ this._maybeReportUnexpectedNull(selection, value);
230
241
  return false;
231
242
  }
232
243
  return true;
@@ -281,9 +292,17 @@ var RelayReader = /*#__PURE__*/function () {
281
292
  message: "Relay: Error in resolver for field at ".concat(error.fieldPath, " in ").concat(error.owner)
282
293
  };
283
294
  case 'missing_required_field.throw':
284
- return {
285
- message: "Relay: Missing @required value at path '".concat(error.fieldPath, "' in '").concat(error.owner, "'.")
286
- };
295
+ {
296
+ var reason;
297
+ if (error.fieldValue === null) {
298
+ reason = error.fieldError != null ? "the server returned null with an error: ".concat(error.fieldError.message) : 'the server returned null';
299
+ } else {
300
+ reason = 'the field was missing in the store (data may not have been fetched, or was removed by a graph relationship change: https://relay.dev/docs/next/debugging/why-null/#graph-relationship-change)';
301
+ }
302
+ return {
303
+ message: "Relay: Missing @required value at path '".concat(error.fieldPath, "' in '").concat(error.owner, "': ").concat(reason, ".")
304
+ };
305
+ }
287
306
  case 'missing_required_field.log':
288
307
  return null;
289
308
  default:
@@ -29,11 +29,19 @@ function handleFieldErrors(environment, fieldErrors, loggingContext) {
29
29
  case 'relay_resolver.error':
30
30
  throw new Error("Relay: Resolver error at path '".concat(_fieldError.fieldPath, "' in '").concat(_fieldError.owner, "'. Message: ").concat(_fieldError.error.message));
31
31
  case 'relay_field_payload.error':
32
- throw new Error("Relay: Unexpected response payload - check server logs for details.");
32
+ throw new Error("Relay: Received a field error in the server response for field '".concat(_fieldError.fieldPath, "' in '").concat(_fieldError.owner, "'. Message: ").concat(_fieldError.error.message));
33
33
  case 'missing_expected_data.throw':
34
- throw new Error("Relay: Missing expected data at path '".concat(_fieldError.fieldPath, "' in '").concat(_fieldError.owner, "'."));
34
+ throw new Error("Relay: Missing expected data at path '".concat(_fieldError.fieldPath, "' in '").concat(_fieldError.owner, "'. See https://relay.dev/docs/next/debugging/why-null/ for likely causes."));
35
35
  case 'missing_required_field.throw':
36
- throw new Error("Relay: Missing @required value at path '".concat(_fieldError.fieldPath, "' in '").concat(_fieldError.owner, "'."));
36
+ {
37
+ var reason = void 0;
38
+ if (_fieldError.fieldValue === null) {
39
+ reason = _fieldError.fieldError != null ? "the server returned null with an error: ".concat(_fieldError.fieldError.message) : 'the server returned null';
40
+ } else {
41
+ reason = 'the field was missing in the store (data may not have been fetched, or was removed by a graph relationship change: https://relay.dev/docs/next/debugging/why-null/#graph-relationship-change)';
42
+ }
43
+ throw new Error("Relay: Missing @required value at path '".concat(_fieldError.fieldPath, "' in '").concat(_fieldError.owner, "': ").concat(reason, "."));
44
+ }
37
45
  case 'missing_required_field.log':
38
46
  case 'missing_expected_data.log':
39
47
  break;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "relay-runtime",
3
3
  "description": "A core runtime for building GraphQL-driven applications.",
4
- "version": "0.0.0-main-d112a76d",
4
+ "version": "0.0.0-main-e186ff01",
5
5
  "keywords": [
6
6
  "graphql",
7
7
  "relay"
@@ -353,7 +353,10 @@ class RelayReader {
353
353
  return this._variables[name];
354
354
  }
355
355
 
356
- _maybeReportUnexpectedNull(selection: ReaderRequiredField) {
356
+ _maybeReportUnexpectedNull(
357
+ selection: ReaderRequiredField,
358
+ fieldValue: null | void,
359
+ ) {
357
360
  if (selection.action === 'NONE') {
358
361
  return;
359
362
  }
@@ -371,10 +374,24 @@ class RelayReader {
371
374
  fieldName = selection.field.alias ?? selection.field.name;
372
375
  }
373
376
 
377
+ // When the field was null (not missing from the store), check whether the
378
+ // server also attached a field error to it. _maybeAddFieldErrors runs in
379
+ // the same call stack just before this method, so any companion error for
380
+ // this field is always the last entry pushed to this._fieldErrors.
381
+ let fieldError = null;
382
+ if (fieldValue === null && this._fieldErrors.length > 0) {
383
+ const lastError = this._fieldErrors[this._fieldErrors.length - 1];
384
+ if (lastError.kind === 'relay_field_payload.error') {
385
+ fieldError = lastError.error;
386
+ }
387
+ }
388
+
374
389
  switch (selection.action) {
375
390
  case 'THROW':
376
391
  this._fieldErrors.push({
392
+ fieldError,
377
393
  fieldPath: fieldName,
394
+ fieldValue,
378
395
  handled: false,
379
396
  kind: 'missing_required_field.throw',
380
397
  owner,
@@ -385,7 +402,9 @@ class RelayReader {
385
402
  return;
386
403
  case 'LOG':
387
404
  this._fieldErrors.push({
405
+ fieldError,
388
406
  fieldPath: fieldName,
407
+ fieldValue,
389
408
  kind: 'missing_required_field.log',
390
409
  owner,
391
410
  // the uiContext is always undefined here.
@@ -403,7 +422,7 @@ class RelayReader {
403
422
  value: unknown,
404
423
  ): boolean /*should continue to siblings*/ {
405
424
  if (value == null) {
406
- this._maybeReportUnexpectedNull(selection);
425
+ this._maybeReportUnexpectedNull(selection, value);
407
426
  // We are going to throw, or our parent is going to get nulled out.
408
427
  // Either way, sibling values are going to be ignored, so we can
409
428
  // bail early here as an optimization.
@@ -499,12 +518,23 @@ class RelayReader {
499
518
  return {
500
519
  message: `Relay: Error in resolver for field at ${error.fieldPath} in ${error.owner}`,
501
520
  };
502
- case 'missing_required_field.throw':
521
+ case 'missing_required_field.throw': {
503
522
  // If we have a nested @required(THROW) that will throw,
504
523
  // we want to catch that error and provide it
524
+ let reason: string;
525
+ if (error.fieldValue === null) {
526
+ reason =
527
+ error.fieldError != null
528
+ ? `the server returned null with an error: ${error.fieldError.message}`
529
+ : 'the server returned null';
530
+ } else {
531
+ reason =
532
+ 'the field was missing in the store (data may not have been fetched, or was removed by a graph relationship change: https://relay.dev/docs/next/debugging/why-null/#graph-relationship-change)';
533
+ }
505
534
  return {
506
- message: `Relay: Missing @required value at path '${error.fieldPath}' in '${error.owner}'.`,
535
+ message: `Relay: Missing @required value at path '${error.fieldPath}' in '${error.owner}': ${reason}.`,
507
536
  };
537
+ }
508
538
  case 'missing_required_field.log':
509
539
  // For backwards compatibility, we don't surface log level missing required fields
510
540
  return null;
@@ -1411,6 +1411,22 @@ export type MissingRequiredFieldLogEvent = {
1411
1411
  readonly kind: 'missing_required_field.log',
1412
1412
  readonly owner: string,
1413
1413
  fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1414
+ /**
1415
+ * The value that was read for the @required field:
1416
+ * - `null`: the server returned null for this field. If `fieldError` is also
1417
+ * set, the server attached an error explaining why.
1418
+ * - `undefined` (void): the field was absent from the Relay store entirely.
1419
+ * This is commonly caused by a graph relationship change observed by another
1420
+ * query/mutation, or by an imperative store update that omitted this field.
1421
+ * See https://relay.dev/docs/next/debugging/why-null/#graph-relationship-change
1422
+ */
1423
+ readonly fieldValue: null | void,
1424
+ /**
1425
+ * When `fieldValue` is `null` and the server attached a field error to this
1426
+ * field in the GraphQL response, this contains that error. This is the most
1427
+ * likely explanation for why the field was null.
1428
+ */
1429
+ readonly fieldError: ?TRelayFieldError,
1414
1430
  // To populate this, you should pass the value to a ReactRelayLoggingContext
1415
1431
  readonly uiContext: unknown | void,
1416
1432
  };
@@ -1431,6 +1447,22 @@ export type MissingRequiredFieldThrowEvent = {
1431
1447
  readonly owner: string,
1432
1448
  fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1433
1449
  readonly handled: boolean,
1450
+ /**
1451
+ * The value that was read for the @required field:
1452
+ * - `null`: the server returned null for this field. If `fieldError` is also
1453
+ * set, the server attached an error explaining why.
1454
+ * - `undefined` (void): the field was absent from the Relay store entirely.
1455
+ * This is commonly caused by a graph relationship change observed by another
1456
+ * query/mutation, or by an imperative store update that omitted this field.
1457
+ * See https://relay.dev/docs/next/debugging/why-null/#graph-relationship-change
1458
+ */
1459
+ readonly fieldValue: null | void,
1460
+ /**
1461
+ * When `fieldValue` is `null` and the server attached a field error to this
1462
+ * field in the GraphQL response, this contains that error. This is the most
1463
+ * likely explanation for why the field was null.
1464
+ */
1465
+ readonly fieldError: ?TRelayFieldError,
1434
1466
  // To populate this, you should pass the value to a ReactRelayLoggingContext
1435
1467
  readonly uiContext: unknown | void,
1436
1468
  };
@@ -50,16 +50,27 @@ function handleFieldErrors(
50
50
  );
51
51
  case 'relay_field_payload.error':
52
52
  throw new Error(
53
- `Relay: Unexpected response payload - check server logs for details.`,
53
+ `Relay: Received a field error in the server response for field '${fieldError.fieldPath}' in '${fieldError.owner}'. Message: ${fieldError.error.message}`,
54
54
  );
55
55
  case 'missing_expected_data.throw':
56
56
  throw new Error(
57
- `Relay: Missing expected data at path '${fieldError.fieldPath}' in '${fieldError.owner}'.`,
57
+ `Relay: Missing expected data at path '${fieldError.fieldPath}' in '${fieldError.owner}'. See https://relay.dev/docs/next/debugging/why-null/ for likely causes.`,
58
58
  );
59
- case 'missing_required_field.throw':
59
+ case 'missing_required_field.throw': {
60
+ let reason: string;
61
+ if (fieldError.fieldValue === null) {
62
+ reason =
63
+ fieldError.fieldError != null
64
+ ? `the server returned null with an error: ${fieldError.fieldError.message}`
65
+ : 'the server returned null';
66
+ } else {
67
+ reason =
68
+ 'the field was missing in the store (data may not have been fetched, or was removed by a graph relationship change: https://relay.dev/docs/next/debugging/why-null/#graph-relationship-change)';
69
+ }
60
70
  throw new Error(
61
- `Relay: Missing @required value at path '${fieldError.fieldPath}' in '${fieldError.owner}'.`,
71
+ `Relay: Missing @required value at path '${fieldError.fieldPath}' in '${fieldError.owner}': ${reason}.`,
62
72
  );
73
+ }
63
74
  case 'missing_required_field.log':
64
75
  case 'missing_expected_data.log':
65
76
  // These should have already been filtered out. Sadly, Flow Type