import { Store , Module as VuexModule , GetterTree , MutationTree , ActionTree , } from 'vuex' ; import { Getters as BaseGetters , Mutations as BaseMutations , Actions as BaseActions , BA , BG , BM , } from './assets' ; import { assert , Class , mapValues , noop , combine , traverseDescriptors , error , deprecated , } from './utils' ; import { Context , Commit , Dispatch , createLazyContextPosition } from './context' ; import { ComponentMapper , MappedFunction , RestArgs } from './mapper' ; export interface ModuleOptions < S , G extends BG < S > , M extends BM < S > , A extends BA < S , G , M > , Modules extends Record < string , Module < any , any , any , any , any > > = { } > { namespaced ? : boolean ; state ? : Class < S > ; getters ? : Class < G > ; mutations ? : Class < M > ; actions ? : Class < A > ; modules ? : Modules ; } interface ModuleInstance { options : VuexModule < any , any > ; injectStore : ( store : Store < any > ) => void ; } export class Module < S , G extends BG < S > , M extends BM < S > , A extends BA < S , G , M > , Modules extends Record < string , Module < any , any , any , any , any > > = { } > { path : string [ ] | undefined ; namespace : string | undefined ; private mapper : ComponentMapper < S , G , M , A > = new ComponentMapper < S , G , M , A > ( createLazyContextPosition ( this ) ) ; constructor ( public options = { } ) { } clone ( ) { const options = { ... this . options } ; if ( options . modules ) { options . modules = mapValues ( options . modules , ( m ) => m . clone ( ) ) as Modules ; } return new Module ( options ) ; } context ( store ) { return new Context ( createLazyContextPosition ( this ) , store , this . options ) ; } mapState < K extends keyof S > ( map ) ; mapState < T extends Record < string , keyof S > > ( map ) ; mapState < T extends Record < string , ( state : S , getters : G ) => any > > ( map ) ; mapState ( map ) { deprecated ( '`Module#mapState` is deprecated. Use `createMapper` instead.' ) ; return this . mapper . mapState ( map ) ; } mapGetters < K extends keyof G > ( map ) ; mapGetters < T extends Record < string , keyof G > > ( map ) ; mapGetters ( map ) { deprecated ( '`Module#mapGetters` is deprecated. Use `createMapper` instead.' ) ; return this . mapper . mapGetters ( map ) ; } mapMutations < K extends keyof M > ( map ) ; mapMutations < T extends Record < string , keyof M > > ( map ) ; mapMutations < T extends Record < string , ( commit : Commit < M > , ... args : any [ ] ) => any > > ( map ) ; mapMutations ( map ) { deprecated ( '`Module#mapMutations` is deprecated. Use `createMapper` instead.' ) ; return this . mapper . mapMutations ( map ) ; } mapActions < K extends keyof A > ( map ) ; mapActions < T extends Record < string , keyof A > > ( map ) ; mapActions < T extends Record < string , ( dispatch : Dispatch < A > , ... args : any [ ] ) => any > > ( map ) ; mapActions ( map ) { deprecated ( '`Module#mapActions` is deprecated. Use `createMapper` instead.' ) ; return this . mapper . mapActions ( map ) ; } getStoreOptions ( ) { const injectStoreActionName = 'vuex-smart-module/injectStore' ; const { options , injectStore } = this . create ( [ ] , '' ) ; if ( ! options . actions ) { options . actions = { } ; } options . actions [ injectStoreActionName ] = function ( ) { injectStore ( this ) ; } ; const plugin = ( store ) => { store . dispatch ( injectStoreActionName ) ; const originalHotUpdate = store . hotUpdate ; store . hotUpdate = ( options ) => { originalHotUpdate . call ( store , options ) ; store . dispatch ( injectStoreActionName ) ; } ; } ; return { ... options , plugins : [ plugin ] , } ; } create ( path , namespace ) { assert ( ! this . path || this . path . join ( '.' ) === path . join ( '.' ) , 'You are reusing one module on multiple places in the same store.\n' + 'Clone it by `module.clone()` method to make sure every module in the store is unique.' ) ; this . path = path ; this . namespace = namespace ; const { namespaced = true , state , getters , mutations , actions , modules , } = this . options ; const children = ! modules ? undefined : Object . keys ( modules ) . reduce ( ( acc , key ) => { const m = modules [ key ] ; const nextNamespaced = m . options . namespaced ?? true ; const nextNamespaceKey = nextNamespaced ? key + '/' : '' ; const res = m . create ( path . concat ( key ) , namespaced ? namespace + nextNamespaceKey : nextNamespaceKey ) ; acc . options [ key ] = res . options ; acc . injectStore = combine ( acc . injectStore , res . injectStore ) ; return acc ; } , { options : { } as Record < string , VuexModule < any , any > > , injectStore : noop as ( store : Store < any > ) => void , } ) ; const gettersInstance = getters && initGetters ( getters , this ) ; const mutationsInstance = mutations && initMutations ( mutations , this ) ; const actionsInstance = actions && initActions ( actions , this ) ; return { options : { namespaced , state : ( ) => ( state ? new state ( ) : { } ) , getters : gettersInstance && gettersInstance . getters , mutations : mutationsInstance && mutationsInstance . mutations , actions : actionsInstance && actionsInstance . actions , modules : children && children . options , } , injectStore : combine ( children ? children . injectStore : noop , gettersInstance ? gettersInstance . injectStore : noop , mutationsInstance ? mutationsInstance . injectStore : noop , actionsInstance ? actionsInstance . injectStore : noop ) , } ; } } export function hotUpdate ( store , module ) { const { options , injectStore } = module . create ( [ ] , '' ) ; store . hotUpdate ( options ) ; injectStore ( store ) ; } function initGetters < S , G extends BG < S > , M extends BM < S > , A extends BA < S , G , M > , Modules extends Record < string , Module < any , any , any , any , any > > > ( Getters , module ) { const getters = new Getters ( ) ; const options = { } ; function proxyGetters ( getters , origin ) { const proxy = Object . create ( getters ) ; Object . keys ( options ) . forEach ( ( key ) => { Object . defineProperty ( proxy , key , { get ( ) { error ( `You are accessing ${ Getters . name }#${ key } from ${ Getters . name }#${ origin }` + ' but direct access to another getter is prohibitted.' + ` Access it via this.getters.${ key } instead.` ) ; return getters [ key ] ; } , configurable : true , } ) ; } ) ; return proxy ; } traverseDescriptors ( Getters . prototype , BaseGetters , ( desc , key ) => { if ( typeof desc . value !== 'function' && ! desc . get ) { return ; } const methodFn = desc . value ; const getterFn = desc . get ; options [ key ] = ( ) => { const proxy = process . env . NODE_ENV === 'production' ? getters : proxyGetters ( getters , key ) ; if ( getterFn ) { return getterFn . call ( proxy ) ; } if ( methodFn ) { return methodFn . bind ( proxy ) ; } } ; } , { constructor : true , $init : true , } ) ; return { getters : options , injectStore : ( store ) => { const context = module . context ( store ) ; Object . defineProperty ( getters , '__ctx__' , { get : ( ) => context , configurable : true , } ) ; getters . $init ( store ) ; } , } ; } function initMutations < S , G extends BG < S > , M extends BM < S > , A extends BA < S , G , M > , Modules extends Record < string , Module < any , any , any , any , any > > > ( Mutations , module ) { const mutations = new Mutations ( ) ; const options = { } ; function proxyMutations ( mutations , origin ) { const proxy = Object . create ( mutations ) ; Object . keys ( options ) . forEach ( ( key ) => { proxy [ key ] = ( ... args ) => { error ( `You are accessing ${ Mutations . name }#${ key } from ${ Mutations . name }#${ origin }` + ' but accessing another mutation is prohibitted.' + ' Use an action to consolidate the mutation chain.' ) ; mutations [ key ] . apply ( mutations , args ) ; } ; } ) ; return proxy ; } traverseDescriptors ( Mutations . prototype , BaseMutations , ( desc , key ) => { if ( typeof desc . value !== 'function' ) { return ; } options [ key ] = ( _ , payload ) => { const proxy = process . env . NODE_ENV === 'production' ? mutations : proxyMutations ( mutations , key ) ; return mutations [ key ] . call ( proxy , payload ) ; } ; } ) ; return { mutations : options , injectStore : ( store ) => { const context = module . context ( store ) ; Object . defineProperty ( mutations , '__ctx__' , { get : ( ) => context , configurable : true , } ) ; } , } ; } function initActions < S , G extends BG < S > , M extends BM < S > , A extends BA < S , G , M > , Modules extends Record < string , Module < any , any , any , any , any > > > ( Actions , module ) { const actions = new Actions ( ) ; const options = { } ; function proxyActions ( actions , origin ) { const proxy = Object . create ( actions ) ; Object . keys ( options ) . forEach ( ( key ) => { proxy [ key ] = ( ... args ) => { error ( `You are accessing ${ Actions . name }#${ key } from ${ Actions . name }#${ origin }` + ' but direct access to another action is prohibitted.' + ` Access it via this.dispatch('${ key }') instead.` ) ; actions [ key ] . apply ( actions , args ) ; } ; } ) ; return proxy ; } traverseDescriptors ( Actions . prototype , BaseActions , ( desc , key ) => { if ( typeof desc . value !== 'function' ) { return ; } options [ key ] = ( _ , payload ) => { const proxy = process . env . NODE_ENV === 'production' ? actions : proxyActions ( actions , key ) ; return actions [ key ] . call ( proxy , payload ) ; } ; } , { constructor : true , $init : true , } ) ; return { actions : options , injectStore : ( store ) => { const context = module . context ( store ) ; Object . defineProperty ( actions , '__ctx__' , { get : ( ) => context , configurable : true , } ) ; actions . $init ( store ) ; } , } ; }