You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix partial/complete data values for incremental responses, especially when combined with returnPartialData (#13324)
Apollo Client 4.0-4.2.x is fairly naive in the way `dataState` is
reported for incremental responses. Those versions set `dataState` as
`streaming` if the request is still in-flight, regardless of whether the
data actually satisfies the definition of `streaming`. `streaming` is
meant to represent a state where the only holes in `data` are at
`@defer` boundaries that aren't streamed in. Prior to this change, this
could lead to runtime crashes due to the inaccuracy (see the changeset
for an example).
The other issue is that field `read` functions that modified values on
individual fields were not applied correctly in intermediate responses
returned by the cache. The `read` functions were run correctly, but due
to the completeness checks in `QueryInfo`, the values were never
applied.
`data` returned in incremental chunks are now properly reported as
`partial`, or in some cases `complete` depending on whether it fulfills
the requirements of the query and/or `@defer` boundaries. This change
also fixes the issue where field `read` function return values were not
applied to intermediate chunks.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved `dataState` reporting for incremental `@defer` and `@stream`
responses.
* Results now correctly distinguish between `partial`, `streaming`, and
`complete` based on available cached data.
* Network status continues to indicate when incremental delivery is
still in progress.
* Improved handling of partial cached results, refetches, errors, and
cache merges.
* **New Features**
* Added internal utilities for analyzing GraphQL fields and deferred
fragments.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: jerelmiller <565661+jerelmiller@users.noreply.github.com>
@@ -85,6 +86,21 @@ export type ClassicSignature = SignatureStyle extends "classic" ? unknown : neve
85
86
//@internal @deprecated
86
87
exportfunction cloneDeep<T>(value:T):T;
87
88
89
+
// @public (undocumented)
90
+
interfaceCollectionContext {
91
+
// (undocumented)
92
+
exclude:SelectionNode;
93
+
// Warning: (ae-incompatible-release-tags) The symbol "fragmentMap" is marked as @public, but its signature references "FragmentMap" which is marked as @internal
94
+
//
95
+
// (undocumented)
96
+
fragmentMap:FragmentMap;
97
+
}
98
+
99
+
// Warning: (ae-forgotten-export) The symbol "CollectionContext" needs to be exported by the entry point index.d.ts
Fix the accuracy of `dataState` in complex incremental streaming scenarios, especially when combined with `returnPartialData: true`.
6
+
7
+
Prior to this change, all intermediate chunks used for both `@defer` and `@stream` directives returned a `dataState` of `streaming`, regardless of whether the actual data shape fit the definition of the `streaming` data state. The `streaming` data state represents an incomplete incremental response where the only holes in the data occur at `@defer` boundaries.
8
+
9
+
Let's use the following example of where the previous `dataState` fell down when combined with `returnPartialData`.
10
+
11
+
```gql
12
+
queryGreetingQuery {
13
+
greeting {
14
+
message
15
+
...@defer {
16
+
recipient {
17
+
name
18
+
email
19
+
}
20
+
}
21
+
}
22
+
}
23
+
```
24
+
25
+
1. Scenario 1: partial data inside a `@defer` boundary written to the cache
26
+
27
+
Let's say the cache contained the following partial data:
28
+
29
+
```ts
30
+
{
31
+
greeting: {
32
+
__typename: "Greeting",
33
+
recipient: {
34
+
__typename: "Person",
35
+
name: "John Doe",
36
+
},
37
+
},
38
+
};
39
+
```
40
+
41
+
After the first chunk arrives from the server, the data looks like the following:
42
+
43
+
```ts
44
+
{
45
+
greeting: {
46
+
__typename: "Greeting",
47
+
message: "Hello, John",
48
+
recipient: {
49
+
__typename: "Person",
50
+
name: "John Doe",
51
+
},
52
+
},
53
+
};
54
+
```
55
+
56
+
This data is not `complete` because `recipient.email` is missing. This data is also not `streaming` because the data requirements in the `@defer` boundary are partially fulfilled due to the existence of `recipient`. This could lead to runtime crashes on `recipient.email` if you use the existence of `recipient` to detect whether data in the `@defer` boundary has streamed in or not. This change now accurately reports this as `partial` to ensure the field is marked as a partial field in `recipient`.
57
+
58
+
2. Scenario 2: partial data written to the cache that fulfills the data requirements of the `@defer` boundary
59
+
60
+
Let's say the cache contained the following partial data:
61
+
62
+
```ts
63
+
{
64
+
greeting: {
65
+
__typename: "Greeting",
66
+
recipient: {
67
+
__typename: "Person",
68
+
name: "John Doe",
69
+
email: "john@example.com",
70
+
},
71
+
},
72
+
};
73
+
```
74
+
75
+
After the first chunk arrives from the server, the data looks like the following:
76
+
77
+
```ts
78
+
{
79
+
greeting: {
80
+
__typename: "Greeting",
81
+
message: "Hello, John",
82
+
recipient: {
83
+
__typename: "Person",
84
+
name: "John Doe",
85
+
email: "john@example.com",
86
+
},
87
+
},
88
+
};
89
+
```
90
+
91
+
In this case, the combination of the first chunk and the partial data in the cache now fulfills the data requirements of the query. Even though the server is still streaming data (`NetworkStatus.streaming`), we can report this as `dataState: "complete"` since it is safe to access data on all fields.
92
+
93
+
This change also means `@stream` queries by definition fulfill the data requirements of the query after the first chunk arrives since `@stream` operates on lists and contains no data holes. `@stream` queries now accurately report `dataState` as `complete` or `partial`, depending on whether the list mixes partial data with streamed list items.
94
+
95
+
As a result of this change, some cases where you'd previously see `dataState` reported as `"streaming"` are now reported as `partial` or `complete`.
96
+
97
+
If you use `dataState` to determine whether an incremental request is still in-flight, please use `networkStatus` instead to check for `NetworkStatus.streaming`. `dataState` is type narrowing feature and not intended to report the network status.
Fix an issue where field `read` functions were not applied to intermediate results while streaming `@defer` responses. `cache.diff` ran the `read` functions, but the transformed values were only applied to the emitted result when the updated cache result was considered complete. Intermediate chunks whose only holes were at `@defer` boundaries now correctly return the result of field `read` functions.
Fix an issue with `@stream` queries when using `returnPartialData: true` where the streamed list was truncated after the first incremental chunk when the list contained partial cache data. The list is no longer truncated and partial list items are now retained as incremental chunks arrive. The `dataState` is now reported as `partial` until the server has streamed enough of the list so that each list item fully satisfies the query.
6
+
7
+
This change also updates `@stream` queries so that they reported with `dataState: "complete` instead of `"streaming"` since it is safe to access all fields in the response.
0 commit comments