import { extractVuexModule , getNamespacedPath } from "./module" ; import { VuexModuleConstructor , Map , VuexModule , ProxyWatchers } from "./interfaces" ; import { getClassPath , toCamelCase , refineNamespacedPath } from "./utils" ; export function clearProxyCache < T extends typeof VuexModule > ( cls ) { } export function createProxy < T extends typeof VuexModule > ( $store , cls ) { const VuexClass = cls as VuexModuleConstructor ; if ( VuexClass . prototype . __vuex_proxy_cache__ ) { return VuexClass . prototype . __vuex_proxy_cache__ as InstanceType < T > & ProxyWatchers ; } const namespacedPath = VuexClass . prototype . __namespacedPath__ ? VuexClass . prototype . __namespacedPath__ + "/" : "" ; const proxy = _createProxy ( cls , $store , namespacedPath ) ; createLocalWatchers ( VuexClass , $store , namespacedPath || "" ) ; createLocalSubscriber ( VuexClass , $store , namespacedPath || "" ) ; createLocalSubscriberAction ( VuexClass , $store , namespacedPath || "" ) ; proxy . $watch = function ( field , callback , options ) { const getterNames = VuexClass . prototype . __explicit_getter_names__ ; if ( typeof field === "string" && getterNames . indexOf ( field ) > - 1 ) { return $store . watch ( ( ) => ( namespacedPath ? ( $store . rootGetters || $store . getters ) : $store . getters ) [ namespacedPath + field ] , callback , options ) ; } const className = cls . name . toLowerCase ( ) ; return $store . watch ( ( ) => ( namespacedPath ? ( $store . rootGetters || $store . getters ) : $store . getters ) [ namespacedPath + `__${ className }_internal_getter__` ] ( field ) , callback , options ) ; } ; proxy . $subscribe = function ( field , callback ) { return $store . subscribe ( ( mutation ) => { if ( mutation . type === namespacedPath + field ) { callback ( mutation . payload ) ; } } ) ; } ; proxy . $subscribeAction = function ( field , callback ) { if ( typeof callback === "function" ) { return $store . subscribeAction ( ( action ) => { if ( action . type === namespacedPath + field ) callback ( action . payload ) ; } ) ; } if ( typeof callback === "object" ) { return $store . subscribeAction ( { before ( action ) { if ( action . type === namespacedPath + field ) callback . before ( action . payload ) ; } , after ( action ) { if ( action . type === namespacedPath + field ) callback . after ( action . payload ) ; } } ) ; } } ; if ( VuexClass . prototype . __store_cache__ === undefined ) VuexClass . prototype . __store_cache__ = $store ; VuexClass . prototype . __vuex_proxy_cache__ = proxy ; return proxy as InstanceType < T > & ProxyWatchers ; } export function createLocalProxy < T extends typeof VuexModule > ( cls , $store ) { const VuexClass = cls as VuexModuleConstructor ; if ( VuexClass . prototype . __vuex_local_proxy_cache__ ) { return VuexClass . prototype . __vuex_local_proxy_cache__ as InstanceType < T > ; } const proxy = _createProxy ( cls , $store , "" ) ; VuexClass . prototype . __vuex_local_proxy_cache__ = proxy ; return proxy as InstanceType < T > ; } export function _createProxy < T extends typeof VuexModule > ( cls , $store , namespacedPath = "" ) { const VuexClass = cls as VuexModuleConstructor ; const proxy = { } ; const classPath = getClassPath ( VuexClass . prototype . __namespacedPath__ ) || toCamelCase ( VuexClass . name ) ; let { state , mutations , actions , getters , modules } = extractVuexModule ( VuexClass ) [ classPath ] ; if ( typeof state === "function" ) state = state ( ) ; createGettersAndMutationProxyFromState ( { cls : VuexClass , proxy , state , $store , namespacedPath } ) ; createGettersAndGetterMutationsProxy ( { cls : VuexClass , mutations , getters , proxy , $store , namespacedPath } ) ; createExplicitMutationsProxy ( VuexClass , proxy , $store , namespacedPath ) ; createActionProxy ( { cls : VuexClass , actions , proxy , $store , namespacedPath } ) ; createSubModuleProxy ( $store , VuexClass , proxy , modules ) ; return proxy as InstanceType < T > ; } function createLocalSubscriberAction ( cls , $store , namespacedPath ) { const subscriberIsEnabled = cls . prototype . __options__ && cls . prototype . __options__ . enableLocalWatchers ; if ( ! subscriberIsEnabled ) return ; const field = "$subscribeAction" ; const subscriptionMap = cls [ field ] ; if ( subscriptionMap === undefined ) return ; for ( let field in subscriptionMap ) { const watcher = subscriptionMap [ field ] ; if ( typeof watcher === "function" ) { $store . subscribeAction ( ( action ) => { if ( action . type === namespacedPath + field ) { const proxiedFunction = ( payload ) => watcher . call ( cls . prototype . __vuex_proxy_cache__ , payload ) ; proxiedFunction ( action . payload ) ; } } ) ; continue ; } if ( typeof watcher === "object" ) { $store . subscribeAction ( { before : ( action ) => { if ( action . type === namespacedPath + field && watcher . before ) { const proxiedFunc = ( payload ) => watcher . before . call ( cls . prototype . __vuex_proxy_cache__ , payload ) ; proxiedFunc ( action . payload ) ; } } , after : ( action ) => { if ( action . type === namespacedPath + field && watcher . after ) { const proxiedFunc = ( payload ) => watcher . after . call ( cls . prototype . __vuex_proxy_cache__ , payload ) ; proxiedFunc ( action . payload ) ; } } } ) ; } } } function createLocalSubscriber ( cls , $store , namespacedPath ) { const subscriberIsEnabled = cls . prototype . __options__ && cls . prototype . __options__ . enableLocalWatchers ; if ( ! subscriberIsEnabled ) return ; const field = "$subscribe" ; const subscriptionMap = cls [ field ] ; if ( subscriptionMap === undefined ) return ; $store . subscribe ( ( mutation , state ) => { for ( let field in subscriptionMap ) { const subscribeFunc = subscriptionMap [ field ] ; const proxiedFunc = ( payload ) => subscribeFunc . call ( cls . prototype . __vuex_proxy_cache__ , payload ) ; if ( mutation . type === namespacedPath + field ) { proxiedFunc ( mutation . payload ) ; } } } ) ; } function createLocalWatchers ( cls , $store , namespacedPath ) { const watcherIsEnabled = cls . prototype . __options__ && cls . prototype . __options__ . enableLocalWatchers ; if ( ! watcherIsEnabled ) return ; const field = "$watch" ; const watchMap = cls [ field ] ; if ( watchMap === undefined ) return ; const getterNames = cls . prototype . __explicit_getter_names__ ; const className = cls . name . toLowerCase ( ) ; for ( let field in watchMap ) { const fieldIsAnExplicitGetter = getterNames . indexOf ( field ) > - 1 ; const watchFunc = watchMap [ field ] ; const proxiedWatchFunc = function ( newVal , oldVal ) { return watchFunc . call ( cls . prototype . __vuex_proxy_cache__ , newVal , oldVal ) ; } ; if ( fieldIsAnExplicitGetter ) { $store . watch ( ( ) => ( namespacedPath ? ( $store . rootGetters || $store . getters ) : $store . getters ) [ namespacedPath + field ] , proxiedWatchFunc ) ; } else { $store . watch ( ( ) => ( namespacedPath ? ( $store . rootGetters || $store . getters ) : $store . getters ) [ namespacedPath + `__${ className }_internal_getter__` ] ( field ) , proxiedWatchFunc ) ; } } } function createSubModuleProxy ( $store , cls , proxy , modules ) { const store = cls . prototype . __store_cache__ || $store ; for ( let field in modules ) { const subModuleClass = cls . prototype . __submodules_cache__ [ field ] as VuexModuleConstructor ; const namespacedPath = getNamespacedPath ( subModuleClass ) ; subModuleClass . prototype . __namespacedPath__ = cls . prototype . __namespacedPath__ + "/" + namespacedPath ; proxy [ field ] = createProxy ( store , subModuleClass ) ; } } function createGettersAndMutationProxyFromState ( { cls , proxy , state , $store , namespacedPath = "" , currentField = "" , maxDepth = 1 } ) { /**
     * 1. Go through all fields in the object and check the values of those fields.
     *
     *  1.1.  If the value of the field is not an object.
     *    1.1.1   Define a getter that returns that value
     *            and a setter that calls a mutation commit on that value and move to the next field.
     *
     *  1.2.  If the value of the field is an object,
     *    1.2.1   Define a getter that returns that value
     *            and a setter that calls a mutation commit on that value.
     *    1.2.2.  Go back to STEP 1.
     */ const className = cls . name . toLowerCase ( ) ; namespacedPath = refineNamespacedPath ( namespacedPath ) ; const strict = cls . prototype . __options__ && cls . prototype . __options__ . strict ; const submoduleNames = Object . keys ( cls . prototype . __submodules_cache__ ) ; for ( let field in state ) { const fieldIsSubmodule = submoduleNames . indexOf ( field ) > - 1 ; let value = state [ field ] ; if ( currentField . length && ! currentField . endsWith ( "." ) ) currentField += "." ; const path = currentField + field ; if ( maxDepth === 0 || typeof value !== "object" || ( typeof value === 'object' && ! fieldIsSubmodule ) ) { if ( ! strict || fieldIsSubmodule ) { Object . defineProperty ( proxy , field , { get : ( ) => { const getters = cls . prototype . __namespacedPath__ ? ( $store . rootGetters || $store . getters ) : $store . getters ; if ( getters ) { const getterPath = refineNamespacedPath ( cls . prototype . __namespacedPath__ ) + `__${ className }_internal_getter__` ; return getters [ getterPath ] ( path ) ; } else return $store [ `__${ className }_internal_getter__` ] ( path ) ; } , set : payload => { const commit = $store . commit || cls . prototype . __store_cache__ . commit ; if ( commit ) commit ( refineNamespacedPath ( cls . prototype . __namespacedPath__ ) + `__${ className }_internal_mutator__` , { field : path , payload } , { root : true } ) ; else { const store = cls . prototype . __context_store__ ! ; store . commit ( `__${ className }_internal_mutator__` , { field : path , payload } , { root : true } ) ; } } , } ) ; } else { Object . defineProperty ( proxy , field , { get : ( ) => { if ( $store . getters ) { return $store . getters [ namespacedPath + `__${ className }_internal_getter__` ] ( path ) ; } else return $store [ `__${ className }_internal_getter__` ] ( path ) ; } , } ) ; } continue ; } proxy [ field ] = { } ; createGettersAndMutationProxyFromState ( { cls , proxy : proxy [ field ] , state : value , $store , namespacedPath , currentField : currentField + field , maxDepth : maxDepth - 1 , } ) ; } return proxy ; } function __createGettersAndMutationProxyFromState ( { cls , proxy , state , $store , namespacedPath = "" } ) { const className = cls . name . toLowerCase ( ) ; namespacedPath = refineNamespacedPath ( namespacedPath ) ; const strict = cls . prototype . __options__ && cls . prototype . __options__ . strict ; const submoduleNames = Object . keys ( cls . prototype . __submodules_cache__ ) ; for ( let field in state ) { const fieldIsSubmodule = submoduleNames . indexOf ( field ) > - 1 ; if ( ! strict || fieldIsSubmodule ) { Object . defineProperty ( proxy , field , { get : ( ) => { if ( $store . getters ) { return $store . getters [ namespacedPath + `__${ className }_internal_getter__` ] ( field ) ; } else return $store [ `__${ className }_internal_getter__` ] ( field ) ; } , set : payload => { if ( $store . commit ) $store . commit ( namespacedPath + `__${ className }_internal_mutator__` , { field , payload } ) ; else { const store = cls . prototype . __context_store__ ! ; store . commit ( `__${ className }_internal_mutator__` , { field , payload } , { root : true } ) ; } } , } ) ; } else { Object . defineProperty ( proxy , field , { get : ( ) => { if ( $store . getters ) { return $store . getters [ namespacedPath + `__${ className }_internal_getter__` ] ( field ) ; } else return $store [ `__${ className }_internal_getter__` ] ( field ) ; } , } ) ; } } return proxy ; } function createExplicitMutationsProxy ( cls , proxy , $store , namespacedPath ) { const mutations = cls . prototype . __mutations_cache__ && cls . prototype . __mutations_cache__ . __explicit_mutations__ || { } ; const commit = cls . prototype . __store_cache__ ? cls . prototype . __store_cache__ . commit : $store . commit ; namespacedPath = refineNamespacedPath ( cls . prototype . __namespacedPath__ . length ? cls . prototype . __namespacedPath__ + "/" : namespacedPath ) ; for ( let field in mutations ) { proxy [ field ] = ( payload ) => commit ( namespacedPath + field , payload , { root : true } ) ; } } function createGettersAndGetterMutationsProxy ( { cls , getters , mutations , proxy , $store , namespacedPath } ) { const getterMutations = Object . keys ( cls . prototype . __mutations_cache__ && cls . prototype . __mutations_cache__ . __setter_mutations__ || { } ) ; const className = cls . name . toLowerCase ( ) ; if ( $store && $store [ `__${ className }_internal_getter__` ] ) { $store [ `__${ className }_internal_mutator__` ] = mutations [ `__${ className }_internal_mutator__` ] ; } namespacedPath = refineNamespacedPath ( namespacedPath ) ; for ( let field in getters ) { if ( $store === undefined || proxy [ field ] ) continue ; const fieldHasGetterAndMutation = getterMutations . indexOf ( field ) > - 1 ; if ( fieldHasGetterAndMutation ) { Object . defineProperty ( proxy , field , { get : ( ) => { const storeGetters = namespacedPath ? ( $store . rootGetters || $store . getters ) : $store . getters ; if ( storeGetters ) return storeGetters [ namespacedPath + field ] ; else return $store [ namespacedPath + field ] ; } , set : ( payload ) => $store . commit ( namespacedPath + field , payload , { root : ! ! namespacedPath } ) , } ) ; continue ; } if ( Object . prototype . hasOwnProperty . call ( proxy , field ) ) continue ; Object . defineProperty ( proxy , field , { get : ( ) => { const storeGetters = namespacedPath ? ( $store . rootGetters || $store . getters ) : $store . getters ; if ( storeGetters ) return storeGetters [ namespacedPath + field ] ; else return $store [ namespacedPath + field ] ; } } ) ; } } function createActionProxy ( { cls , actions , proxy , $store , namespacedPath } ) { const dispatch = cls . prototype . __store_cache__ ? cls . prototype . __store_cache__ . dispatch : $store . dispatch ; namespacedPath = refineNamespacedPath ( cls . prototype . __namespacedPath__ . length ? cls . prototype . __namespacedPath__ + "/" : namespacedPath ) ; for ( let field in actions ) { proxy [ field ] = function ( payload ) { return dispatch ( namespacedPath + field , payload ) ; } ; } } interface ProxyCreator { proxy : Map ; $store : any ; namespacedPath : string ; } interface MutationProxyCreator extends ProxyCreator { cls : VuexModuleConstructor ; } interface GetterProxyCreator extends MutationProxyCreator { getters : Map ; mutations : Map ; } interface ActionProxyCreator extends MutationProxyCreator { actions : Map ; }