From 3d35acb49b259dbf7049ed7261e5b1b0994c74ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Wed, 25 Feb 2026 16:18:03 +0100 Subject: [PATCH 01/15] Add removeQueries --- .../src/__tests__/createPersister.test.ts | 89 +++++++++++++++++++ .../src/createPersister.ts | 32 +++++++ 2 files changed, 121 insertions(+) diff --git a/packages/query-persist-client-core/src/__tests__/createPersister.test.ts b/packages/query-persist-client-core/src/__tests__/createPersister.test.ts index 5434409264f..54a6b6e9c69 100644 --- a/packages/query-persist-client-core/src/__tests__/createPersister.test.ts +++ b/packages/query-persist-client-core/src/__tests__/createPersister.test.ts @@ -675,4 +675,93 @@ describe('createPersister', () => { expect(client.getQueryCache().getAll()).toHaveLength(1) }) }) + + describe('removeQueries', () => { + test('should remove restore queries from storage without filters', async () => { + const storage = getFreshStorage() + const { persister, client, queryKey } = setupPersister(['foo'], { + storage, + }) + client.setQueryData(queryKey, 'foo') + + await persister.persistQueryByKey(queryKey, client) + + expect(await storage.entries()).toHaveLength(1) + await persister.removeQueries() + expect(client.getQueryCache().getAll()).toHaveLength(0) + }) + + test('should remove queries from storage', async () => { + const storage = getFreshStorage() + const { persister, client, queryKey } = setupPersister(['foo', 'bar'], { + storage, + }) + client.setQueryData(queryKey, 'foo') + + await persister.persistQueryByKey(queryKey, client) + + expect(await storage.entries()).toHaveLength(1) + await persister.removeQueries({ queryKey }) + expect(await storage.entries()).toHaveLength(0) + }) + + test('should not remove queries from cache if there is no match', async () => { + const storage = getFreshStorage() + const { persister, client, queryKey } = setupPersister(['foo', 'bar'], { + storage, + }) + client.setQueryData(queryKey, 'foo') + + await persister.persistQueryByKey(queryKey, client) + + expect(await storage.entries()).toHaveLength(1) + await persister.removeQueries({ queryKey: ['bar'] }) + expect(await storage.entries()).toHaveLength(1) + }) + + test('should properly remove queries from cache with partial match', async () => { + const storage = getFreshStorage() + const { persister, client, queryKey } = setupPersister(['foo', 'bar'], { + storage, + }) + client.setQueryData(queryKey, 'foo') + + await persister.persistQueryByKey(queryKey, client) + + expect(await storage.entries()).toHaveLength(1) + await persister.removeQueries({ queryKey: ['foo'] }) + expect(await storage.entries()).toHaveLength(0) + }) + + test('should not remove queries from cache with exact match if there is no match', async () => { + const storage = getFreshStorage() + const { persister, client, queryKey } = setupPersister(['foo', 'bar'], { + storage, + }) + client.setQueryData(queryKey, 'foo') + + await persister.persistQueryByKey(queryKey, client) + + expect(await storage.entries()).toHaveLength(1) + await persister.removeQueries({ queryKey: ['foo'], exact: true }) + expect(await storage.entries()).toHaveLength(1) + }) + + test('should remove queries from cache with exact match', async () => { + const storage = getFreshStorage() + const { persister, client, queryKey } = setupPersister(['foo', 'bar'], { + storage, + }) + client.setQueryData(queryKey, 'foo') + + await persister.persistQueryByKey(queryKey, client) + + expect(await storage.entries()).toHaveLength(1) + await persister.removeQueries({ + queryKey: queryKey, + exact: true, + }) + expect(await storage.entries()).toHaveLength(0) + }) + }) }) diff --git a/packages/query-persist-client-core/src/createPersister.ts b/packages/query-persist-client-core/src/createPersister.ts index 55fccc5558b..5199ebda9ad 100644 --- a/packages/query-persist-client-core/src/createPersister.ts +++ b/packages/query-persist-client-core/src/createPersister.ts @@ -301,6 +301,37 @@ export function experimental_createQueryPersister({ } } + async function removeQueries( + filters: Pick = {}, + ): Promise { + const { exact, queryKey } = filters + + if (storage?.entries) { + const entries = await storage.entries() + for (const [key, value] of entries) { + if (key.startsWith(prefix)) { + const persistedQuery = await deserialize(value) + + if (queryKey) { + if (exact) { + if (persistedQuery.queryHash !== hashKey(queryKey)) { + continue + } + } else if (!partialMatchKey(persistedQuery.queryKey, queryKey)) { + continue + } + } + + storage.removeItem(key) + } + } + } else if (process.env.NODE_ENV === 'development') { + throw new Error( + 'Provided storage does not implement `entries` method. Restoration of all stored entries is not possible without ability to iterate over storage items.', + ) + } + } + return { persisterFn, persistQuery, @@ -308,5 +339,6 @@ export function experimental_createQueryPersister({ retrieveQuery, persisterGc, restoreQueries, + removeQueries, } } From 39084af18b5b06ef08f9e6549280469ab3e5ff8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Wed, 25 Feb 2026 16:23:43 +0100 Subject: [PATCH 02/15] Docs --- docs/framework/react/plugins/createPersister.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/framework/react/plugins/createPersister.md b/docs/framework/react/plugins/createPersister.md index 413979a7e0f..0118188b6df 100644 --- a/docs/framework/react/plugins/createPersister.md +++ b/docs/framework/react/plugins/createPersister.md @@ -126,6 +126,21 @@ The filter object supports the following properties: For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`. For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`. +### `removeQueries(queryClient: QueryClient, filters): Promise` + +When using `queryClient.removeQueries` the data is kept in the persistor and to be removed separately. +This function can be used to remove queries that are currently stored by persister. + +The filter object supports the following properties: + +- `queryKey?: QueryKey` + - Set this property to define a query key to match on. +- `exact?: boolean` + - If you don't want to search queries inclusively by query key, you can pass the `exact: true` option to return only the query with the exact query key you have passed. + +For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`. +For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`. + ## API ### `experimental_createQueryPersister` From 9d425b037453ac07f41114b6f69ec9d57acdaecb Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:25:40 +0000 Subject: [PATCH 03/15] ci: apply automated fixes --- docs/framework/react/plugins/createPersister.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/framework/react/plugins/createPersister.md b/docs/framework/react/plugins/createPersister.md index 0118188b6df..a99de889872 100644 --- a/docs/framework/react/plugins/createPersister.md +++ b/docs/framework/react/plugins/createPersister.md @@ -129,7 +129,7 @@ For example `Object.entries(localStorage)` for `localStorage` or `entries` from ### `removeQueries(queryClient: QueryClient, filters): Promise` When using `queryClient.removeQueries` the data is kept in the persistor and to be removed separately. -This function can be used to remove queries that are currently stored by persister. +This function can be used to remove queries that are currently stored by persister. The filter object supports the following properties: From 76317f494ee3fe4fa1df4e3d30ce731ff938a95c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Wed, 25 Feb 2026 16:32:17 +0100 Subject: [PATCH 04/15] Fix test --- .../src/__tests__/createPersister.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/query-persist-client-core/src/__tests__/createPersister.test.ts b/packages/query-persist-client-core/src/__tests__/createPersister.test.ts index 54a6b6e9c69..9a340f33196 100644 --- a/packages/query-persist-client-core/src/__tests__/createPersister.test.ts +++ b/packages/query-persist-client-core/src/__tests__/createPersister.test.ts @@ -688,7 +688,7 @@ describe('createPersister', () => { expect(await storage.entries()).toHaveLength(1) await persister.removeQueries() - expect(client.getQueryCache().getAll()).toHaveLength(0) + expect(await storage.entries()).toHaveLength(0) }) test('should remove queries from storage', async () => { From d8867e40780e164328c5cddf69057e1ce051cca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Wed, 25 Feb 2026 16:38:22 +0100 Subject: [PATCH 05/15] Add changeset --- .changeset/fifty-zebras-stay.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fifty-zebras-stay.md diff --git a/.changeset/fifty-zebras-stay.md b/.changeset/fifty-zebras-stay.md new file mode 100644 index 00000000000..12d1b731da7 --- /dev/null +++ b/.changeset/fifty-zebras-stay.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-persist-client-core': major +--- + +Add removeQueries to experimental_createQueryPersister From 70d9d0f39b18a177cb0ff0d9e4f176420552cc83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Wed, 25 Feb 2026 16:43:19 +0100 Subject: [PATCH 06/15] Udpate docs --- docs/framework/react/plugins/createPersister.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/framework/react/plugins/createPersister.md b/docs/framework/react/plugins/createPersister.md index a99de889872..0b59b216ce1 100644 --- a/docs/framework/react/plugins/createPersister.md +++ b/docs/framework/react/plugins/createPersister.md @@ -126,9 +126,9 @@ The filter object supports the following properties: For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`. For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`. -### `removeQueries(queryClient: QueryClient, filters): Promise` +### `removeQueries(filters): Promise` -When using `queryClient.removeQueries` the data is kept in the persistor and to be removed separately. +When using `queryClient.removeQueries`, the data remains in the persister and needs to be removed separately. This function can be used to remove queries that are currently stored by persister. The filter object supports the following properties: From 235beeb6437c3d2d4d281d70f58bf037e81d5ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Wed, 25 Feb 2026 16:43:47 +0100 Subject: [PATCH 07/15] Await removeItem --- packages/query-persist-client-core/src/createPersister.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/query-persist-client-core/src/createPersister.ts b/packages/query-persist-client-core/src/createPersister.ts index 5199ebda9ad..1c5a4049b43 100644 --- a/packages/query-persist-client-core/src/createPersister.ts +++ b/packages/query-persist-client-core/src/createPersister.ts @@ -322,12 +322,12 @@ export function experimental_createQueryPersister({ } } - storage.removeItem(key) + await storage.removeItem(key) } } } else if (process.env.NODE_ENV === 'development') { throw new Error( - 'Provided storage does not implement `entries` method. Restoration of all stored entries is not possible without ability to iterate over storage items.', + 'Provided storage does not implement `entries` method. Removal of stored entries is not possible without ability to iterate over storage items.', ) } } From e52bb4f0792acd5f7012d50946e98747f1033fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Wed, 25 Feb 2026 16:52:28 +0100 Subject: [PATCH 08/15] Improve test names --- .../src/__tests__/createPersister.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/query-persist-client-core/src/__tests__/createPersister.test.ts b/packages/query-persist-client-core/src/__tests__/createPersister.test.ts index 9a340f33196..e9ff7eb8bf3 100644 --- a/packages/query-persist-client-core/src/__tests__/createPersister.test.ts +++ b/packages/query-persist-client-core/src/__tests__/createPersister.test.ts @@ -705,7 +705,7 @@ describe('createPersister', () => { expect(await storage.entries()).toHaveLength(0) }) - test('should not remove queries from cache if there is no match', async () => { + test('should not remove queries from storage if there is no match', async () => { const storage = getFreshStorage() const { persister, client, queryKey } = setupPersister(['foo', 'bar'], { storage, @@ -719,7 +719,7 @@ describe('createPersister', () => { expect(await storage.entries()).toHaveLength(1) }) - test('should properly remove queries from cache with partial match', async () => { + test('should properly remove queries from storage with partial match', async () => { const storage = getFreshStorage() const { persister, client, queryKey } = setupPersister(['foo', 'bar'], { storage, @@ -733,7 +733,7 @@ describe('createPersister', () => { expect(await storage.entries()).toHaveLength(0) }) - test('should not remove queries from cache with exact match if there is no match', async () => { + test('should not remove queries from storage with exact match if there is no match', async () => { const storage = getFreshStorage() const { persister, client, queryKey } = setupPersister(['foo', 'bar'], { storage, @@ -747,7 +747,7 @@ describe('createPersister', () => { expect(await storage.entries()).toHaveLength(1) }) - test('should remove queries from cache with exact match', async () => { + test('should remove queries from storage with exact match', async () => { const storage = getFreshStorage() const { persister, client, queryKey } = setupPersister(['foo', 'bar'], { storage, From 73589e0dc4352d04cc2616d63c1a53bef8028077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Wed, 25 Feb 2026 16:45:23 +0100 Subject: [PATCH 09/15] Update .changeset/fifty-zebras-stay.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .changeset/fifty-zebras-stay.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fifty-zebras-stay.md b/.changeset/fifty-zebras-stay.md index 12d1b731da7..197f98b390e 100644 --- a/.changeset/fifty-zebras-stay.md +++ b/.changeset/fifty-zebras-stay.md @@ -1,5 +1,5 @@ --- -'@tanstack/query-persist-client-core': major +'@tanstack/query-persist-client-core': minor --- Add removeQueries to experimental_createQueryPersister From 8bd48b631e8a5fd94bf8a7c60341e215ecd49fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Wed, 25 Feb 2026 16:57:25 +0100 Subject: [PATCH 10/15] Add coderabbitai suggestion --- .../src/createPersister.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/query-persist-client-core/src/createPersister.ts b/packages/query-persist-client-core/src/createPersister.ts index 1c5a4049b43..cda5544b1f7 100644 --- a/packages/query-persist-client-core/src/createPersister.ts +++ b/packages/query-persist-client-core/src/createPersister.ts @@ -266,9 +266,15 @@ export function experimental_createQueryPersister({ if (storage?.entries) { const entries = await storage.entries() + const storageKeyPrefix = `${prefix}-` for (const [key, value] of entries) { - if (key.startsWith(prefix)) { - const persistedQuery = await deserialize(value) + if (key.startsWith(storageKeyPrefix)) { + let persistedQuery: PersistedQuery + try { + persistedQuery = await deserialize(value) + } catch { + continue + } if (isExpiredOrBusted(persistedQuery)) { await storage.removeItem(key) @@ -308,9 +314,15 @@ export function experimental_createQueryPersister({ if (storage?.entries) { const entries = await storage.entries() + const storageKeyPrefix = `${prefix}-` for (const [key, value] of entries) { - if (key.startsWith(prefix)) { - const persistedQuery = await deserialize(value) + if (key.startsWith(storageKeyPrefix)) { + let persistedQuery: PersistedQuery + try { + persistedQuery = await deserialize(value) + } catch { + continue + } if (queryKey) { if (exact) { From 448c3cb048334e2ecbb32043065eeb25b19d751e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Wed, 25 Feb 2026 17:07:51 +0100 Subject: [PATCH 11/15] More data integrity checks --- .../src/createPersister.ts | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/query-persist-client-core/src/createPersister.ts b/packages/query-persist-client-core/src/createPersister.ts index cda5544b1f7..53c4fb98acd 100644 --- a/packages/query-persist-client-core/src/createPersister.ts +++ b/packages/query-persist-client-core/src/createPersister.ts @@ -244,7 +244,13 @@ export function experimental_createQueryPersister({ const entries = await storage.entries() for (const [key, value] of entries) { if (key.startsWith(prefix)) { - const persistedQuery = await deserialize(value) + let persistedQuery: PersistedQuery + try { + persistedQuery = await deserialize(value) + } catch { + await storage.removeItem(key) + continue + } if (isExpiredOrBusted(persistedQuery)) { await storage.removeItem(key) @@ -317,6 +323,11 @@ export function experimental_createQueryPersister({ const storageKeyPrefix = `${prefix}-` for (const [key, value] of entries) { if (key.startsWith(storageKeyPrefix)) { + if (!queryKey) { + await storage.removeItem(key) + continue + } + let persistedQuery: PersistedQuery try { persistedQuery = await deserialize(value) @@ -324,14 +335,12 @@ export function experimental_createQueryPersister({ continue } - if (queryKey) { - if (exact) { - if (persistedQuery.queryHash !== hashKey(queryKey)) { - continue - } - } else if (!partialMatchKey(persistedQuery.queryKey, queryKey)) { + if (exact) { + if (persistedQuery.queryHash !== hashKey(queryKey)) { continue } + } else if (!partialMatchKey(persistedQuery.queryKey, queryKey)) { + continue } await storage.removeItem(key) From 544d2114740c6c59e176e204a37830d8c77ae457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Thu, 26 Feb 2026 10:14:10 +0100 Subject: [PATCH 12/15] removeItem if deserialize fails --- packages/query-persist-client-core/src/createPersister.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/query-persist-client-core/src/createPersister.ts b/packages/query-persist-client-core/src/createPersister.ts index 53c4fb98acd..67a32f1f3be 100644 --- a/packages/query-persist-client-core/src/createPersister.ts +++ b/packages/query-persist-client-core/src/createPersister.ts @@ -332,6 +332,7 @@ export function experimental_createQueryPersister({ try { persistedQuery = await deserialize(value) } catch { + await storage.removeItem(key) continue } From a60f320d61c74f86d668db861c1764412561e0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Thu, 26 Feb 2026 10:19:28 +0100 Subject: [PATCH 13/15] removeItem if deserialize fails in retrieveQuery as well --- packages/query-persist-client-core/src/createPersister.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/query-persist-client-core/src/createPersister.ts b/packages/query-persist-client-core/src/createPersister.ts index 67a32f1f3be..b7084b3ff9e 100644 --- a/packages/query-persist-client-core/src/createPersister.ts +++ b/packages/query-persist-client-core/src/createPersister.ts @@ -131,7 +131,13 @@ export function experimental_createQueryPersister({ try { const storedData = await storage.getItem(storageKey) if (storedData) { - const persistedQuery = await deserialize(storedData) + let persistedQuery: PersistedQuery + try { + persistedQuery = await deserialize(storedData) + } catch { + await storage.removeItem(storageKey) + return + } if (isExpiredOrBusted(persistedQuery)) { await storage.removeItem(storageKey) From f7d574486c4034e48af24800ea0e4c616b000a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Thu, 26 Feb 2026 10:31:45 +0100 Subject: [PATCH 14/15] removeItem if deserialize fails in restoreQueries --- packages/query-persist-client-core/src/createPersister.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/query-persist-client-core/src/createPersister.ts b/packages/query-persist-client-core/src/createPersister.ts index b7084b3ff9e..c4817a7137c 100644 --- a/packages/query-persist-client-core/src/createPersister.ts +++ b/packages/query-persist-client-core/src/createPersister.ts @@ -285,6 +285,7 @@ export function experimental_createQueryPersister({ try { persistedQuery = await deserialize(value) } catch { + await storage.removeItem(key) continue } From a22eadff9f75af4e8b141435e60d198ba59a06c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Thu, 26 Feb 2026 10:53:15 +0100 Subject: [PATCH 15/15] Revert changes outside removeQueries scope --- .../src/createPersister.ts | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/packages/query-persist-client-core/src/createPersister.ts b/packages/query-persist-client-core/src/createPersister.ts index c4817a7137c..780ae4d6acd 100644 --- a/packages/query-persist-client-core/src/createPersister.ts +++ b/packages/query-persist-client-core/src/createPersister.ts @@ -131,13 +131,7 @@ export function experimental_createQueryPersister({ try { const storedData = await storage.getItem(storageKey) if (storedData) { - let persistedQuery: PersistedQuery - try { - persistedQuery = await deserialize(storedData) - } catch { - await storage.removeItem(storageKey) - return - } + const persistedQuery = await deserialize(storedData) if (isExpiredOrBusted(persistedQuery)) { await storage.removeItem(storageKey) @@ -250,13 +244,7 @@ export function experimental_createQueryPersister({ const entries = await storage.entries() for (const [key, value] of entries) { if (key.startsWith(prefix)) { - let persistedQuery: PersistedQuery - try { - persistedQuery = await deserialize(value) - } catch { - await storage.removeItem(key) - continue - } + const persistedQuery = await deserialize(value) if (isExpiredOrBusted(persistedQuery)) { await storage.removeItem(key) @@ -278,16 +266,9 @@ export function experimental_createQueryPersister({ if (storage?.entries) { const entries = await storage.entries() - const storageKeyPrefix = `${prefix}-` for (const [key, value] of entries) { - if (key.startsWith(storageKeyPrefix)) { - let persistedQuery: PersistedQuery - try { - persistedQuery = await deserialize(value) - } catch { - await storage.removeItem(key) - continue - } + if (key.startsWith(prefix)) { + const persistedQuery = await deserialize(value) if (isExpiredOrBusted(persistedQuery)) { await storage.removeItem(key)