import { ModuleConfiguration, ModuleCreator, ModuleCreatorsMap, StateSnapshot, StonexModules } from '.' import { copy, isType, noop, types } from './helpers/base' import { convertToStandardModule, isPureModule } from './helpers/module' import { ActionModifier, Modifier, ModifiersWorker, ModuleModifier } from './ModifiersWorker' import { StateWorker } from './StateWorker' import { StonexModule } from './StonexModule' import { createStoreBinder } from './StoreBinder' /** * @typedef {Object} MP - Map of Stonex Modules class references */ export declare interface Store { modules: StonexModules getState: (moduleName: string) => State setState: ( moduleName: string, changes: ((() => Partial) | Partial), callback?: (state: State) => any ) => any resetState: (moduleName: string, callback?: (state: any) => any) => void connectModule: ( moduleName: string, data: ModuleCreator ) => StonexModule createStateSnapshot: () => StateSnapshot, storeId: number } export declare interface StoreConfiguration { stateWorker?: new (...args: any[]) => StateWorker, modifiers?: Array> } /** * Creates a new stonex store. * Combining all your stonex modules together and allows to use them in your application. * * @class StonexStore * @implements {Store} * @template MP * * @example * import SomeModule from './SomeModule' * * const store = new StonexStore({ * someModule: SomeModule * }) * * store.modules.someModule.doSomething() * * store.createStateSnapshot() */ class StonexStore implements Store { /** * Creates snapshot of state * * @param {MP} modules - Map with keys where key it is name of module and value it is class reference or object * * @static * @memberof StonexStore * * @returns {StateSnapshot} */ public static createStateSnapshot = (modules: MP): StateSnapshot => Object.keys(modules).reduce((state, name) => { state[name] = copy(modules[name].state) return state }, {}) as StateSnapshot /** * Unique identificator of store. * Usings inside Stonex Store. Don't change it! * * @type {number} * @memberof StonexStore */ public storeId: number = Math.round(Math.random() * Number.MAX_SAFE_INTEGER - Date.now()) /** * Map of the Stonex modules * * @type {StonexModules} * @memberof StonexStore */ public modules: StonexModules = {} as StonexModules /** * Set of methods which needed to work with module's state (updating, initializing, etc) * * Its can be overriden via StonexStore constructor: * @example * new StonexStore(modules, { stateWorker: OwnStateWorker }) * * @private * @type {StateWorker} * @memberof StonexStore */ private stateWorker: StateWorker /** * Creates an instance of StonexStore. * * @param {Partial>} modulesMap * @param {StoreConfiguration} storeConfiguration - have keys 'stateWorker', 'modifiers' * @memberof StonexStore */ constructor ( modulesMap: Partial>, { stateWorker = StateWorker, modifiers }: StoreConfiguration = {} ) { this.stateWorker = new stateWorker() const moduleModifiers = ModifiersWorker.getModuleModifiers(modifiers || [], this) for (const moduleName of Object.keys(modulesMap)) { this.connectModule( moduleName, modulesMap[moduleName], moduleModifiers ) } } /** * Create snapshot of the current store state * * @memberof StonexStore * @returns {StateSnapshot} */ public createStateSnapshot = (): StateSnapshot => StonexStore.createStateSnapshot(this.modules) // tslint:disable:max-line-length /** * Allows to attach stonex module to the store * * @template State * @param {string} moduleName - name of stonex module. This name will usings inside stonex store * @param {(ModuleCreator | ModuleConfiguration)} data - It can be: stonex module class reference, pure stonex module or ModuleConfiguration * @param {ModuleModifier[]} moduleModifiers - list of module modifiers (specific middleware) * @returns {StonexModule} * @memberof StonexStore * * * @example * yourStore.connectModule('moduleName', ModuleClass) * * yourStore.modules.moduleName.methodFromModuleClass() */ // tslint:enable:max-line-length public connectModule ( moduleName: string, data: ModuleCreator | ModuleConfiguration, moduleModifiers: ModuleModifier[] = [] ): StonexModule { const createDefaultStoreBinder = () => createStoreBinder(moduleName, this) const { module: Module, storeBinder = createDefaultStoreBinder() } = ( isType(data, types.function) || (isType(data, types.object) && typeof (data as ModuleConfiguration).module === 'undefined') ) ? { module: data as ModuleConfiguration['module'], storeBinder: createDefaultStoreBinder(), } : data as ModuleConfiguration const moduleInstance = new (isPureModule(Module) ? convertToStandardModule(Module) : Module)(storeBinder) const actionModifiers: ActionModifier[] = ModifiersWorker.getActionModifiers(moduleModifiers, moduleInstance) if (!moduleInstance.__STONEXMODULE__) { console.error(`${name} is not a Stonex Module` + '\r\n' + `To solve this you should extend your class ${name} from StonexModule class`) } moduleInstance.__initialState = copy(moduleInstance.state) this.stateWorker.initializeState(moduleInstance) ModifiersWorker.attachActionModifiersToModule(actionModifiers, moduleInstance) this.modules[moduleName] = moduleInstance return moduleInstance } /** * Update Stonex module state * * @param {string} moduleName - name of module * @param {*} changes - changes which need to apply to state of module * @param {function} callback - function which will been called when state has been changed * * @memberof StonexStore * @returns {void} */ public setState = ( moduleName: string, changes: Partial | ((state: State) => Partial), callback: (state: State) => any = noop ): void => this.stateWorker.setState(this.getModuleByName(moduleName), changes, callback) /** * Returns Stonex module state * * @param {string} moduleName * * @memberof StonexStore * @returns {*} */ public getState = (moduleName: string): any => this.stateWorker.getState(moduleName) /** * Reset state of the Stonex module to initial value (first value of module state) * * @param {string} moduleName - name of module * @param {function} callback - function which will been called when state has been cleared * * @memberof StonexStore * @returns {void} */ public resetState = (moduleName: string, callback: (state: any) => any = noop): void => this.stateWorker.resetState(this.getModuleByName(moduleName), callback) /** * Find module in Stonex store by name * * @private * @memberof StonexStore * * @returns {(StonexModule | never)} */ private getModuleByName = (moduleName: string): StonexModule | never => { const module = this.modules[moduleName] if (!module) { throw new Error(`Module with name ${moduleName} is not exist in your stonex store`) } return module } } export default StonexStore