diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46160e22899..af9e20a93a9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,9 @@
```
[@benjamn](https://github.com/benjamn) in [#8678](https://github.com/apollographql/apollo-client/pull/8678)
+- Ensure `cache.identify` never throws when primary key fields are missing, and include the source object in the error message when `keyFields` processing fails.
+ [@benjamn](https://github.com/benjamn) in [#8679](https://github.com/apollographql/apollo-client/pull/8679)
+
### React Refactoring
#### Bug Fixes (due to [@brainkim](https://github.com/brainkim) in [#8596](https://github.com/apollographql/apollo-client/pull/8596)):
diff --git a/src/cache/inmemory/__tests__/entityStore.ts b/src/cache/inmemory/__tests__/entityStore.ts
index a02febc9dfe..53edc086d66 100644
--- a/src/cache/inmemory/__tests__/entityStore.ts
+++ b/src/cache/inmemory/__tests__/entityStore.ts
@@ -1772,9 +1772,21 @@ describe('EntityStore', () => {
c: 3,
})).toBe('ABCs:{"b":2,"a":1,"c":3}');
- expect(() => cache.identify(ABCs)).toThrowError(
- "Missing field 'b' while computing key fields",
- );
+ { // TODO Extact this to a helper function.
+ const consoleWarnSpy = jest.spyOn(console, "warn");
+ consoleWarnSpy.mockImplementation(() => {});
+ try {
+ expect(cache.identify(ABCs)).toBeUndefined();
+ expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ new Error(`Missing field 'b' while extracting keyFields from ${
+ JSON.stringify(ABCs)
+ }`),
+ );
+ } finally {
+ consoleWarnSpy.mockRestore();
+ }
+ }
expect(cache.readFragment({
id: cache.identify({
diff --git a/src/cache/inmemory/__tests__/policies.ts b/src/cache/inmemory/__tests__/policies.ts
index c2a82e025c9..a8bf40b51a1 100644
--- a/src/cache/inmemory/__tests__/policies.ts
+++ b/src/cache/inmemory/__tests__/policies.ts
@@ -346,7 +346,11 @@ describe("type policies", function () {
book: theInformationBookData,
},
});
- }).toThrowError("Missing field 'year' while computing key fields");
+ }).toThrowError(
+ `Missing field 'year' while extracting keyFields from ${JSON.stringify(
+ theInformationBookData
+ )}`,
+ );
});
it("does not clobber previous keyFields with undefined", function () {
diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts
index f48a210f071..c021250bb84 100644
--- a/src/cache/inmemory/inMemoryCache.ts
+++ b/src/cache/inmemory/inMemoryCache.ts
@@ -4,6 +4,7 @@ import './fixPolyfills';
import { DocumentNode } from 'graphql';
import { OptimisticWrapperFunction, wrap } from 'optimism';
import { equal } from '@wry/equality';
+import { invariant } from 'ts-invariant';
import { ApolloCache } from '../core/cache';
import { Cache } from '../core/types/Cache';
@@ -334,8 +335,12 @@ export class InMemoryCache extends ApolloCache {
// sure that none of the primary key fields have been renamed by aliasing.
// If you pass a Reference object, its __ref ID string will be returned.
public identify(object: StoreObject | Reference): string | undefined {
- return isReference(object) ? object.__ref :
- this.policies.identify(object)[0];
+ if (isReference(object)) return object.__ref;
+ try {
+ return this.policies.identify(object)[0];
+ } catch (e) {
+ invariant.warn(e);
+ }
}
public evict(options: Cache.EvictOptions): boolean {
diff --git a/src/cache/inmemory/policies.ts b/src/cache/inmemory/policies.ts
index bdbacfa43ad..d5971168c30 100644
--- a/src/cache/inmemory/policies.ts
+++ b/src/cache/inmemory/policies.ts
@@ -1103,7 +1103,9 @@ function computeKeyFieldsObject(
const responseKey = aliases && aliases[s] || s;
invariant(
hasOwn.call(response, responseKey),
- `Missing field '${responseKey}' while computing key fields`,
+ `Missing field '${responseKey}' while extracting keyFields from ${
+ JSON.stringify(response)
+ }`,
);
keyObj[lastActualKey = s] = response[lastResponseKey = responseKey];
}