diff --git a/docs/reference/QueryCache.md b/docs/reference/QueryCache.md index 09a9c8e5357..2c34da50881 100644 --- a/docs/reference/QueryCache.md +++ b/docs/reference/QueryCache.md @@ -22,7 +22,7 @@ const queryCache = new QueryCache({ }, }) -const query = queryCache.find(['posts']) +const query = queryCache.find({ queryKey: ['posts'] }) ``` Its available methods are: @@ -52,7 +52,7 @@ Its available methods are: > Note: This is not typically needed for most applications, but can come in handy when needing more information about a query in rare scenarios (eg. Looking at the query.state.dataUpdatedAt timestamp to decide whether a query is fresh enough to be used as an initial value) ```tsx -const query = queryCache.find(queryKey) +const query = queryCache.find({ queryKey }) ``` **Options** @@ -71,7 +71,7 @@ const query = queryCache.find(queryKey) > Note: This is not typically needed for most applications, but can come in handy when needing more information about a query in rare scenarios ```tsx -const queries = queryCache.findAll(queryKey) +const queries = queryCache.findAll({ queryKey }) ``` **Options** 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 8e385bc3dad..ca0a8aeddbf 100644 --- a/packages/query-persist-client-core/src/__tests__/createPersister.test.ts +++ b/packages/query-persist-client-core/src/__tests__/createPersister.test.ts @@ -523,6 +523,23 @@ describe('createPersister', () => { }) }) + describe('retrieveQuery', () => { + it('should return the persisted data when called without a restore callback', async () => { + const storage = getFreshStorage() + const { persister, client, queryHash, queryKey } = setupPersister( + ['foo'], + { storage }, + ) + + client.setQueryData(queryKey, 'baz') + await persister.persistQueryByKey(queryKey, client) + + const restoredData = await persister.retrieveQuery(queryHash) + + expect(restoredData).toBe('baz') + }) + }) + describe('persisterGc', () => { it('should properly clean storage from busted entries', async () => { const storage = getFreshStorage() @@ -542,6 +559,36 @@ describe('createPersister', () => { await persister.persisterGc() expect(await storage.entries()).toHaveLength(0) }) + + it('should remove entries that cannot be deserialized', async () => { + const storage = getFreshStorage() + const { persister } = setupPersister(['foo'], { storage }) + + await storage.setItem(`${PERSISTER_KEY_PREFIX}-["foo"]`, 'not-json{') + expect(await storage.entries()).toHaveLength(1) + + await persister.persisterGc() + expect(await storage.entries()).toHaveLength(0) + }) + + it('should keep entries that are neither expired nor busted', async () => { + const storage = getFreshStorage() + const { persister, client, query, queryKey } = setupPersister(['foo'], { + storage, + }) + query.setState({ + dataUpdatedAt: Date.now(), + data: 'foo', + }) + client.getQueryCache().add(query) + + await persister.persistQueryByKey(queryKey, client) + + expect(await storage.entries()).toHaveLength(1) + + await persister.persisterGc() + expect(await storage.entries()).toHaveLength(1) + }) }) describe('restoreQueries', () => { @@ -674,6 +721,19 @@ describe('createPersister', () => { }) expect(client.getQueryCache().getAll()).toHaveLength(1) }) + + it('should remove entries that cannot be deserialized', async () => { + const storage = getFreshStorage() + const { persister, client } = setupPersister(['foo'], { storage }) + + await storage.setItem(`${PERSISTER_KEY_PREFIX}-["foo"]`, 'not-json{') + expect(await storage.entries()).toHaveLength(1) + + await persister.restoreQueries(client) + + expect(await storage.entries()).toHaveLength(0) + expect(client.getQueryCache().getAll()).toHaveLength(0) + }) }) describe('removeQueries', () => { @@ -763,5 +823,17 @@ describe('createPersister', () => { }) expect(await storage.entries()).toHaveLength(0) }) + + it('should remove entries that cannot be deserialized', async () => { + const storage = getFreshStorage() + const { persister } = setupPersister(['foo'], { storage }) + + await storage.setItem(`${PERSISTER_KEY_PREFIX}-["foo"]`, 'not-json{') + expect(await storage.entries()).toHaveLength(1) + + await persister.removeQueries({ queryKey: ['foo'] }) + + expect(await storage.entries()).toHaveLength(0) + }) }) }) diff --git a/packages/query-persist-client-core/src/__tests__/persist.test.ts b/packages/query-persist-client-core/src/__tests__/persist.test.ts index 3138abc9727..8d2c0f1b56b 100644 --- a/packages/query-persist-client-core/src/__tests__/persist.test.ts +++ b/packages/query-persist-client-core/src/__tests__/persist.test.ts @@ -1,6 +1,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { QueriesObserver, QueryClient, dehydrate } from '@tanstack/query-core' import { + MutationObserver, + QueriesObserver, + QueryClient, + dehydrate, +} from '@tanstack/query-core' +import { + persistQueryClient, persistQueryClientRestore, persistQueryClientSubscribe, } from '../persist' @@ -68,6 +74,28 @@ describe('persist', () => { unsubscribe() }) + + it('should not be triggered on mutation observer type events', () => { + const persister = createSpyPersister() + + const unsubscribe = persistQueryClientSubscribe({ + queryClient, + persister, + }) + + const observer = new MutationObserver(queryClient, { + mutationFn: () => Promise.resolve('data'), + }) + const unsubscribeObserver = observer.subscribe(vi.fn()) + observer.setOptions({ mutationKey: ['test'] }) + unsubscribeObserver() + + // Events fired by manipulating the mutation observer are not cache + // events, so they must not trigger a persist. + expect(persister.persistClient).not.toHaveBeenCalled() + + unsubscribe() + }) }) describe('persistQueryClientRestore', () => { @@ -228,4 +256,45 @@ describe('persist', () => { expect(persister.removeClient).toHaveBeenCalledTimes(1) }) }) + + describe('persistQueryClient', () => { + it('should subscribe to the query cache after a successful restore', async () => { + const persister = createSpyPersister() + persister.restoreClient = () => Promise.resolve(undefined) + + const [unsubscribe, restorePromise] = persistQueryClient({ + queryClient, + persister, + }) + await restorePromise + + queryClient.setQueryData(['key'], 'data') + + expect(persister.persistClient).toHaveBeenCalled() + + unsubscribe() + }) + + it('should not subscribe when unsubscribed before the restore completes', async () => { + const persister = createSpyPersister() + let resolveRestore: () => void + persister.restoreClient = () => + new Promise((resolve) => { + resolveRestore = () => resolve(undefined) + }) + + const [unsubscribe, restorePromise] = persistQueryClient({ + queryClient, + persister, + }) + // Unsubscribe before the restore resolves + unsubscribe() + resolveRestore!() + await restorePromise + + queryClient.setQueryData(['key'], 'data') + + expect(persister.persistClient).not.toHaveBeenCalled() + }) + }) })