From 24b691beca5802d62af0494b56af0b1957e235f4 Mon Sep 17 00:00:00 2001 From: Stuart Clark Date: Sun, 16 Aug 2026 06:40:41 +0000 Subject: [PATCH] fix(druxt): re-hydrate included resources on getCollection cache hit addCollection dehydrated included resources into state.resources but then deleted them from the stored collection, so a second getCollection dispatch for an already-cached query returned included: undefined - silently breaking anything resolving an included resource (e.g. an image/media reference) on a cache hit. --- .changeset/lucky-moons-wave.md | 5 +++++ packages/druxt/src/stores/druxt.js | 11 ++++++---- packages/druxt/test/stores/druxt.test.js | 28 ++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 .changeset/lucky-moons-wave.md diff --git a/.changeset/lucky-moons-wave.md b/.changeset/lucky-moons-wave.md new file mode 100644 index 000000000..fd942f932 --- /dev/null +++ b/.changeset/lucky-moons-wave.md @@ -0,0 +1,5 @@ +--- +'druxt': patch +--- + +fix(#781): re-hydrate included resources on getCollection cache hit diff --git a/packages/druxt/src/stores/druxt.js b/packages/druxt/src/stores/druxt.js index 46eb12028..7ff0f82d2 100644 --- a/packages/druxt/src/stores/druxt.js +++ b/packages/druxt/src/stores/druxt.js @@ -85,10 +85,10 @@ const DruxtStore = ({ store }) => { // Store and dehydrate collection resources. collection.data = dehydrateResources({ commit: this.commit, prefix, queryObject, resources: collection.data }) - // Extract and store included resources. + // Keep the dehydrated refs (don't delete) so a cache hit in + // getCollection can re-hydrate `included`, same as `data`. if (collection.included) { collection.included = dehydrateResources({ commit: this.commit, prefix, queryObject, resources: collection.included }) - delete collection.included } // Recursively merge new collection data into stored collection. @@ -207,10 +207,13 @@ const DruxtStore = ({ store }) => { // If collection hash exists, re-hydrate and return the data. if (!bypassCache && ((state.collections[type] || {})[hash] || {})[prefix]) { + const cached = state.collections[type][hash][prefix] return { - ...state.collections[type][hash][prefix], + ...cached, // Hydrate resource data. - data: state.collections[type][hash][prefix].data.map((o) => ((state.resources[o.type][o.id] || {})[prefix] || {}).data) + data: cached.data.map((o) => ((state.resources[o.type][o.id] || {})[prefix] || {}).data), + // Same for included - a cache hit must match a fresh fetch. + ...(cached.included ? { included: cached.included.map((o) => ((state.resources[o.type][o.id] || {})[prefix] || {}).data) } : {}), } } diff --git a/packages/druxt/test/stores/druxt.test.js b/packages/druxt/test/stores/druxt.test.js index 155ab394a..1d318cf6b 100644 --- a/packages/druxt/test/stores/druxt.test.js +++ b/packages/druxt/test/stores/druxt.test.js @@ -62,8 +62,12 @@ describe('DruxtStore', () => { expect.objectContaining({ id, type: 'node--page' }) ) - // Expect the collection be stored without included data. - expect(store.state.druxt.collections['node--page']._default[undefined].included).toBeFalsy() + // Expect the collection be stored with dehydrated (not dropped) + // included resources, so a later cache hit can re-hydrate `included` + // the same way it re-hydrates `data`. + expect(store.state.druxt.collections['node--page']._default[undefined].included[0]).toStrictEqual( + expect.objectContaining({ id: included[0].id, type: 'node--article' }) + ) }) test('addResource', async () => { @@ -299,6 +303,26 @@ describe('DruxtStore', () => { expect(mockAxios.get).toHaveBeenCalledTimes(2) }) + test('getCollection cache hit re-hydrates included data', async () => { + const mockCollectionPage = await getMockCollection('node--page') + const includedId = 'included-article-uuid' + store.commit('druxt/addCollection', { + collection: { + ...mockCollectionPage, + included: [{ type: 'node--article', id: includedId, attributes: {} }], + }, + type: 'node--page', + hash: '_default', + }) + + // A cache hit must return `included` the same way a fresh fetch would. + const cached = await store.dispatch('druxt/getCollection', { type: 'node--page' }) + expect(cached.included).toHaveLength(1) + expect(cached.included[0]).toStrictEqual( + expect.objectContaining({ id: includedId, type: 'node--article' }) + ) + }) + test('flushCollection', async () => { const type = 'node--page' const hash ='_default'