Allow adding middlewares via a module (#8)
This commit is contained in:
Родитель
f65ab303b8
Коммит
9d9f2445b8
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"middlewares",
|
||||
"reset"
|
||||
]
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -48,6 +48,9 @@
|
|||
},
|
||||
"verbose": false,
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"testPathIgnorePatterns": [
|
||||
".*\\.d\\.ts"
|
||||
],
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
|
@ -61,4 +64,4 @@
|
|||
"@types/redux": "^3.6.0",
|
||||
"@types/redux-saga": "^0.10.5"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +1,71 @@
|
|||
import { AnyAction, ReducersMapObject, Store } from "redux";
|
||||
import { AnyAction, ReducersMapObject, Store, Middleware } from "redux";
|
||||
|
||||
/**
|
||||
* Represents a module which is set of reducers, sagas, inital actions and final actions
|
||||
* Represents a module which is set of reducers, sagas, initial actions and final actions
|
||||
*/
|
||||
export interface IModule<State> {
|
||||
/**
|
||||
* Id of the module
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Id of the module
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* Reducers for the module
|
||||
*/
|
||||
reducerMap?: ReducersMapObject<State, AnyAction>;
|
||||
/**
|
||||
* Reducers for the module
|
||||
*/
|
||||
reducerMap?: ReducersMapObject<State, AnyAction>;
|
||||
|
||||
/**
|
||||
* These sagas are executed immediatly after adding the module to the store (before dispatching initial actions)
|
||||
*/
|
||||
sagas?: ISagaRegistration<any>[];
|
||||
/**
|
||||
* Middlewares to add to the store
|
||||
*/
|
||||
middlewares?: Middleware[];
|
||||
|
||||
/**
|
||||
* These actions are dispatched immediatly after adding the module in the store
|
||||
*/
|
||||
initialActions?: AnyAction[];
|
||||
/**
|
||||
* These sagas are executed immediately after adding the module to the store (before dispatching initial actions)
|
||||
*/
|
||||
sagas?: ISagaRegistration<any>[];
|
||||
|
||||
/**
|
||||
* These actions are dispatched immediatly before removing the module from the store
|
||||
*/
|
||||
finalActions?: AnyAction[];
|
||||
/**
|
||||
* These actions are dispatched immediately after adding the module in the store
|
||||
*/
|
||||
initialActions?: AnyAction[];
|
||||
|
||||
/**
|
||||
* These actions are dispatched immediatly before removing the module from the store
|
||||
*/
|
||||
finalActions?: AnyAction[];
|
||||
}
|
||||
|
||||
export interface ISagaWithArguments<T> {
|
||||
saga: (argument?: T) => Iterator<any>;
|
||||
argument?: T;
|
||||
saga: (argument?: T) => Iterator<any>;
|
||||
argument?: T;
|
||||
}
|
||||
|
||||
export type ISagaRegistration<T> = (() => Iterator<any>) | ISagaWithArguments<T>;
|
||||
|
||||
export interface IDynamicallyAddedModule {
|
||||
/**
|
||||
* Call to remove the module from the store
|
||||
*/
|
||||
remove: () => void;
|
||||
/**
|
||||
* Call to remove the module from the store
|
||||
*/
|
||||
remove: () => void;
|
||||
}
|
||||
|
||||
export interface IModuleManager {
|
||||
/**
|
||||
* Add the given module to the store
|
||||
*/
|
||||
addModule: (...modules: IModule<any>[]) => IDynamicallyAddedModule
|
||||
/**
|
||||
* Add the given module to the store
|
||||
*/
|
||||
addModule: (...modules: IModule<any>[]) => IDynamicallyAddedModule
|
||||
}
|
||||
|
||||
export type IModuleStore<State> = Store<State> & IModuleManager & {
|
||||
/**
|
||||
* Remove all the modules from the store
|
||||
*/
|
||||
dispose: () => void;
|
||||
/**
|
||||
* Remove all the modules from the store
|
||||
*/
|
||||
dispose: () => void;
|
||||
};
|
||||
|
||||
export interface IItemManager<T> {
|
||||
getItems: () => T[];
|
||||
add: (items: T[]) => void;
|
||||
remove: (item: T[]) => void;
|
||||
getItems: () => T[];
|
||||
add: (items: T[]) => void;
|
||||
remove: (item: T[]) => void;
|
||||
dispose: () => void;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
//inspired from https://github.com/pofigizm/redux-dynamic-middlewares
|
||||
|
||||
import { compose, Middleware } from "redux";
|
||||
import { IItemManager } from "../Contracts";
|
||||
|
||||
export interface IDynamicMiddlewareManager extends IItemManager<Middleware> {
|
||||
dynamicMiddleware: Middleware;
|
||||
resetMiddlewares: () => void;
|
||||
}
|
||||
export const getMiddlewareManager = (): IDynamicMiddlewareManager => {
|
||||
let allDynamicMiddlewares: Middleware[] = [];
|
||||
|
||||
const dynamicMiddleware = store => next => (action) => {
|
||||
const chain: Function[] = allDynamicMiddlewares.map(m => m(store))
|
||||
|
||||
return compose<(action) => any>(...chain)(next)(action);
|
||||
}
|
||||
|
||||
const add = (middlewares: Middleware[]) => {
|
||||
allDynamicMiddlewares = [...allDynamicMiddlewares, ...middlewares];
|
||||
return middlewares;
|
||||
}
|
||||
|
||||
const remove = (middlewares: Middleware[]) => {
|
||||
middlewares.forEach(middleware => {
|
||||
const index = allDynamicMiddlewares.findIndex(d => d === middleware)
|
||||
|
||||
if (index === -1) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Middleware does not exist!', middleware)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
allDynamicMiddlewares = allDynamicMiddlewares.filter((_, mdwIndex) => mdwIndex !== index)
|
||||
});
|
||||
return middlewares;
|
||||
}
|
||||
|
||||
const resetMiddlewares = () => {
|
||||
allDynamicMiddlewares = []
|
||||
}
|
||||
|
||||
return {
|
||||
getItems: () => allDynamicMiddlewares,
|
||||
dynamicMiddleware,
|
||||
add,
|
||||
remove,
|
||||
resetMiddlewares,
|
||||
dispose: () => { allDynamicMiddlewares = [] }
|
||||
}
|
||||
}
|
|
@ -1,21 +1,25 @@
|
|||
import { IModule, ISagaRegistration, IItemManager } from "../Contracts";
|
||||
import { AnyAction, ReducersMapObject, Dispatch, Reducer } from "redux";
|
||||
import { getReducerManager, IReducerManager, getRefCountedReducerManager } from "./ReducerManager";
|
||||
import { getSagaManager } from "./SagaManager";
|
||||
import { AnyAction, Dispatch, Middleware, Reducer, ReducersMapObject } from "redux";
|
||||
import { SagaMiddleware } from "redux-saga";
|
||||
import { getRefCountedManager } from "./RefCountedManager";
|
||||
import { IItemManager, IModule, ISagaRegistration } from "../Contracts";
|
||||
import { equals as sagaEquals } from "../Utils/SagaComparer";
|
||||
import { getReducerManager, getRefCountedReducerManager, IReducerManager } from "./ReducerManager";
|
||||
import { getRefCountedManager } from "./RefCountedManager";
|
||||
import { getSagaManager } from "./SagaManager";
|
||||
|
||||
export interface IModuleManager<State> extends IItemManager<IModule<State>> {
|
||||
setDispatch: (dispatch: Dispatch<AnyAction>) => void;
|
||||
getReducer: (state: State, action: AnyAction) => State;
|
||||
}
|
||||
|
||||
export function getModuleManager<SagaContext, State>(sagaMiddleware: SagaMiddleware<SagaContext>): IModuleManager<State> {
|
||||
export function getModuleManager<SagaContext, State>(
|
||||
sagaMiddleware: SagaMiddleware<SagaContext>,
|
||||
middlewareManager: IItemManager<Middleware>): IModuleManager<State> {
|
||||
|
||||
let _dispatch = null;
|
||||
let _reducerManager: IReducerManager<State>;
|
||||
const _sagaManager: IItemManager<ISagaRegistration<any>> = getRefCountedManager(getSagaManager(sagaMiddleware), sagaEquals);
|
||||
const _addedModules: Set<any> = new Set();
|
||||
let modules: IModule<any>[] = [];
|
||||
const _moduleIds = new Set();
|
||||
|
||||
const _dispatchActions = (moduleId: string, actions: AnyAction[]) => {
|
||||
if (!actions) {
|
||||
|
@ -43,6 +47,20 @@ export function getModuleManager<SagaContext, State>(sagaMiddleware: SagaMiddlew
|
|||
_sagaManager.remove(sagas);
|
||||
}
|
||||
|
||||
const _addMiddlewares = (middlewares: Middleware[]) => {
|
||||
if (!middlewares) {
|
||||
return;
|
||||
}
|
||||
middlewareManager.add(middlewares);
|
||||
}
|
||||
|
||||
const _removeMiddlewares = (middlewares: Middleware[]) => {
|
||||
if (!middlewares) {
|
||||
return;
|
||||
}
|
||||
middlewareManager.remove(middlewares);
|
||||
}
|
||||
|
||||
const _addReducers = (reducerMap: ReducersMapObject<Reducer, AnyAction>) => {
|
||||
if (!reducerMap) {
|
||||
return;
|
||||
|
@ -72,7 +90,7 @@ export function getModuleManager<SagaContext, State>(sagaMiddleware: SagaMiddlew
|
|||
return (s || null);
|
||||
};
|
||||
|
||||
return {
|
||||
const moduleManager = {
|
||||
getReducer: _reduce,
|
||||
setDispatch: (dispatch: Dispatch<AnyAction>) => {
|
||||
_dispatch = dispatch;
|
||||
|
@ -86,13 +104,17 @@ export function getModuleManager<SagaContext, State>(sagaMiddleware: SagaMiddlew
|
|||
const justAddedModules: IModule<any>[] = [];
|
||||
|
||||
modulesToAdd.forEach(module => {
|
||||
if (!_addedModules.has(module.id)) {
|
||||
_addedModules.add(module.id);
|
||||
if (!_moduleIds.has(module.id)) {
|
||||
_moduleIds.add(module.id);
|
||||
_addReducers(module.reducerMap);
|
||||
const middlewares = module.middlewares;
|
||||
if (middlewares) {
|
||||
_addMiddlewares(middlewares);
|
||||
}
|
||||
justAddedModules.push(module);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
_dispatch && _dispatch({ type: "@@Internal/ModuleManager/ReducerAdded" });
|
||||
|
||||
// add the sagas and dispatch actions at the end so all the reducers are registered
|
||||
|
@ -110,16 +132,21 @@ export function getModuleManager<SagaContext, State>(sagaMiddleware: SagaMiddlew
|
|||
modulesToRemove = modulesToRemove.filter(module => module);
|
||||
modulesToRemove.forEach(module => {
|
||||
|
||||
if (_addedModules.has(module.id)) {
|
||||
if (_moduleIds.has(module.id)) {
|
||||
_dispatchActions(module.id, module.finalActions);
|
||||
|
||||
_removeReducers(module.reducerMap);
|
||||
_removeMiddlewares(module.middlewares);
|
||||
_removeSagas(module.sagas);
|
||||
_addedModules.delete(module.id);
|
||||
|
||||
_moduleIds.delete(module.id);
|
||||
modules = modules.filter(m => m.id !== module.id);
|
||||
_dispatch && _dispatch({ type: "@@Internal/ModuleManager/ModuleRemoved" });
|
||||
}
|
||||
});
|
||||
},
|
||||
dispose: () => {
|
||||
moduleManager.remove(modules);
|
||||
}
|
||||
};
|
||||
return moduleManager;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,8 @@ export function getSagaManager(sagaMiddleware: SagaMiddleware<any>): IItemManage
|
|||
task.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
dispose: () => { }
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { default as createSagaMiddleware, SagaMiddleware } from "redux-saga";
|
|||
import { IModule, IModuleStore } from "./Contracts";
|
||||
import { getModuleManager } from "./Managers/ModuleManager";
|
||||
import { getRefCountedManager } from "./Managers/RefCountedManager";
|
||||
import { getMiddlewareManager } from './Managers/MiddlewareManager';
|
||||
|
||||
/**
|
||||
* Configure the module store
|
||||
|
@ -27,8 +28,9 @@ export function configureStore<SagaContext, State>(initialState: DeepPartial<Sta
|
|||
);
|
||||
|
||||
const composeEnhancers = compose;
|
||||
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware));
|
||||
const modules = getRefCountedManager(getModuleManager<SagaContext, State>(sagaMiddleware), (a: IModule<any>, b: IModule<any>) => a.id === b.id);
|
||||
const middlewareManager = getRefCountedManager(getMiddlewareManager(), (a, b) => a === b);
|
||||
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware, middlewareManager.dynamicMiddleware));
|
||||
const modules = getRefCountedManager(getModuleManager<SagaContext, State>(sagaMiddleware, middlewareManager), (a: IModule<any>, b: IModule<any>) => a.id === b.id);
|
||||
|
||||
// Create store
|
||||
const store: IModuleStore<State> = createStore<State, any, {}, {}>(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { getModuleManager } from "../../Managers/ModuleManager";
|
||||
import { getMiddlewareManager } from '../../Managers/MiddlewareManager';
|
||||
function getSagaMiddleware(callback) {
|
||||
return {
|
||||
run: () => {
|
||||
|
@ -12,7 +13,8 @@ function getSagaMiddleware(callback) {
|
|||
it("module manager tests", () => {
|
||||
let taskCancellationCounter = 0;
|
||||
const taskCancelCallback = () => taskCancellationCounter++;
|
||||
const moduleManager = getModuleManager(getSagaMiddleware(taskCancelCallback) as any);
|
||||
const middlewareManager = getMiddlewareManager();
|
||||
const moduleManager = getModuleManager(getSagaMiddleware(taskCancelCallback) as any, middlewareManager);
|
||||
let actionsDispatched = [];
|
||||
|
||||
moduleManager.setDispatch((action) => {
|
||||
|
|
|
@ -5,7 +5,8 @@ it("ref counted manager tests", () => {
|
|||
const manager = {
|
||||
getItems: () => Array.from(items.keys()),
|
||||
add: (s: string[]) => s.length > 0 && s.forEach(s1 => items.add(s1)),
|
||||
remove: (s: string[]) => s.forEach(s1 => items.delete(s1))
|
||||
remove: (s: string[]) => s.forEach(s1 => items.delete(s1)),
|
||||
dispose: () => { }
|
||||
};
|
||||
|
||||
const refCounter = getRefCountedManager(manager, (a, b) => a === b);
|
||||
|
|
|
@ -6,3 +6,4 @@ export * from "./Utils/RefCounter";
|
|||
export * from "./Utils/Registry";
|
||||
export * from "./Utils/SagaComparer";
|
||||
export * from "./Managers/PluginManager";
|
||||
export * from "./Managers/MiddlewareManager";
|
||||
|
|
Загрузка…
Ссылка в новой задаче