93 lines
2.6 KiB
TypeScript
93 lines
2.6 KiB
TypeScript
import type { Dispatch, Middleware, UnknownAction } from 'redux'
|
|
import { compose } from '../reduxImports'
|
|
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,
|
|
}
|
|
}
|