Skip to content

Commit 3e16ae6

Browse files
jtomaszewskiclaude
andcommitted
fix(graphcache): preserve entity data when creating new entities
Previously, when a mutation created a truly new entity, graphcache would incorrectly invalidate existing entities of the same type. This happened because the "entity exists" check occurred AFTER writing, so the check was always truthy for new entities. The actual trigger was `!count` (no active references), which wiped data from existing entities. Changes: - Check if entity existed BEFORE writing mutation data to cache - Only auto-invalidate for existing entities with no references - Update test to reflect corrected behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 77663e8 commit 3e16ae6

File tree

2 files changed

+22
-9
lines changed

2 files changed

+22
-9
lines changed

exchanges/graphcache/src/cacheExchange.test.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2229,7 +2229,7 @@ describe('optimistic updates', () => {
22292229
});
22302230

22312231
describe('mutation updates', () => {
2232-
it('invalidates the type when the entity is not present in the cache', () => {
2232+
it('does not auto-invalidate when creating a truly new entity (use updaters instead)', () => {
22332233
vi.useFakeTimers();
22342234

22352235
const authorsQuery = gql`
@@ -2326,12 +2326,9 @@ describe('mutation updates', () => {
23262326

23272327
expect(response).toHaveBeenCalledTimes(2);
23282328
expect(result).toHaveBeenCalledTimes(2);
2329-
expect(reexec).toHaveBeenCalledTimes(1);
2330-
2331-
next(opOne);
2332-
vi.runAllTimers();
2333-
expect(response).toHaveBeenCalledTimes(3);
2334-
expect(result).toHaveBeenCalledTimes(3);
2329+
// With the fix, creating a truly new entity does NOT auto-invalidate
2330+
// existing entities. Use an updater if you need this behavior.
2331+
expect(reexec).toHaveBeenCalledTimes(0);
23352332
expect(result.mock.calls[1][0].data).toEqual({
23362333
addAuthor: {
23372334
id: '2',

exchanges/graphcache/src/operations/write.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,22 @@ const writeSelection = (
330330
continue; // Skip this field
331331
}
332332

333+
// Check if mutation field entity existed BEFORE writing (for auto-invalidation)
334+
let entityExistedBeforeWrite: boolean | undefined;
335+
if (
336+
typename === ctx.store.rootFields['mutation'] &&
337+
!ctx.optimistic &&
338+
fieldValue &&
339+
typeof fieldValue === 'object' &&
340+
!Array.isArray(fieldValue)
341+
) {
342+
const key = ctx.store.keyOfEntity(fieldValue as any);
343+
if (key) {
344+
entityExistedBeforeWrite =
345+
InMemoryData.readRecord(key, '__typename') !== undefined;
346+
}
347+
}
348+
333349
if (node.selectionSet) {
334350
// Process the field and write links for the child entities that have been written
335351
if (entityKey && rootField === 'query') {
@@ -400,9 +416,9 @@ const writeSelection = (
400416
} else if (fieldValue && typeof fieldValue === 'object') {
401417
const key = ctx.store.keyOfEntity(fieldValue as any);
402418
if (key) {
403-
const resolved = InMemoryData.readRecord(key, '__typename');
404419
const count = InMemoryData.getRefCount(key);
405-
if ((!resolved || !count) && fieldValue.__typename) {
420+
// Only invalidate if entity existed BEFORE this mutation and has no references
421+
if (entityExistedBeforeWrite && !count && fieldValue.__typename) {
406422
invalidateType(fieldValue.__typename, [key]);
407423
}
408424
}

0 commit comments

Comments
 (0)