1
1
import type { InternalHandlerBuilder , SubscriptionSelectors } from './types'
2
- import type { SubscriptionState } from '../apiState'
2
+ import type { SubscriptionInternalState , SubscriptionState } from '../apiState'
3
3
import { produceWithPatches } from 'immer'
4
4
import type { Action } from '@reduxjs/toolkit'
5
- import { countObjectKeys } from '../../utils/countObjectKeys '
5
+ import { getOrInsertComputed , createNewMap } from '../../utils/getOrInsert '
6
6
7
7
export const buildBatchedActionsHandler : InternalHandlerBuilder <
8
8
[ actionShouldContinue : boolean , returnValue : SubscriptionSelectors | boolean ]
9
- > = ( { api, queryThunk, internalState } ) => {
9
+ > = ( { api, queryThunk, internalState, mwApi } ) => {
10
10
const subscriptionsPrefix = `${ api . reducerPath } /subscriptions`
11
11
12
12
let previousSubscriptions : SubscriptionState =
@@ -20,58 +20,63 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
20
20
// Actually intentionally mutate the subscriptions state used in the middleware
21
21
// This is done to speed up perf when loading many components
22
22
const actuallyMutateSubscriptions = (
23
- mutableState : SubscriptionState ,
23
+ currentSubscriptions : SubscriptionInternalState ,
24
24
action : Action ,
25
25
) => {
26
26
if ( updateSubscriptionOptions . match ( action ) ) {
27
27
const { queryCacheKey, requestId, options } = action . payload
28
28
29
- if ( mutableState ?. [ queryCacheKey ] ?. [ requestId ] ) {
30
- mutableState [ queryCacheKey ] ! [ requestId ] = options
29
+ const sub = currentSubscriptions . get ( queryCacheKey )
30
+ if ( sub ?. has ( requestId ) ) {
31
+ sub . set ( requestId , options )
31
32
}
32
33
return true
33
34
}
34
35
if ( unsubscribeQueryResult . match ( action ) ) {
35
36
const { queryCacheKey, requestId } = action . payload
36
- if ( mutableState [ queryCacheKey ] ) {
37
- delete mutableState [ queryCacheKey ] ! [ requestId ]
37
+ const sub = currentSubscriptions . get ( queryCacheKey )
38
+ if ( sub ) {
39
+ sub . delete ( requestId )
38
40
}
39
41
return true
40
42
}
41
43
if ( api . internalActions . removeQueryResult . match ( action ) ) {
42
- delete mutableState [ action . payload . queryCacheKey ]
44
+ currentSubscriptions . delete ( action . payload . queryCacheKey )
43
45
return true
44
46
}
45
47
if ( queryThunk . pending . match ( action ) ) {
46
48
const {
47
49
meta : { arg, requestId } ,
48
50
} = action
49
- const substate = ( mutableState [ arg . queryCacheKey ] ??= { } )
50
- substate [ `${ requestId } _running` ] = { }
51
+ const substate = getOrInsertComputed (
52
+ currentSubscriptions ,
53
+ arg . queryCacheKey ,
54
+ createNewMap ,
55
+ )
51
56
if ( arg . subscribe ) {
52
- substate [ requestId ] =
53
- arg . subscriptionOptions ?? substate [ requestId ] ?? { }
57
+ substate . set (
58
+ requestId ,
59
+ arg . subscriptionOptions ?? substate . get ( requestId ) ?? { } ,
60
+ )
54
61
}
55
62
return true
56
63
}
57
64
let mutated = false
58
- if (
59
- queryThunk . fulfilled . match ( action ) ||
60
- queryThunk . rejected . match ( action )
61
- ) {
62
- const state = mutableState [ action . meta . arg . queryCacheKey ] || { }
63
- const key = `${ action . meta . requestId } _running`
64
- mutated ||= ! ! state [ key ]
65
- delete state [ key ]
66
- }
65
+
67
66
if ( queryThunk . rejected . match ( action ) ) {
68
67
const {
69
68
meta : { condition, arg, requestId } ,
70
69
} = action
71
70
if ( condition && arg . subscribe ) {
72
- const substate = ( mutableState [ arg . queryCacheKey ] ??= { } )
73
- substate [ requestId ] =
74
- arg . subscriptionOptions ?? substate [ requestId ] ?? { }
71
+ const substate = getOrInsertComputed (
72
+ currentSubscriptions ,
73
+ arg . queryCacheKey ,
74
+ createNewMap ,
75
+ )
76
+ substate . set (
77
+ requestId ,
78
+ arg . subscriptionOptions ?? substate . get ( requestId ) ?? { } ,
79
+ )
75
80
76
81
mutated = true
77
82
}
@@ -83,12 +88,12 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
83
88
const getSubscriptions = ( ) => internalState . currentSubscriptions
84
89
const getSubscriptionCount = ( queryCacheKey : string ) => {
85
90
const subscriptions = getSubscriptions ( )
86
- const subscriptionsForQueryArg = subscriptions [ queryCacheKey ] ?? { }
87
- return countObjectKeys ( subscriptionsForQueryArg )
91
+ const subscriptionsForQueryArg = subscriptions . get ( queryCacheKey )
92
+ return subscriptionsForQueryArg ?. size ?? 0
88
93
}
89
94
const isRequestSubscribed = ( queryCacheKey : string , requestId : string ) => {
90
95
const subscriptions = getSubscriptions ( )
91
- return ! ! subscriptions ?. [ queryCacheKey ] ?. [ requestId ]
96
+ return ! ! subscriptions ?. get ( queryCacheKey ) ?. get ( requestId )
92
97
}
93
98
94
99
const subscriptionSelectors : SubscriptionSelectors = {
@@ -97,6 +102,21 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
97
102
isRequestSubscribed,
98
103
}
99
104
105
+ function serializeSubscriptions (
106
+ currentSubscriptions : SubscriptionInternalState ,
107
+ ) : SubscriptionState {
108
+ // We now use nested Maps for subscriptions, instead of
109
+ // plain Records. Stringify this accordingly so we can
110
+ // convert it to the shape we need for the store.
111
+ return JSON . parse (
112
+ JSON . stringify (
113
+ Object . fromEntries (
114
+ [ ...currentSubscriptions ] . map ( ( [ k , v ] ) => [ k , Object . fromEntries ( v ) ] ) ,
115
+ ) ,
116
+ ) ,
117
+ )
118
+ }
119
+
100
120
return (
101
121
action ,
102
122
mwApi ,
@@ -106,13 +126,14 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
106
126
] => {
107
127
if ( ! previousSubscriptions ) {
108
128
// Initialize it the first time this handler runs
109
- previousSubscriptions = JSON . parse (
110
- JSON . stringify ( internalState . currentSubscriptions ) ,
129
+ previousSubscriptions = serializeSubscriptions (
130
+ internalState . currentSubscriptions ,
111
131
)
112
132
}
113
133
114
134
if ( api . util . resetApiState . match ( action ) ) {
115
- previousSubscriptions = internalState . currentSubscriptions = { }
135
+ previousSubscriptions = { }
136
+ internalState . currentSubscriptions . clear ( )
116
137
updateSyncTimer = null
117
138
return [ true , false ]
118
139
}
@@ -133,6 +154,15 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
133
154
134
155
let actionShouldContinue = true
135
156
157
+ // HACK Sneak the test-only polling state back out
158
+ if (
159
+ process . env . NODE_ENV === 'test' &&
160
+ typeof action . type === 'string' &&
161
+ action . type === `${ api . reducerPath } /getPolling`
162
+ ) {
163
+ return [ false , internalState . currentPolls ] as any
164
+ }
165
+
136
166
if ( didMutate ) {
137
167
if ( ! updateSyncTimer ) {
138
168
// We only use the subscription state for the Redux DevTools at this point,
@@ -142,8 +172,8 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
142
172
// In 1.9, it was updated in a microtask, but now we do it at most every 500ms.
143
173
updateSyncTimer = setTimeout ( ( ) => {
144
174
// Deep clone the current subscription data
145
- const newSubscriptions : SubscriptionState = JSON . parse (
146
- JSON . stringify ( internalState . currentSubscriptions ) ,
175
+ const newSubscriptions : SubscriptionState = serializeSubscriptions (
176
+ internalState . currentSubscriptions ,
147
177
)
148
178
// Figure out a smaller diff between original and current
149
179
const [ , patches ] = produceWithPatches (
0 commit comments