import compose from '@tinkoff/utils/function/compose' ; import type { Dispatcher } from './dispatcher' ; import type { Event } from '../createEvent/createEvent.h' ; import type { StoreClass , StoreInstance } from '../typings' ; import type { Middleware } from './dispatcher.h' ; import { subscribe } from './storeSubscribe' ; import { SimpleEmitter } from '../stores/SimpleEmitter' ; import type { Reducer } from '../createReducer/createReducer.h' ; type InitialState = { stores : Record < string , any > ; } ; type Handlers = Dispatcher [ 'handlers' ] ; type StoreHandler = Handlers [ string ] [ number ] ; const eventExecutor = < P > ( event , handlerFns ) => { const keys = Object . keys ( handlerFns ) ; for ( let index = 0 ; index < keys . length ; index ++ ) { const storeName = keys [ index ] ; const handlerFn = handlerFns [ storeName ] ; if ( ! handlerFn ) { throw new Error ( `${ storeName } does not have a handler for action ${ event . type }` ) ; } handlerFn ( event . payload , event . type ) ; } } ; export const convertAction = ( actionOrNameEvent , payload ? ) => { if ( typeof actionOrNameEvent === 'string' ) { return { payload , type : actionOrNameEvent , error : false , } ; } return actionOrNameEvent ; } ; export class DispatcherContext < TContext > extends SimpleEmitter { dispatcher : Dispatcher ; storeInstances : Record < StoreClass [ 'storeName' ] , StoreInstance > ; storeUnsubscribeCallbacks : Record < StoreClass [ 'storeName' ] , Function > ; dispatcherInterface : { getContext : ( ) => TContext ; getStore : DispatcherContext < TContext > [ 'getStore' ] ; } ; rehydratedStoreState : Record < string , any > ; context : TContext ; private fullState : Record < string , any > ; applyDispatch = < Payload > ( event ) => { const eventHandlers = this . dispatcher . handlers [ event . type ] || [ ] ; if ( ! eventHandlers . length ) { return event . payload ; } this . applyHandlers ( event , eventHandlers ) ; return event . payload ; } ; param context constructor ( dispatcher , context , initialState , middlewares ? ) { super ( ) ; this . dispatcher = dispatcher ; this . storeInstances = { } ; this . storeUnsubscribeCallbacks = { } ; this . context = context ; this . dispatcherInterface = { getContext : ( ) => this . context , getStore : this . getStore . bind ( this ) , } ; this . rehydratedStoreState = { } ; this . fullState = { } ; if ( initialState ) { this . rehydrate ( initialState ) ; } if ( middlewares ) { this . applyDispatch = this . applyMiddlewares ( middlewares ) ; } Object . keys ( this . dispatcher . stores ) . forEach ( ( store ) => { this . getStore ( store ) ; } ) ; } applyMiddlewares ( middlewares ) { let dispatch = ( ... args ) => { throw new Error ( 'Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch.' ) ; } ; const api = { getState : this . getState . bind ( this ) , subscribe : this . subscribe . bind ( this ) , dispatch : ( ... args ) => dispatch ( ... args ) , } ; dispatch = compose ( ... middlewares . map ( ( middleware ) => middleware ( api ) ) ) ( this . applyDispatch ) ; return dispatch ; } deprecated param storeClass returns throws getStore < T extends StoreClass > ( storeClass ) ; getStore < T extends StoreClass > ( storeClass ) ; getStore < T extends StoreClass > ( storeClass ) ; getStore < T extends StoreClass > ( storeClass ) { let storeClassPrepared ; let optional = false ; if ( typeof storeClass === 'object' ) { storeClassPrepared = storeClass . store ; optional = storeClass . optional ; } else { storeClassPrepared = storeClass ; } const storeName = this . dispatcher . getStoreName ( storeClassPrepared ) ; if ( ! this . storeInstances [ storeName ] ) { let Store = this . dispatcher . stores [ storeName ] ; if ( ! Store ) { if ( typeof storeClassPrepared === 'function' ) { this . dispatcher . registerStore ( storeClassPrepared ) ; Store = storeClassPrepared ; } else { if ( optional ) { return null ; } throw new Error ( `Store ${ storeName } was not registered.` ) ; } } const storeInstance = new Store ( this . dispatcherInterface ) ; this . storeInstances [ storeName ] = storeInstance ; if ( this . rehydratedStoreState && this . rehydratedStoreState [ storeName ] ) { const state = this . rehydratedStoreState [ storeName ] ; if ( storeInstance . rehydrate ) { storeInstance . rehydrate ( state ) ; } this . rehydratedStoreState [ storeName ] = null ; } const subscribeHandler = ( ) => { const newState = storeInstance . getState ( ) ; if ( newState !== this . fullState [ storeName ] ) { this . fullState = { ... this . fullState , [ storeName ] : newState , } ; this . emit ( 'change' ) ; } } ; subscribe ( storeInstance , subscribeHandler ) ; const unsubscribe = ( ) => { storeInstance . off ( 'change' , subscribeHandler ) ; } ; this . storeUnsubscribeCallbacks [ storeName ] = unsubscribe ; if ( Store . dependencies ) { Store . dependencies . forEach ( ( dependencyStoreClass ) => { const dependency = this . getStore ( dependencyStoreClass ) ; subscribe ( dependency , ( ) => { storeInstance . emit ( 'change' ) ; } ) ; } ) ; } } return this . storeInstances [ storeName ] ; } param eventOrType param payload throws dispatch < Payload > ( eventOrType , payload ? ) { if ( ! eventOrType ) { throw new Error ( `eventOrType parameter ${ eventOrType } is invalid.` ) ; } const event = convertAction ( eventOrType , payload ) ; return this . applyDispatch ( event ) ; } applyHandlers < P > ( action , handlers ) { const handlerFns = { } ; const storeInstanceGet = ( store ) => { if ( handlerFns [ store . name ] ) { return ; } const storeInstance = this . getStore ( store . name ) ; if ( ! storeInstance ) { return ; } if ( typeof store . handler === 'function' ) { handlerFns [ store . name ] = store . handler . bind ( storeInstance ) ; } else { if ( process . env . NODE_ENV !== 'production' ) { if ( ! storeInstance [ store . handler ] ) { throw new Error ( `${ store . name } does not have a method called ${ store . handler }` ) ; } } handlerFns [ store . name ] = storeInstance [ store . handler ] . bind ( storeInstance ) ; } } ; const handlersLength = handlers . length ; for ( let index = 0 ; index < handlersLength ; index ++ ) { storeInstanceGet ( handlers [ index ] ) ; } return eventExecutor ( action , handlerFns ) ; } method returns dehydrate ( ) { const stores = { } ; const keys = Object . keys ( this . storeInstances ) ; for ( let i = 0 ; i < keys . length ; i ++ ) { const storeName = keys [ i ] ; const store = this . storeInstances [ storeName ] ; if ( ! store . dehydrate ( ) ) { continue ; } stores [ storeName ] = store . dehydrate ( ) ; } return { stores , } ; } method param dispatcherState rehydrate ( dispatcherState ) { if ( dispatcherState . stores ) { const keys = Object . keys ( dispatcherState . stores ) ; for ( let index = 0 ; index < keys . length ; index ++ ) { const storeName = keys [ index ] ; this . rehydratedStoreState [ storeName ] = dispatcherState . stores [ storeName ] ; } } } subscribe ( callback ) ; subscribe < S > ( reducer , callback ) ; subscribe ( ... args ) { if ( 'storeName' in args [ 0 ] ) { const reducer = args [ 0 ] ; const callback = args [ 1 ] ; const reducerInstance = this . storeInstances [ reducer . storeName ] ; const listener = ( ) => { const state = this . getState ( reducer ) ; callback ! ( state ) ; } ; reducerInstance . on ( 'change' , listener ) ; return ( ) => { reducerInstance . off ( 'change' , listener ) ; } ; } const callback = args [ 0 ] ; const listener = ( ) => { const state = this . getState ( ) ; callback ! ( state ) ; } ; this . on ( 'change' , listener ) ; return ( ) => { this . off ( 'change' , listener ) ; } ; } getState ( ) ; getState < S > ( reducer ) ; getState ( reducer ? ) { if ( reducer ) { return this . fullState [ reducer . storeName ] ; } return this . fullState ; } setContext ( context ) { this . context = context ; } hasStore ( store ) { return store . storeName in this . storeInstances && this . dispatcher . hasStore ( store ) ; } registerStore ( store ) { this . dispatcher . registerStore ( store ) ; this . getStore ( store ) ; } unregisterStore ( store ) { const { storeName } = store ; this . dispatcher . unregisterStore ( store ) ; this . storeUnsubscribeCallbacks [ storeName ] ( ) ; delete this . storeUnsubscribeCallbacks [ storeName ] ; delete this . rehydratedStoreState [ storeName ] ; delete this . storeInstances [ storeName ] ; } }