Skip to content
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

keyFields mismatch with query leads to hard Invariant Violation crash #6673

Open
berekuk opened this issue Jul 22, 2020 · 23 comments
Open

keyFields mismatch with query leads to hard Invariant Violation crash #6673

berekuk opened this issue Jul 22, 2020 · 23 comments

Comments

@berekuk
Copy link

berekuk commented Jul 22, 2020

Intended outcome:

Specifying unknown fields in keyFields (or failing to ask for all keyFields in queries) should result in a catchable exception which doesn't affect the entire server process.

Actual outcome:

When I pass an unknown field name to keyFields in InMemoryCache configuration, the entire node process crashes down with Invariant Violation error.

Here's the stacktrace I'm getting with the repo below:

/Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/zen-observable/lib/Observable.js:65
      throw e;
      ^

Invariant Violation: Missing field 'foo' while computing key fields
    at new InvariantError (/Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/ts-invariant/lib/invariant.js:16:28)
    at Object.invariant (/Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/ts-invariant/lib/invariant.js:28:15)
    at /Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:3788:130
    at Array.forEach (<anonymous>)
    at computeKeyObject (/Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:3777:15)
    at /Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:3740:13
    at Policies.identify (/Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:3454:33)
    at StoreWriter.processSelectionSet (/Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:4448:27)
    at StoreWriter.processFieldValue (/Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:4536:21)
    at /Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:4485:47
    at Set.forEach (<anonymous>)
    at StoreWriter.processSelectionSet (/Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:4471:17)
    at StoreWriter.writeToStore (/Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:4420:29)
    at InMemoryCache.write (/Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:4659:37)
    at InMemoryCache.ApolloCache.writeQuery (/Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:3321:21)
    at /Users/berekuk/coding/tmp/apollo-crash/apollo-crash/node_modules/@apollo/client/apollo-client.cjs.js:2359:31

My attempts to pin down this problem lead me to setTimeout call in zen-observable's hostReportError and to invariant(...) call in computeKeyObject in apollo-client.

How to reproduce the issue:

Here's my repo which reproduces the problem: https://github.com/berekuk/bug-apollo-keyFields-crash

It uses Next.js since that's how I discovered this issue and since I didn't want to implement a custom Express.js server just for this bugreport.

I also tried to reproduce it with a simple node script which used apolloClient.watchQuery but didn't succeed. useQuery probably does something differently, but I'm not sure what exactly.

Versions

See https://github.com/berekuk/bug-apollo-keyFields-crash/blob/master/package.json:

    "@apollo/client": "^3.0.2",
    "graphql": "^15.3.0",

PS: Besides this specific issue, I'd very much prefer for apollo-client never to do something like this on non-fatal errors. I'm worried, though, because it seems like apollo-client calls invariant() quite often, and though I'm not familiar with ts-invariant and zen-observable APIs, it probably behaves like a hard non-easily-catchable assertion in all those cases (?).

@berekuk berekuk changed the title Invalid keyFields lead to hard Invariant Violation crash keyFields mismatch with query lead to hard Invariant Violation crash Jul 22, 2020
@berekuk berekuk changed the title keyFields mismatch with query lead to hard Invariant Violation crash keyFields mismatch with query leads to hard Invariant Violation crash Jul 22, 2020
@benjamn
Copy link
Member

benjamn commented Jul 22, 2020

I would challenge your assumption that this kind of mistake should be "non-fatal." If an ID can't be computed for some instances of an object type, those deficient objects will effectively not be compatible with the rest of the objects of that type in your data graph. That seems like a pretty serious problem that you would want to catch (ideally in development) and fix!

I agree that it should be possible to catch and handle this kind of error at runtime (and I would have thought that was how it worked—so there's definitely a bug if that's not the case), but it shouldn't be something you can easily ignore.

@berekuk
Copy link
Author

berekuk commented Jul 22, 2020

I'm completely ok with this mistake causing an exception which I (or my framework, e.g. Next.js) would have to catch explicitly.

I'm definitely NOT ok with this error bringing my entire production website down, as it happened today :(
One page/query which I wrote incorrectly or forgot about during a refactoring or apollo-client upgrade (it worked fine in 2.6) shouldn't have this effect.

