Skip to content

[Bug] Normalized cache objects lost after hydration due to FrailMap weak reference overwrite #2326

Description

@schettn

Description

When restoring a cache snapshot, normalized objects can unexpectedly get garbage collected. This is especially noticeable when components suspend (via suspense: true), as the React tree temporarily drops references to the cache data while waiting for the network, allowing the GC to wipe the objects.

Root Cause

By design, normalized objects without immediate active query references need to be strongly referenced inside FrailMap to survive.

In packages/gqty/src/Cache/normalization.ts, this is correctly enforced:

store.set(id, result, {
  strong: true, // Intended strong reference
});

However, in packages/gqty/src/Cache/persistence.ts (inside importCacheSnapshot), a redundant .set() call accidentally overwrites this strong reference with a weak one:

data.normalizedObjects = Object.entries(normalized).reduce(
  (store, [key, value]) => {
    const norbject = normalizeObject(value as CacheObject, {
      ...options,
      store,
    });

    // ❌ BUG: Since key === id, this overwrites the entry 
    // with FrailMap's default weak reference (strong: false)
    if (norbject !== undefined) {
      store.set(key, norbject); 
    }

    return store;
  },
  new FrailMap<string, NormalizedObjectShell<CacheObject>>()
);

Proposed Solution

Remove the redundant store.set(key, norbject) call entirely in packages/gqty/src/Cache/persistence.ts. normalizeObject already guarantees that the object is placed in the store with the correct id and a strong: true reference.

  data.normalizedObjects = Object.entries(normalized).reduce(
    (store, [key, value]) => {
-     const norbject = normalizeObject(value as CacheObject, {
+     normalizeObject(value as CacheObject, {
        ...options,
        store,
      });

-     if (norbject !== undefined) {
-       store.set(key, norbject);
-     }
-
      return store;
    },
    new FrailMap<string, NormalizedObjectShell<CacheObject>>()
  );

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions