question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

FieldPolicy read() not always invoked before broadcasting a query (3.3.7)

See original GitHub issue

Intended outcome:

We define a read() FieldPolicy function that looks directly into the store to determine all records that meet the query. We expect this read() function to be invoked with the current state of the store (unless the store has not changed) before each notification to a subscriber of the query, i.e. at broadcastQueries time. Most of the time this is the case, but when a mutation response updates the store (in a link.complete() function), and then calls broadcastQueries, the read() FieldPolicy for the query is not invoked, resulting in a notification that does not include the mutation response.

Actual outcome:

The problem appears to be in two places in the code:

  1. QueryInfo.getDiff does not call this.cache.diff unless the variables have changed (the variables have not changed), I have patched it to avoid that check.
   QueryInfo.prototype.getDiff = function (variables) {
        if (variables === void 0) { variables = this.variables; }
 >>>       if (false && this.diff && equal(variables, this.variables)) {
            return this.diff;
        }
        this.updateWatch(this.variables = variables);
        return this.diff = this.cache.diff({
            query: this.document,
            variables: variables,
            returnPartialData: true,
            optimistic: true,
        });
    };
 

Once this check is removed, the second problem is hit:

  1. In StoreReader, the executeSelectionSet is cached in a way that won’t run in this condition. I don’t fully understand the caching here, but when I make this change, I get the expected result:
            makeCacheKey: function (selectionSet, parent, context) {
>>>                if (false && supportsResultCaching(context.store)) {
                    return context.store.makeCacheKey(selectionSet, isReference(parent) ? parent.__ref : parent, context.varString);
                }
            }

Obviously my patches would cause tragic performance degradations in some cases, so something better must be come up with.

How to reproduce the issue:

I’m sorry that I don’t have the time to create a separate test case for this. Hopefully my references to the source code make the issue clear.

Versions

@apollo/client v3.3.7

System: OS: macOS 11.0.1 Binaries: Node: 12.16.2 - ~/.nvm/versions/node/v12.16.2/bin/node Yarn: 2.4.0 - ~/.nvm/versions/node/v12.16.2/bin/yarn npm: 6.14.4 - ~/.nvm/versions/node/v12.16.2/bin/npm Browsers: Chrome: 88.0.4324.96 Firefox: 84.0.2 Safari: 14.0.1

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
francisucommented, Feb 17, 2021

@benjamn I think you have hit on the issue. I’m directly reading the store (in fact I have patched the call to the read() function to include the EntityStore as an argument.

Our use of the store is that each record is identified with a record type, and then id, like “Person:1”. In order to resolve a query, on “Person”, I look directly into the EntityStore (data) to find all of the records that start with “Person”. And then the code directly access the fields in the record (it does not use readFragment, nor readField and it should).

The code needs to look at every record in the store, as records could have been added from a subscription event which is related to the record type, but not to any particular query. And as far as I know, there is no function that allows the store to be directly read by record type. readQuery is close, but those records are associated with the given query.

I think the correct implementation then is to create a main query for each record type, which contains all of the known records in the store (for that record type). For subscription events, update that record type query with any new records. And then, in the read() function, use readQuery when against the record type query for resolving any other queries related to that record type.

I’m going to close this. If this problem happens still, when I’m using the APIs, then I will be back. Thanks so much for your help!

1reaction
francisucommented, Feb 17, 2021

@benjamn I don’t think this is my problem. The store is getting updated after the mutation returns, but the read() function is not getting invoked in the broadcastQueries phase, and so the query results in the cache are not updated, and the query subscription is not triggered. I don’t think the storeResult in QueryManager.mutate plays a part in this.

In point one of my comments, I think the optimization to not do a diff if the variables have not changed it incorrect, because the store may have changed, and so the read() function for the query must be invoked. As I understand it, the read() function will always be invoked to calculate the response to the query if there is any possibility of a change to the store (which is what we are counting on).

Essentially our use case is that our queries are automatically refreshed from the store (to our application callback) whenever anything in the cache has changed (due to either a mutation or the receipt of a subscription message). We don’t want our application developer to have to specify which queries are to be updated upon a mutation, we are using the cache notification stuff to figure this out. And thus we use the read() function to fully process the query from the store (it does the necessary argument filtering). I can see this sort of feature implemented in the Apollo Cache as another level in making it easier to program against.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Mutation results don't include FieldPolicy changes #7665
I'm using a FieldPolicy to translate a custom Date scalar from strings ... FieldPolicy read() not always invoked before broadcasting a query ......
Read more >
Customizing the behavior of cached fields - Apollo GraphQL
This field policy defines a read function that specifies what the cache returns whenever Person.name is queried. The read function. If you define...
Read more >
@apollo/client | Yarn - Package Manager
Use Apollo's gateway to compose a unified graph from multiple subgraphs, determine a query plan, and route requests across your services. Apollo Client...
Read more >
apollo-client - Awesome JS
Fix issue where loading remains true after observer.refetch is called repeatedly with different variables when the same data are returned. @alessbell in #10143....
Read more >
Fine Tuning Apollo Client Caching for Your Data Graph (Ben ...
Learn how to customize the Apollo Client cache to suit your weirdest data modeling and reactivity needs, using the all -new cache API...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found