11import type { InternalHandlerBuilder , SubscriptionSelectors } from './types'
2- import type { SubscriptionState } from '../apiState'
2+ import type { SubscriptionInternalState , SubscriptionState } from '../apiState'
33import { produceWithPatches } from 'immer'
44import type { Action } from '@reduxjs/toolkit'
5- import { countObjectKeys } from '../../utils/countObjectKeys '
5+ import { getOrInsertComputed , createNewMap } from '../../utils/getOrInsert '
66
77export const buildBatchedActionsHandler : InternalHandlerBuilder <
88 [ actionShouldContinue : boolean , returnValue : SubscriptionSelectors | boolean ]
9- > = ( { api, queryThunk, internalState } ) => {
9+ > = ( { api, queryThunk, internalState, mwApi } ) => {
1010 const subscriptionsPrefix = `${ api . reducerPath } /subscriptions`
1111
1212 let previousSubscriptions : SubscriptionState =
@@ -20,58 +20,63 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
2020 // Actually intentionally mutate the subscriptions state used in the middleware
2121 // This is done to speed up perf when loading many components
2222 const actuallyMutateSubscriptions = (
23- mutableState : SubscriptionState ,
23+ currentSubscriptions : SubscriptionInternalState ,
2424 action : Action ,
2525 ) => {
2626 if ( updateSubscriptionOptions . match ( action ) ) {
2727 const { queryCacheKey, requestId, options } = action . payload
2828
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 )
3132 }
3233 return true
3334 }
3435 if ( unsubscribeQueryResult . match ( action ) ) {
3536 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 )
3840 }
3941 return true
4042 }
4143 if ( api . internalActions . removeQueryResult . match ( action ) ) {
42- delete mutableState [ action . payload . queryCacheKey ]
44+ currentSubscriptions . delete ( action . payload . queryCacheKey )
4345 return true
4446 }
4547 if ( queryThunk . pending . match ( action ) ) {
4648 const {
4749 meta : { arg, requestId } ,
4850 } = action
49- const substate = ( mutableState [ arg . queryCacheKey ] ??= { } )
50- substate [ `${ requestId } _running` ] = { }
51+ const substate = getOrInsertComputed (
52+ currentSubscriptions ,
53+ arg . queryCacheKey ,
54+ createNewMap ,
55+ )
5156 if ( arg . subscribe ) {
52- substate [ requestId ] =
53- arg . subscriptionOptions ?? substate [ requestId ] ?? { }
57+ substate . set (
58+ requestId ,
59+ arg . subscriptionOptions ?? substate . get ( requestId ) ?? { } ,
60+ )
5461 }
5562 return true
5663 }
5764 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+
6766 if ( queryThunk . rejected . match ( action ) ) {
6867 const {
6968 meta : { condition, arg, requestId } ,
7069 } = action
7170 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+ )
7580
7681 mutated = true
7782 }
@@ -83,12 +88,12 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
8388 const getSubscriptions = ( ) => internalState . currentSubscriptions
8489 const getSubscriptionCount = ( queryCacheKey : string ) => {
8590 const subscriptions = getSubscriptions ( )
86- const subscriptionsForQueryArg = subscriptions [ queryCacheKey ] ?? { }
87- return countObjectKeys ( subscriptionsForQueryArg )
91+ const subscriptionsForQueryArg = subscriptions . get ( queryCacheKey )
92+ return subscriptionsForQueryArg ?. size ?? 0
8893 }
8994 const isRequestSubscribed = ( queryCacheKey : string , requestId : string ) => {
9095 const subscriptions = getSubscriptions ( )
91- return ! ! subscriptions ?. [ queryCacheKey ] ?. [ requestId ]
96+ return ! ! subscriptions ?. get ( queryCacheKey ) ?. get ( requestId )
9297 }
9398
9499 const subscriptionSelectors : SubscriptionSelectors = {
@@ -97,6 +102,21 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
97102 isRequestSubscribed,
98103 }
99104
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+
100120 return (
101121 action ,
102122 mwApi ,
@@ -106,13 +126,14 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
106126 ] => {
107127 if ( ! previousSubscriptions ) {
108128 // Initialize it the first time this handler runs
109- previousSubscriptions = JSON . parse (
110- JSON . stringify ( internalState . currentSubscriptions ) ,
129+ previousSubscriptions = serializeSubscriptions (
130+ internalState . currentSubscriptions ,
111131 )
112132 }
113133
114134 if ( api . util . resetApiState . match ( action ) ) {
115- previousSubscriptions = internalState . currentSubscriptions = { }
135+ previousSubscriptions = { }
136+ internalState . currentSubscriptions . clear ( )
116137 updateSyncTimer = null
117138 return [ true , false ]
118139 }
@@ -133,6 +154,15 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
133154
134155 let actionShouldContinue = true
135156
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+
136166 if ( didMutate ) {
137167 if ( ! updateSyncTimer ) {
138168 // We only use the subscription state for the Redux DevTools at this point,
@@ -142,8 +172,8 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
142172 // In 1.9, it was updated in a microtask, but now we do it at most every 500ms.
143173 updateSyncTimer = setTimeout ( ( ) => {
144174 // 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 ,
147177 )
148178 // Figure out a smaller diff between original and current
149179 const [ , patches ] = produceWithPatches (
0 commit comments