Skip to content

Conversation

jerelmiller
Copy link
Member

Fixes #12660
Fixes #11704

Fixes issues where arrays returned in @defer payloads would maintain cached items when the defer array was shorter than the cached array. This change also affects @stream arrays so that when a @stream chunk is processed, it truncates the array to the length of the stream payload. This ensures the length of the final result equals the length of the server array, much like it would if a plain refetch were issued.

Copy link

changeset-bot bot commented Sep 15, 2025

🦋 Changeset detected

Latest commit: c496039

The changes in this PR will be included in the next version bump.

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

@apollo-librarian
Copy link

apollo-librarian bot commented Sep 15, 2025

✅ Docs preview has no changes

The preview was not built because there were no changes.

Build ID: 65998f1ed1f52cf86e6a07cb
Build Logs: View logs

Copy link

pkg-pr-new bot commented Sep 15, 2025

npm i https://pkg.pr.new/apollographql/apollo-client/@apollo/client@12923

commit: c496039

@@ -0,0 +1,5 @@
---
"@apollo/client": minor
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm marking this as a minor change. While it does fix the array merging behavior, it's a big enough difference to warrant a minor.

expect(outgoingRequestSpy).toHaveBeenCalledTimes(2);
});

it.each([["cache-first"], ["no-cache"]] as const)(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These were added from #11374 (with some tweaks)

expect(outgoingRequestSpy).toHaveBeenCalledTimes(2);
});

it.each([["cache-first"], ["no-cache"]] as const)(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added from #11374 (with some tweaks)

Query: {
fields: {
friendList: {
merge: (existing = [], incoming, { field }) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider exposing a utility function or helper of some kind to do this kind of thing? At the very least, we probably want to put something in our docs along with @stream that describes how to do this in your own code. This demonstrates that you can maintain the original cached items when using stream instead of default overwriting the array.

friendList: [
{ name: "Luke", id: "1" },
{ name: "Han", id: "2" },
{ name: "Leia Cached", id: "3" },
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior will now rely on a merge function to keep the cached item if the user wishes.

Query: {
fields: {
friendList: {
merge: (_, incoming) => incoming,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this here to silence the warning about data loss, but I think this might happen a lot with @stream since fetches against a @stream field will naturally return a shorter list than already-cached lists since the list is streamed in over time.

Should we consider muting the warning for @stream fields like we do for @defer? Or should we recommend adding these type policies to explicitly opt into the @stream behavior?

export class DeepMerger<TContextArgs extends any[] = any[]> {
constructor(
private reconciler: ReconcilerFunction<TContextArgs> = defaultReconciler as any as ReconcilerFunction<TContextArgs>
private reconciler: ReconcilerFunction<TContextArgs> = defaultReconciler as any as ReconcilerFunction<TContextArgs>,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is an internal class, I know we are free to update the API so with that in mind... should reconciler be moved to options instead? Its a bit awkward in places where we need arrayMerge but not reconciler since you have to pass undefined as the first argument.

Thoughts on changing it so we can do:

new DeepMerger({ arrayMerge: 'truncate' });

?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant