@reduxjs/toolkit
Version:
The official, opinionated, batteries-included toolset for efficient Redux development
94 lines (84 loc) • 2.68 kB
text/typescript
import type { Dispatch, Middleware, UnknownAction } from 'redux'
import { compose } from 'redux'
import { createAction } from '../createAction'
import { isAllOf } from '../matchers'
import { nanoid } from '../nanoid'
import { getOrInsertComputed } from '../utils'
import type {
AddMiddleware,
DynamicMiddleware,
DynamicMiddlewareInstance,
MiddlewareEntry,
WithMiddleware,
} from './types'
export type {
DynamicMiddlewareInstance,
GetDispatchType as GetDispatch,
MiddlewareApiConfig,
} from './types'
const createMiddlewareEntry = <
State = any,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
>(
middleware: Middleware<any, State, DispatchType>,
): MiddlewareEntry<State, DispatchType> => ({
middleware,
applied: new Map(),
})
const matchInstance =
(instanceId: string) =>
(action: any): action is { meta: { instanceId: string } } =>
action?.meta?.instanceId === instanceId
export const createDynamicMiddleware = <
State = any,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
>(): DynamicMiddlewareInstance<State, DispatchType> => {
const instanceId = nanoid()
const middlewareMap = new Map<
Middleware<any, State, DispatchType>,
MiddlewareEntry<State, DispatchType>
>()
const withMiddleware = Object.assign(
createAction(
'dynamicMiddleware/add',
(...middlewares: Middleware<any, State, DispatchType>[]) => ({
payload: middlewares,
meta: {
instanceId,
},
}),
),
{ withTypes: () => withMiddleware },
) as WithMiddleware<State, DispatchType>
const addMiddleware = Object.assign(
function addMiddleware(
...middlewares: Middleware<any, State, DispatchType>[]
) {
middlewares.forEach((middleware) => {
getOrInsertComputed(middlewareMap, middleware, createMiddlewareEntry)
})
},
{ withTypes: () => addMiddleware },
) as AddMiddleware<State, DispatchType>
const getFinalMiddleware: Middleware<{}, State, DispatchType> = (api) => {
const appliedMiddleware = Array.from(middlewareMap.values()).map((entry) =>
getOrInsertComputed(entry.applied, api, entry.middleware),
)
return compose(...appliedMiddleware)
}
const isWithMiddleware = isAllOf(withMiddleware, matchInstance(instanceId))
const middleware: DynamicMiddleware<State, DispatchType> =
(api) => (next) => (action) => {
if (isWithMiddleware(action)) {
addMiddleware(...action.payload)
return api.dispatch
}
return getFinalMiddleware(api)(next)(action)
}
return {
middleware,
addMiddleware,
withMiddleware,
instanceId,
}
}