-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Add a dataState
enum to ApolloQueryResult
#12350
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release-4.0
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 219d890 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
commit: |
* - `complete`: `data` is a fully satisfied query result fulfilled | ||
* either from the cache or network. | ||
*/ | ||
dataState: "complete"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let the bike shedding commence!
✅ Deploy Preview for apollo-client-docs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
size-limit report 📦
|
src/core/types.ts
Outdated
| { | ||
// Defer to the passed in type to properly type the `@defer` fields. | ||
data: T; | ||
dataState: "hasNext"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not super in love with this name, but its been difficult to come up with something that doesn't sound the same as partial
. The difference here is that partial
is meant to describe a partial cache hit where some fields are missing, where hasNext
is a @defer
query that hasn't fully streamed in the full data set.
Other options considered:
streaming
pending
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe deferring
? otherwise streaming
would work, but it's slightly harder to make the mental connection to @defer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm ya this one is tough. Eventually this will need to work with @stream
so I suppose streaming
fits with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
waitingForChunks
maybe? Really not sure. streaming
seems better than deferring
to me, pending
is often just used synonymous to loading
, so I'd avoid that.
} | ||
| { | ||
// Defer to the passed in type to properly type the `@defer` fields. | ||
data: T; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the case of hasNext
, I've left the T
type alone. GraphQL Codegen will generate a type which includes a union on all deferred fields that handles the "missing" holes in the type.
The biggest downside to this is that the complete
case also returns the type as-is which means the user will still have to check the deferred fields to ensure they are there to satisfy TypeScript. Ideally we could unwrap that type a bit, but this may prove to be challenging.
I welcome any thoughts here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's wait how this shapes out on the codegen side. This seems fine for now.
// Even though `data` didn't change, the `dataStatus` is updated to reflect | ||
// that the full result has been stremed in so we expect another render | ||
// value. | ||
await expect(stream).toEmitApolloQueryResult({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we are reporting the dataStatus
enum with a proper value, I think it is necessary to emit another value that reports the dataStatus
as complete
, even though the data
property itself didn't change since the deferred fragment was masked.
d04f0bf
to
e05d975
Compare
@@ -734,9 +736,9 @@ export function toQueryResult<TData, TVariables extends OperationVariables>( | |||
observable: ObservableQuery<TData, TVariables>, | |||
client: ApolloClient<object> | |||
): InternalQueryResult<TData, TVariables> { | |||
const { data, partial, ...resultWithoutPartial } = result; | |||
const { data, partial, dataState, ...resultWithoutPartial } = result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will expose this property in a separate PR so we can add support for it to all hooks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally this looks good, but I'm a bit concerned that we have some code paths that will end up with a hasNext
while others will end up with a partial
for the same situation, depending if things come from a direct network request or from the cache while a network request is pending nonetheless.
It might even be a good idea to omit the hasNext
case until we can guarantee consistency there, what are your thoughts?
src/core/types.ts
Outdated
/** | ||
* Describes the completeness of `data`. | ||
* - `none`: No data could be fulfilled from the cache or the result is | ||
* incomplete. `data` is `undefined`. | ||
* - `partial`: Some data could be fulfilled from the cache but `data` is | ||
* incomplete. This is only possible when `returnPartialData` is `true`. | ||
* - `hasNext`: `data` is incomplete as a result of a deferred query and | ||
* the result is still streaming in. | ||
* - `complete`: `data` is a fully satisfied query result fulfilled | ||
* either from the cache or network. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use @inheritDoc
for the docblock so we have it on all four variants :)
} | ||
| { | ||
// Defer to the passed in type to properly type the `@defer` fields. | ||
data: T; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's wait how this shapes out on the codegen side. This seems fine for now.
src/core/types.ts
Outdated
| { | ||
// Defer to the passed in type to properly type the `@defer` fields. | ||
data: T; | ||
dataState: "hasNext"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
waitingForChunks
maybe? Really not sure. streaming
seems better than deferring
to me, pending
is often just used synonymous to loading
, so I'd avoid that.
@@ -823,15 +823,16 @@ export class QueryManager<TStore> { | |||
return this.fetchQuery<TData, TVars>(queryId, { ...options, query }) | |||
.then( | |||
(result) => | |||
result && { | |||
result && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this truthyness test still necessary?
data: data as TData | undefined, | ||
const result = { | ||
data, | ||
dataState: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means that streaming
(or whatever we'll call it) will be kinda non-deterministic, depending on if this ObservableQuery triggered the query or if it's currently being read & updated from the cache.
Should we add a streaming
status at all given that context or wait until we can get this 100% accurate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is part of the reason I wanted to add a source
property at some point because I think this becomes more obvious what that state. streaming
will only ever be emitted from the link observable and that code path doesn't go through fromData
/resultsFromCache
. This observable value is always the first one emitted, so the link observable will ensure that the proper dataState
is set from that point forward.
That said, perhaps I'm misunderstand what you're referring to as non-deterministic?
e05d975
to
f4d6f53
Compare
I got this rebased, though I think I want to think this through a bit more before we merge it. I think there is more opportunity for optimization here. For example, |
Closes #12344
Closes #12345
Adds a new
dataState
enum returned fromApolloQueryResult
that describes the state of thedata
property.ApolloQueryResult
is now a discriminated union that better describes the value ofdata
when in a particular state which provides additional type safety.This new enum is now able to track whether the is waiting for additional chunks when used with
@defer
which can be useful to determine whether the full result has streamed in or not.