Actually, I believe no kind of code except for syntax errors should have this effect, and I'm really strugging to understand why it's acceptable to intentionally write setTimeout(() => { throw new Error() }) in any npm library.

@benjamn benjamn added this to the Post 3.0 milestone Jul 22, 2020
@benjamn
Copy link
Member

benjamn commented Jul 22, 2020

Ok, I think we agree on all of that. We will dig into this and make sure that we're providing appropriate observer.error handlers for all of our Observables, so that this behavior is never triggered.

@vfonic
Copy link

vfonic commented Sep 8, 2020

I'm just burning through my second day of upgrading from apollo client v2 to v3.

Just came here to say that what you did with apollo client v3 wasted so many hours of so many people with zero new functionality. I still didn't manage to get v3 to fully work, and the above error is just one of the errors with this major version upgrade.

Please consider making less breaking changes for the future major update. This is just too much...

@benjamn
Copy link
Member

benjamn commented Sep 9, 2020

@vfonic If you don't know why you're upgrading ("zero new functionality" is absurd, though I assume you just mean you don't see value in any of the new functionality), any amount of migration effort is going to feel like a chore.

We are not going to slow down the pace of development, but we will continue to have extended prerelease periods before major releases, allowing anyone to try out what we're planning. The beta/RC period for AC3 was nearly a year long! Here's the current v3.2.0 PR, with beta releases you can already try (though it's just a minor release).

More feedback on the consequences of any breaking changes (especially if they were not intended to be breaking) is always welcome. And if you're happy with AC2, maybe try taking a more gradual approach to updating to AC3? I realize it's a lot, but we really want you to end up appreciating the value of what you're doing, rather than getting frustrated.

@vfonic
Copy link

vfonic commented Sep 9, 2020

Thanks for kind words. I appreciate it!

I'm just trying to keep up the pace so I don't end up stuck in some prehistoric version with borderline impossible upgrade path.

I added a CSS-in-JS package and it didn't play nicely with the other packages I had so I tried to upgrade whichever packages stopped working or started having issues (in the process I also upgraded typescript to v4 so that probably broke quite some things). One such package was apollo-client so I chose to upgrade it.

In the end, after I got it to work, I realized the new cache improvements, that I read about, made it slower (at least when using .writeQuery + 'network-only') than in v2 so much so that the UI wasn't getting updated on time (I call writeQuery and redirect immediately after, the redirect redirects back as it reads old data from the cache and figures something's missing; this used to work). I don't know if I set up something wrong. If I get the time, I'll try to create a small repo with repro steps.

One last thing and then I'll finish my blabering: a simple codemod could make this shuffling between v2 and v3 much easier. (apollo-client => @apollo/client, react-apollo => @apollo/client, graphql-tag => @apollo/client). These are the only changes I've read about and noticed while upgrading.

I understand there must be so many changes, but as a user, I didn't see them. I prefer Angular v50 (or React Native), than @apollo/client v3 that has so many changes that it's too difficult to upgrade (AngularJS => Angular). Perhaps having beta releases for a year long is not for the better?

@benjamn
Copy link
Member

benjamn commented Sep 10, 2020

One last thing and then I'll finish my blabering: a simple codemod could make this shuffling between v2 and v3 much easier. (apollo-client => @apollo/client, react-apollo => @apollo/client, graphql-tag => @apollo/client).

@vfonic I'm glad you mentioned this, because we do have an automated transform that can handle most of the import changes, which I now realize was never mentioned in the migration guide (fixed now). It's not perfect, but it's safe to run even if you've already migrated some/most of your imports, since it avoids changing any imports that have already been migrated. Sorry that wasn't more clearly advertised before!

@Antonio-Laguna
Copy link

Antonio-Laguna commented Sep 17, 2020

I've just had this issue. If the schema of a query changes with what was in cache, the whole app blows with this error. Is there any way to catch this?

Even if this is a fatal error there's still a chance to ignore the cache and fetch again before going nuke on the app. Right?

@benjamn benjamn self-assigned this Sep 29, 2020
@sparebytes
Copy link

sparebytes commented Dec 9, 2020

The big issue with this is how difficult it is to track down the offending query as the stack trace and message give no clue as to which one it is except for the field name.

@macrozone
Copy link

macrozone commented Jan 13, 2021

The big issue with this is how difficult it is to track down the offending query as the stack trace and message give no clue as to which one it is except for the field name.

have the same issue, it just does not tell you at all where this is coming from. it just crashes the whole server (because of SSR).

Edit: i brute force checked every Query, but i did not found any query where the field is missing. Also i checked the cache an every object there has the field i want to use

@AoLi12306
Copy link

I was facing similar issue, this happened to me because I was using "id" + "column B" as the primary key, however during fetching, I only fetched "id", after also fetched "column B", error has gone.

@RituSomani
Copy link

I'm facing similar issue. Is there something to do with Schema

@ts-ign0re
Copy link

Yeah, same error

@jonixj
Copy link

jonixj commented May 24, 2021

almost a year since this was reported, still no update?

@wpride
Copy link

wpride commented Jun 10, 2021

A better error message would be really nice here.

@jorbuedo
Copy link

Same here. Is there any workaround to at least debug what's the offending entity? As far as I can tell, all of them have the keyFields.

@jorbuedo
Copy link

jorbuedo commented Aug 19, 2021

Could be as simple as adding the response to the message there:

invariant(!strict, `Missing field '${responseName}' while computing key fields`);

I did that locally to find out the problem.

@benjamn
Copy link
Member

benjamn commented Aug 20, 2021

A couple updates:

Thanks for everyone's patience. Please have a look at those PRs if you're curious/impatient!

@jorbuedo
Copy link

Looks good, thanks for the updates!

In case you are curious, my use case was using Contentful cms. Each content model inherits from Entry, so I added:

possibleTypes: {
    Entry: [...listOfComponents],
},
typePolicies: {
  Entry: {
    keyFields: ['sys', ['id']],
  },
}

Then when querying for some hyperlink that was supposed to be of a certain type, having a fragment that expanded the id... it just broke when the CMS unexpectadly returned a different component.

Catching errors early is good, but having changes on the CMS blow up the site would be no good. So thanks for removing the exception!

benjamn added a commit that referenced this issue Aug 25, 2021
Should help with `cache.identify`-related cases of issue #6673.
benjamn added a commit that referenced this issue Aug 26, 2021
#8679)

* Make `cache.identify` return undefined instead of throwing, to help
  with `cache.identify`-related cases of issue #6673.

* Improve error message when `computeKeyFieldsObject` throws:
  #6673 (comment)
@adamyonk
Copy link
Contributor

I get that this truly is a "fatal" sort of error, but when this happens in a useQuery the state of the app gets stuck on that query being in the loading: true state with no error. And react error boundaries don't seem to be able to catch this. Is there some way to get at this error so I can move the app into a safer error state?

@alicerocheman
Copy link

alicerocheman commented Oct 21, 2021

This error message is ridiculously useless, unless you've only got 1 or 2 entities.

I've got many, and all their keyFields are "id"!

PLEASE add the entity name to the error message, please, pretty pretty please... 🥺

As a workaround, I did this (currently line 1813 of node_modules/@apollo/client/cache/cache.cjs.js):
__DEV__ ? globals.invariant(!strict, "Missing field '" + responseName + "' while computing key fields of "+ JSON.stringify(response)) : globals.invariant(!strict, 4);

@dleavitt
Copy link
Contributor

dleavitt commented Jun 17, 2022

@benjamn nice job on this, it's now very easy to figure out how to resolve these when they occur!

A couple additional questions:

1. Is there any way to handle/catch these invariant violations?

It would be great to be able to show error UI, do reporting, etc. Ideally they should never happen in prod, but in practice it can be hard to know you've tested every possibility in highly polymorphic queries (e.g. an interface that's implemented by dozens of concrete types).

2. Could there be an option to mimic keyFields: false rather than throwing the invariant error if a keyField is missing?

This could also be the default behavior when catching and not re-raising one of these invariant errors.

Like, I might some queries where I just want to opt out of caching behavior for some types.. Let's say I'm trying to get a list of all commenters on a blog post. I'd love to be able to do like post { id name comments { user { id name } } rather than having to make sure I'm asking for the keyFields on comments, which in this query is just an empty edge. Obvs not super compelling in this particular case but important in the "interface with dozens of concrete types" scenario.

@jpvajda jpvajda removed this from the Post 3.0 milestone Aug 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests