Skip to content

Commit 25b4042

Browse files
committed
Restructure provided tags to improve deletion perf
1 parent d624fa3 commit 25b4042

File tree

5 files changed

+91
-154
lines changed

5 files changed

+91
-154
lines changed

packages/toolkit/src/query/core/apiState.ts

+24-32
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { BaseQueryError } from '../baseQueryTypes'
33
import type {
44
BaseEndpointDefinition,
55
EndpointDefinitions,
6+
FullTagDescription,
67
InfiniteQueryDefinition,
78
MutationDefinition,
89
PageParamFrom,
@@ -15,14 +16,8 @@ import type { Id, WithRequiredProp } from '../tsHelpers'
1516
export type QueryCacheKey = string & { _type: 'queryCacheKey' }
1617
export type QuerySubstateIdentifier = { queryCacheKey: QueryCacheKey }
1718
export type MutationSubstateIdentifier =
18-
| {
19-
requestId: string
20-
fixedCacheKey?: string
21-
}
22-
| {
23-
requestId?: string
24-
fixedCacheKey: string
25-
}
19+
| { requestId: string; fixedCacheKey?: string }
20+
| { requestId?: string; fixedCacheKey: string }
2621

2722
export type RefetchConfigOptions = {
2823
refetchOnMountOrArgChange: boolean | number
@@ -225,18 +220,15 @@ export type QuerySubState<
225220
D extends BaseEndpointDefinition<any, any, any>,
226221
DataType = ResultTypeFrom<D>,
227222
> = Id<
228-
| ({
229-
status: QueryStatus.fulfilled
230-
} & WithRequiredProp<
223+
| ({ status: QueryStatus.fulfilled } & WithRequiredProp<
231224
BaseQuerySubState<D, DataType>,
232225
'data' | 'fulfilledTimeStamp'
233226
> & { error: undefined })
234-
| ({
235-
status: QueryStatus.pending
236-
} & BaseQuerySubState<D, DataType>)
237-
| ({
238-
status: QueryStatus.rejected
239-
} & WithRequiredProp<BaseQuerySubState<D, DataType>, 'error'>)
227+
| ({ status: QueryStatus.pending } & BaseQuerySubState<D, DataType>)
228+
| ({ status: QueryStatus.rejected } & WithRequiredProp<
229+
BaseQuerySubState<D, DataType>,
230+
'error'
231+
>)
240232
| {
241233
status: QueryStatus.uninitialized
242234
originalArgs?: undefined
@@ -274,18 +266,17 @@ type BaseMutationSubState<D extends BaseEndpointDefinition<any, any, any>> = {
274266
}
275267

276268
export type MutationSubState<D extends BaseEndpointDefinition<any, any, any>> =
277-
| (({
278-
status: QueryStatus.fulfilled
279-
} & WithRequiredProp<
269+
| (({ status: QueryStatus.fulfilled } & WithRequiredProp<
280270
BaseMutationSubState<D>,
281271
'data' | 'fulfilledTimeStamp'
282272
>) & { error: undefined })
283-
| (({
284-
status: QueryStatus.pending
285-
} & BaseMutationSubState<D>) & { data?: undefined })
286-
| ({
287-
status: QueryStatus.rejected
288-
} & WithRequiredProp<BaseMutationSubState<D>, 'error'>)
273+
| (({ status: QueryStatus.pending } & BaseMutationSubState<D>) & {
274+
data?: undefined
275+
})
276+
| ({ status: QueryStatus.rejected } & WithRequiredProp<
277+
BaseMutationSubState<D>,
278+
'error'
279+
>)
289280
| {
290281
requestId?: undefined
291282
status: QueryStatus.uninitialized
@@ -309,10 +300,13 @@ export type CombinedState<
309300
}
310301

311302
export type InvalidationState<TagTypes extends string> = {
312-
[_ in TagTypes]: {
313-
[id: string]: Array<QueryCacheKey>
314-
[id: number]: Array<QueryCacheKey>
303+
tags: {
304+
[_ in TagTypes]: {
305+
[id: string]: Array<QueryCacheKey>
306+
[id: number]: Array<QueryCacheKey>
307+
}
315308
}
309+
keys: Record<QueryCacheKey, Array<FullTagDescription<any>>>
316310
}
317311

318312
export type QueryState<D extends EndpointDefinitions> = {
@@ -346,6 +340,4 @@ export type RootState<
346340
Definitions extends EndpointDefinitions,
347341
TagTypes extends string,
348342
ReducerPath extends string,
349-
> = {
350-
[P in ReducerPath]: CombinedState<Definitions, TagTypes, P>
351-
}
343+
> = { [P in ReducerPath]: CombinedState<Definitions, TagTypes, P> }

packages/toolkit/src/query/core/buildSelectors.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,7 @@ export function buildSelectors<
199199
function withRequestFlags<T extends { status: QueryStatus }>(
200200
substate: T,
201201
): T & RequestStatusFlags {
202-
return {
203-
...substate,
204-
...getRequestStatusFlags(substate.status),
205-
}
202+
return { ...substate, ...getRequestStatusFlags(substate.status) }
206203
}
207204

208205
function selectApiState(rootState: RootState) {
@@ -344,7 +341,7 @@ export function buildSelectors<
344341
const apiState = state[reducerPath]
345342
const toInvalidate = new Set<QueryCacheKey>()
346343
for (const tag of tags.filter(isNotNullish).map(expandTagDescription)) {
347-
const provided = apiState.provided[tag.type]
344+
const provided = apiState.provided.tags[tag.type]
348345
if (!provided) {
349346
continue
350347
}

packages/toolkit/src/query/core/buildSlice.ts

+39-35
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,7 @@ export function buildSlice({
217217

218218
function writeFulfilledCacheEntry(
219219
draft: QueryState<any>,
220-
meta: {
221-
arg: QueryThunkArg
222-
requestId: string
223-
} & {
220+
meta: { arg: QueryThunkArg; requestId: string } & {
224221
fulfilledTimeStamp: number
225222
baseQueryMeta: unknown
226223
},
@@ -297,11 +294,7 @@ export function buildSlice({
297294
action: PayloadAction<
298295
ProcessedQueryUpsertEntry[],
299296
string,
300-
{
301-
RTK_autoBatch: boolean
302-
requestId: string
303-
timestamp: number
304-
}
297+
{ RTK_autoBatch: boolean; requestId: string; timestamp: number }
305298
>,
306299
) {
307300
for (const entry of action.payload) {
@@ -488,9 +481,14 @@ export function buildSlice({
488481
| ReturnType<ReturnType<InfiniteQueryThunk<any>>>
489482
>
490483

484+
const initialInvalidationState: InvalidationState<string> = {
485+
tags: {},
486+
keys: {},
487+
}
488+
491489
const invalidationSlice = createSlice({
492490
name: `${reducerPath}/invalidation`,
493-
initialState: initialState as InvalidationState<string>,
491+
initialState: initialInvalidationState,
494492
reducers: {
495493
updateProvidedBy: {
496494
reducer(
@@ -502,24 +500,21 @@ export function buildSlice({
502500
) {
503501
const { queryCacheKey, providedTags } = action.payload
504502

505-
for (const tagTypeSubscriptions of Object.values(draft)) {
506-
for (const idSubscriptions of Object.values(tagTypeSubscriptions)) {
507-
const foundAt = idSubscriptions.indexOf(queryCacheKey)
508-
if (foundAt !== -1) {
509-
idSubscriptions.splice(foundAt, 1)
510-
}
511-
}
512-
}
503+
removeCacheKeyFromTags(draft, queryCacheKey)
513504

514505
for (const { type, id } of providedTags) {
515-
const subscribedQueries = ((draft[type] ??= {})[
506+
const subscribedQueries = ((draft.tags[type] ??= {})[
516507
id || '__internal_without_id'
517508
] ??= [])
518509
const alreadySubscribed = subscribedQueries.includes(queryCacheKey)
519510
if (!alreadySubscribed) {
520511
subscribedQueries.push(queryCacheKey)
521512
}
522513
}
514+
515+
// Remove readonly from the providedTags array
516+
draft.keys[queryCacheKey] =
517+
providedTags as FullTagDescription<string>[]
523518
},
524519
prepare: prepareAutoBatched<{
525520
queryCacheKey: QueryCacheKey
@@ -532,23 +527,14 @@ export function buildSlice({
532527
.addCase(
533528
querySlice.actions.removeQueryResult,
534529
(draft, { payload: { queryCacheKey } }) => {
535-
for (const tagTypeSubscriptions of Object.values(draft)) {
536-
for (const idSubscriptions of Object.values(
537-
tagTypeSubscriptions,
538-
)) {
539-
const foundAt = idSubscriptions.indexOf(queryCacheKey)
540-
if (foundAt !== -1) {
541-
idSubscriptions.splice(foundAt, 1)
542-
}
543-
}
544-
}
530+
removeCacheKeyFromTags(draft, queryCacheKey)
545531
},
546532
)
547533
.addMatcher(hasRehydrationInfo, (draft, action) => {
548534
const { provided } = extractRehydrationInfo(action)!
549535
for (const [type, incomingTags] of Object.entries(provided)) {
550536
for (const [id, cacheKeys] of Object.entries(incomingTags)) {
551-
const subscribedQueries = ((draft[type] ??= {})[
537+
const subscribedQueries = ((draft.tags[type] ??= {})[
552538
id || '__internal_without_id'
553539
] ??= [])
554540
for (const queryCacheKey of cacheKeys) {
@@ -574,11 +560,7 @@ export function buildSlice({
574560
const action: CalculateProvidedByAction = {
575561
type: 'UNKNOWN',
576562
payload: value,
577-
meta: {
578-
requestStatus: 'fulfilled',
579-
requestId: 'UNKNOWN',
580-
arg,
581-
},
563+
meta: { requestStatus: 'fulfilled', requestId: 'UNKNOWN', arg },
582564
}
583565

584566
writeProvidedTagsForQuery(draft, action)
@@ -588,6 +570,28 @@ export function buildSlice({
588570
},
589571
})
590572

573+
function removeCacheKeyFromTags(
574+
draft: InvalidationState<any>,
575+
queryCacheKey: QueryCacheKey,
576+
) {
577+
const existingTags = draft.keys[queryCacheKey] ?? []
578+
579+
// Delete this cache key from any existing tags that may have provided it
580+
for (const tag of existingTags) {
581+
const tagType = tag.type
582+
const tagId = tag.id ?? '__internal_without_id'
583+
const tagSubscriptions = draft.tags[tagType]?.[tagId]
584+
585+
if (tagSubscriptions) {
586+
draft.tags[tagType][tagId] = tagSubscriptions.filter(
587+
(qc) => qc !== queryCacheKey,
588+
)
589+
}
590+
}
591+
592+
delete draft.keys[queryCacheKey]
593+
}
594+
591595
function writeProvidedTagsForQuery(
592596
draft: InvalidationState<string>,
593597
action: CalculateProvidedByAction,

packages/toolkit/src/query/tests/optimisticUpdates.test.tsx

+12-30
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,7 @@ const api = createApi({
6060
}),
6161
})
6262

63-
const storeRef = setupApiStore(api, {
64-
...actionsReducer,
65-
})
63+
const storeRef = setupApiStore(api, { ...actionsReducer })
6664

6765
describe('basic lifecycle', () => {
6866
let onStart = vi.fn(),
@@ -96,9 +94,7 @@ describe('basic lifecycle', () => {
9694
test('success', async () => {
9795
const { result } = renderHook(
9896
() => extendedApi.endpoints.test.useMutation(),
99-
{
100-
wrapper: storeRef.wrapper,
101-
},
97+
{ wrapper: storeRef.wrapper },
10298
)
10399

104100
baseQuery.mockResolvedValue('success')
@@ -119,9 +115,7 @@ describe('basic lifecycle', () => {
119115
test('error', async () => {
120116
const { result } = renderHook(
121117
() => extendedApi.endpoints.test.useMutation(),
122-
{
123-
wrapper: storeRef.wrapper,
124-
},
118+
{ wrapper: storeRef.wrapper },
125119
)
126120

127121
baseQuery.mockRejectedValueOnce('error')
@@ -201,11 +195,7 @@ describe('updateQueryData', () => {
201195
test('updates (list) cache values including provided tags, undos that', async () => {
202196
baseQuery
203197
.mockResolvedValueOnce([
204-
{
205-
id: '3',
206-
title: 'All about cheese.',
207-
contents: 'TODO',
208-
},
198+
{ id: '3', title: 'All about cheese.', contents: 'TODO' },
209199
])
210200
.mockResolvedValueOnce(42)
211201
const { result } = renderHook(() => api.endpoints.listPosts.useQuery(), {
@@ -218,7 +208,7 @@ describe('updateQueryData', () => {
218208
provided = storeRef.store.getState().api.provided
219209
})
220210

221-
const provided3 = provided.Post['3']
211+
const provided3 = provided.tags.Post['3']
222212

223213
let returnValue!: ReturnType<ReturnType<typeof api.util.updateQueryData>>
224214
act(() => {
@@ -242,7 +232,7 @@ describe('updateQueryData', () => {
242232
provided = storeRef.store.getState().api.provided
243233
})
244234

245-
const provided4 = provided.Post['4']
235+
const provided4 = provided.tags.Post['4']
246236

247237
expect(provided4).toEqual(provided3)
248238

@@ -254,19 +244,15 @@ describe('updateQueryData', () => {
254244
provided = storeRef.store.getState().api.provided
255245
})
256246

257-
const provided4Next = provided.Post['4']
247+
const provided4Next = provided.tags.Post['4']
258248

259249
expect(provided4Next).toEqual([])
260250
})
261251

262252
test('updates (list) cache values excluding provided tags, undoes that', async () => {
263253
baseQuery
264254
.mockResolvedValueOnce([
265-
{
266-
id: '3',
267-
title: 'All about cheese.',
268-
contents: 'TODO',
269-
},
255+
{ id: '3', title: 'All about cheese.', contents: 'TODO' },
270256
])
271257
.mockResolvedValueOnce(42)
272258
const { result } = renderHook(() => api.endpoints.listPosts.useQuery(), {
@@ -301,7 +287,7 @@ describe('updateQueryData', () => {
301287
provided = storeRef.store.getState().api.provided
302288
})
303289

304-
const provided4 = provided.Post['4']
290+
const provided4 = provided.tags.Post['4']
305291

306292
expect(provided4).toEqual(undefined)
307293

@@ -313,7 +299,7 @@ describe('updateQueryData', () => {
313299
provided = storeRef.store.getState().api.provided
314300
})
315301

316-
const provided4Next = provided.Post['4']
302+
const provided4Next = provided.tags.Post['4']
317303

318304
expect(provided4Next).toEqual(undefined)
319305
})
@@ -382,9 +368,7 @@ describe('full integration', () => {
382368
query: api.endpoints.post.useQuery('3'),
383369
mutation: api.endpoints.updatePost.useMutation(),
384370
}),
385-
{
386-
wrapper: storeRef.wrapper,
387-
},
371+
{ wrapper: storeRef.wrapper },
388372
)
389373
await hookWaitFor(() => expect(result.current.query.isSuccess).toBeTruthy())
390374

@@ -433,9 +417,7 @@ describe('full integration', () => {
433417
query: api.endpoints.post.useQuery('3'),
434418
mutation: api.endpoints.updatePost.useMutation(),
435419
}),
436-
{
437-
wrapper: storeRef.wrapper,
438-
},
420+
{ wrapper: storeRef.wrapper },
439421
)
440422
await hookWaitFor(() => expect(result.current.query.isSuccess).toBeTruthy())
441423

0 commit comments

Comments
 (0)