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 { getOrInsert } 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 = getOrInsert (
52+ currentSubscriptions ,
53+ arg . queryCacheKey ,
54+ new Map ( ) ,
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 = getOrInsert (
72+ currentSubscriptions ,
73+ arg . queryCacheKey ,
74+ new Map ( ) ,
75+ )
76+ substate . set (
77+ requestId ,
78+ arg . subscriptionOptions ?? substate . get ( requestId ) ?? { } ,
79+ )
7580
7681 mutated = true
7782 }
@@ -83,12 +88,13 @@ 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 =
92+ subscriptions . get ( queryCacheKey ) ?? new Map ( )
93+ return subscriptionsForQueryArg . size
8894 }
8995 const isRequestSubscribed = ( queryCacheKey : string , requestId : string ) => {
9096 const subscriptions = getSubscriptions ( )
91- return ! ! subscriptions ?. [ queryCacheKey ] ?. [ requestId ]
97+ return ! ! subscriptions ?. get ( queryCacheKey ) ?. get ( requestId )
9298 }
9399
94100 const subscriptionSelectors : SubscriptionSelectors = {
@@ -97,6 +103,21 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
97103 isRequestSubscribed,
98104 }
99105
106+ function serializeSubscriptions (
107+ currentSubscriptions : SubscriptionInternalState ,
108+ ) : SubscriptionState {
109+ // We now use nested Maps for subscriptions, instead of
110+ // plain Records. Stringify this accordingly so we can
111+ // convert it to the shape we need for the store.
112+ return JSON . parse (
113+ JSON . stringify (
114+ Object . fromEntries (
115+ [ ...currentSubscriptions ] . map ( ( [ k , v ] ) => [ k , Object . fromEntries ( v ) ] ) ,
116+ ) ,
117+ ) ,
118+ )
119+ }
120+
100121 return (
101122 action ,
102123 mwApi ,
@@ -106,13 +127,14 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
106127 ] => {
107128 if ( ! previousSubscriptions ) {
108129 // Initialize it the first time this handler runs
109- previousSubscriptions = JSON . parse (
110- JSON . stringify ( internalState . currentSubscriptions ) ,
130+ previousSubscriptions = serializeSubscriptions (
131+ internalState . currentSubscriptions ,
111132 )
112133 }
113134
114135 if ( api . util . resetApiState . match ( action ) ) {
115- previousSubscriptions = internalState . currentSubscriptions = { }
136+ previousSubscriptions = { }
137+ internalState . currentSubscriptions . clear ( )
116138 updateSyncTimer = null
117139 return [ true , false ]
118140 }
@@ -142,8 +164,8 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder<
142164 // In 1.9, it was updated in a microtask, but now we do it at most every 500ms.
143165 updateSyncTimer = setTimeout ( ( ) => {
144166 // Deep clone the current subscription data
145- const newSubscriptions : SubscriptionState = JSON . parse (
146- JSON . stringify ( internalState . currentSubscriptions ) ,
167+ const newSubscriptions : SubscriptionState = serializeSubscriptions (
168+ internalState . currentSubscriptions ,
147169 )
148170 // Figure out a smaller diff between original and current
149171 const [ , patches ] = produceWithPatches (
0 commit comments