Skip to content

Commit 978fc52

Browse files
rklompautofix-ci[bot]coderabbitai[bot]
authored
feat(experimental_createQueryPersister): Add removeQueries (#10186)
* Add removeQueries * Docs * ci: apply automated fixes * Fix test * Add changeset * Udpate docs * Await removeItem * Improve test names * Update .changeset/fifty-zebras-stay.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Add coderabbitai suggestion * More data integrity checks * removeItem if deserialize fails * removeItem if deserialize fails in retrieveQuery as well * removeItem if deserialize fails in restoreQueries * Revert changes outside removeQueries scope --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent d7d102e commit 978fc52

4 files changed

Lines changed: 151 additions & 0 deletions

File tree

.changeset/fifty-zebras-stay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/query-persist-client-core': minor
3+
---
4+
5+
Add removeQueries to experimental_createQueryPersister

docs/framework/react/plugins/createPersister.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,21 @@ The filter object supports the following properties:
126126
For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`.
127127
For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`.
128128

129+
### `removeQueries(filters): Promise<void>`
130+
131+
When using `queryClient.removeQueries`, the data remains in the persister and needs to be removed separately.
132+
This function can be used to remove queries that are currently stored by persister.
133+
134+
The filter object supports the following properties:
135+
136+
- `queryKey?: QueryKey`
137+
- Set this property to define a query key to match on.
138+
- `exact?: boolean`
139+
- 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.
140+
141+
For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`.
142+
For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`.
143+
129144
## API
130145

131146
### `experimental_createQueryPersister`

packages/query-persist-client-core/src/__tests__/createPersister.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,4 +675,93 @@ describe('createPersister', () => {
675675
expect(client.getQueryCache().getAll()).toHaveLength(1)
676676
})
677677
})
678+
679+
describe('removeQueries', () => {
680+
test('should remove restore queries from storage without filters', async () => {
681+
const storage = getFreshStorage()
682+
const { persister, client, queryKey } = setupPersister(['foo'], {
683+
storage,
684+
})
685+
client.setQueryData(queryKey, 'foo')
686+
687+
await persister.persistQueryByKey(queryKey, client)
688+
689+
expect(await storage.entries()).toHaveLength(1)
690+
await persister.removeQueries()
691+
expect(await storage.entries()).toHaveLength(0)
692+
})
693+
694+
test('should remove queries from storage', async () => {
695+
const storage = getFreshStorage()
696+
const { persister, client, queryKey } = setupPersister(['foo', 'bar'], {
697+
storage,
698+
})
699+
client.setQueryData(queryKey, 'foo')
700+
701+
await persister.persistQueryByKey(queryKey, client)
702+
703+
expect(await storage.entries()).toHaveLength(1)
704+
await persister.removeQueries({ queryKey })
705+
expect(await storage.entries()).toHaveLength(0)
706+
})
707+
708+
test('should not remove queries from storage if there is no match', async () => {
709+
const storage = getFreshStorage()
710+
const { persister, client, queryKey } = setupPersister(['foo', 'bar'], {
711+
storage,
712+
})
713+
client.setQueryData(queryKey, 'foo')
714+
715+
await persister.persistQueryByKey(queryKey, client)
716+
717+
expect(await storage.entries()).toHaveLength(1)
718+
await persister.removeQueries({ queryKey: ['bar'] })
719+
expect(await storage.entries()).toHaveLength(1)
720+
})
721+
722+
test('should properly remove queries from storage with partial match', async () => {
723+
const storage = getFreshStorage()
724+
const { persister, client, queryKey } = setupPersister(['foo', 'bar'], {
725+
storage,
726+
})
727+
client.setQueryData(queryKey, 'foo')
728+
729+
await persister.persistQueryByKey(queryKey, client)
730+
731+
expect(await storage.entries()).toHaveLength(1)
732+
await persister.removeQueries({ queryKey: ['foo'] })
733+
expect(await storage.entries()).toHaveLength(0)
734+
})
735+
736+
test('should not remove queries from storage with exact match if there is no match', async () => {
737+
const storage = getFreshStorage()
738+
const { persister, client, queryKey } = setupPersister(['foo', 'bar'], {
739+
storage,
740+
})
741+
client.setQueryData(queryKey, 'foo')
742+
743+
await persister.persistQueryByKey(queryKey, client)
744+
745+
expect(await storage.entries()).toHaveLength(1)
746+
await persister.removeQueries({ queryKey: ['foo'], exact: true })
747+
expect(await storage.entries()).toHaveLength(1)
748+
})
749+
750+
test('should remove queries from storage with exact match', async () => {
751+
const storage = getFreshStorage()
752+
const { persister, client, queryKey } = setupPersister(['foo', 'bar'], {
753+
storage,
754+
})
755+
client.setQueryData(queryKey, 'foo')
756+
757+
await persister.persistQueryByKey(queryKey, client)
758+
759+
expect(await storage.entries()).toHaveLength(1)
760+
await persister.removeQueries({
761+
queryKey: queryKey,
762+
exact: true,
763+
})
764+
expect(await storage.entries()).toHaveLength(0)
765+
})
766+
})
678767
})

packages/query-persist-client-core/src/createPersister.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,12 +301,54 @@ export function experimental_createQueryPersister<TStorageValue = string>({
301301
}
302302
}
303303

304+
async function removeQueries(
305+
filters: Pick<QueryFilters, 'queryKey' | 'exact'> = {},
306+
): Promise<void> {
307+
const { exact, queryKey } = filters
308+
309+
if (storage?.entries) {
310+
const entries = await storage.entries()
311+
const storageKeyPrefix = `${prefix}-`
312+
for (const [key, value] of entries) {
313+
if (key.startsWith(storageKeyPrefix)) {
314+
if (!queryKey) {
315+
await storage.removeItem(key)
316+
continue
317+
}
318+
319+
let persistedQuery: PersistedQuery
320+
try {
321+
persistedQuery = await deserialize(value)
322+
} catch {
323+
await storage.removeItem(key)
324+
continue
325+
}
326+
327+
if (exact) {
328+
if (persistedQuery.queryHash !== hashKey(queryKey)) {
329+
continue
330+
}
331+
} else if (!partialMatchKey(persistedQuery.queryKey, queryKey)) {
332+
continue
333+
}
334+
335+
await storage.removeItem(key)
336+
}
337+
}
338+
} else if (process.env.NODE_ENV === 'development') {
339+
throw new Error(
340+
'Provided storage does not implement `entries` method. Removal of stored entries is not possible without ability to iterate over storage items.',
341+
)
342+
}
343+
}
344+
304345
return {
305346
persisterFn,
306347
persistQuery,
307348
persistQueryByKey,
308349
retrieveQuery,
309350
persisterGc,
310351
restoreQueries,
352+
removeQueries,
311353
}
312354
}

0 commit comments

Comments
 (0)