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 = Dispatch, >( middleware: Middleware, ): MiddlewareEntry => ({ 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 = Dispatch, >(): DynamicMiddlewareInstance => { const instanceId = nanoid() const middlewareMap = new Map< Middleware, MiddlewareEntry >() const withMiddleware = Object.assign( createAction( 'dynamicMiddleware/add', (...middlewares: Middleware[]) => ({ payload: middlewares, meta: { instanceId, }, }), ), { withTypes: () => withMiddleware }, ) as WithMiddleware const addMiddleware = Object.assign( function addMiddleware( ...middlewares: Middleware[] ) { middlewares.forEach((middleware) => { getOrInsertComputed(middlewareMap, middleware, createMiddlewareEntry) }) }, { withTypes: () => addMiddleware }, ) as AddMiddleware 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 = (api) => (next) => (action) => { if (isWithMiddleware(action)) { addMiddleware(...action.payload) return api.dispatch } return getFinalMiddleware(api)(next)(action) } return { middleware, addMiddleware, withMiddleware, instanceId, } }