text: import applyMixin from './mixin'
  import devtoolPlugin from './plugins/devtool'
  import ModuleCollection from './module/module-collection'
  import { forEachValue, isObject, isPromise, assert } from './util'
  
  let Vue // bind on install
  
  export class Store {
    constructor (options = {}) {
      if (process.env.NODE_ENV !== 'production') {
        assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
        assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
        assert(this instanceof Store, `Store must be called with the new operator.`)
      }
  
      const {
        plugins = [],
        strict = false
      } = options
  
      let {
        state = {}
      } = options
      if (typeof state === 'function') {
        state = state()
      }
  
      // store internal state
      this._committing = false
      this._actions = Object.create(null)
      this._mutations = Object.create(null)
      this._wrappedGetters = Object.create(null)
      this._modules = new ModuleCollection(options)
      this._modulesNamespaceMap = Object.create(null)
      this._subscribers = []
      this._watcherVM = new Vue()
  
      // bind commit and dispatch to self
      const store = this
      const { dispatch, commit } = this
      this.dispatch = function boundDispatch (type, payload) {
        return dispatch.call(store, type, payload)
      }
      this.commit = function boundCommit (type, payload, options) {
        return commit.call(store, type, payload, options)
      }
  
      // strict mode
      this.strict = strict
  
      // init root module.
      // this also recursively registers all sub-modules
      // and collects all module getters inside this._wrappedGetters
      installModule(this, state, [], this._modules.root)
  
      // initialize the store vm, which is responsible for the reactivity
      // (also registers _wrappedGetters as computed properties)
      resetStoreVM(this, state)
  
      // apply plugins
      plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
    }
  
    get state () {
      return this._vm._data.$$state
    }
  
    set state (v) {
      if (process.env.NODE_ENV !== 'production') {
        assert(false, `Use store.replaceState() to explicit replace store state.`)
      }
    }
  
    commit (_type, _payload, _options) {
      // check object-style commit
      const {
        type,
        payload,
        options
      } = unifyObjectStyle(_type, _payload, _options)
  
      const mutation = { type, payload }
      const entry = this._mutations[type]
      if (!entry) {
        if (process.env.NODE_ENV !== 'production') {
          console.error(`[vuex] unknown mutation type: ${type}`)
        }
        return
      }
      this._withCommit(() => {
        entry.forEach(function commitIterator (handler) {
          handler(payload)
        })
      })
      this._subscribers.forEach(sub => sub(mutation, this.state))
  
      if (
        process.env.NODE_ENV !== 'production' &&
        options && options.silent
      ) {
        console.warn(
          `[vuex] mutation type: ${type}. Silent option has been removed. ` +
          'Use the filter functionality in the vue-devtools'
        )
      }
    }
  
    dispatch (_type, _payload) {
      // check object-style dispatch
      const {
        type,
        payload
      } = unifyObjectStyle(_type, _payload)
  
      const entry = this._actions[type]
      if (!entry) {
        if (process.env.NODE_ENV !== 'production') {
          console.error(`[vuex] unknown action type: ${type}`)
        }
        return
      }
      return entry.length > 1
        ? Promise.all(entry.map(handler => handler(payload)))
        : entry[0](payload)
    }
  
    subscribe (fn) {
      const subs = this._subscribers
      if (subs.indexOf(fn) < 0) {
        subs.push(fn)
      }
      return () => {
        const i = subs.indexOf(fn)
        if (i > -1) {
          subs.splice(i, 1)
        }
      }
    }
  
    watch (getter, cb, options) {
      if (process.env.NODE_ENV !== 'production') {
        assert(typeof getter === 'function', `store.watch only accepts a function.`)
      }
      return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
    }
  
    replaceState (state) {
      this._withCommit(() => {
        this._vm._data.$$state = state
      })
    }
  
    registerModule (path, rawModule) {
      if (typeof path === 'string') path = [path]
  
      if (process.env.NODE_ENV !== 'production') {
        assert(Array.isArray(path), `module path must be a string or an Array.`)
        assert(path.length > 0, 'cannot register the root module by using registerModule.')
      }
  
      this._modules.register(path, rawModule)
      installModule(this, this.state, path, this._modules.get(path))
      // reset store to update getters...
      resetStoreVM(this, this.state)
    }
  
    unregisterModule (path) {
      if (typeof path === 'string') path = [path]
  
      if (process.env.NODE_ENV !== 'production') {
        assert(Array.isArray(path), `module path must be a string or an Array.`)
      }
  
      this._modules.unregister(path)
      this._withCommit(() => {
        const parentState = getNestedState(this.state, path.slice(0, -1))
        Vue.delete(parentState, path[path.length - 1])
      })
      resetStore(this)
    }
  
    hotUpdate (newOptions) {
      this._modules.update(newOptions)
      resetStore(this, true)
    }
  
    _withCommit (fn) {
      const committing = this._committing
      this._committing = true
      fn()
      this._committing = committing
    }
  }
  
  function resetStore (store, hot) {
    store._actions = Object.create(null)
    store._mutations = Object.create(null)
    store._wrappedGetters = Object.create(null)
    store._modulesNamespaceMap = Object.create(null)
    const state = store.state
    // init all modules
    installModule(store, state, [], store._modules.root, true)
    // reset vm
    resetStoreVM(store, state, hot)
  }
  
  function resetStoreVM (store, state, hot) {
    const oldVm = store._vm
  
    // bind store public getters
    store.getters = {}
    const wrappedGetters = store._wrappedGetters
    const computed = {}
    forEachValue(wrappedGetters, (fn, key) => {
      // use computed to leverage its lazy-caching mechanism
      computed[key] = () => fn(store)
      Object.defineProperty(store.getters, key, {
        get: () => store._vm[key],
        enumerable: true // for local getters
      })
    })
  
    // use a Vue instance to store the state tree
    // suppress warnings just in case the user has added
    // some funky global mixins
    const silent = Vue.config.silent
    Vue.config.silent = true
    store._vm = new Vue({
      data: {
        $$state: state
      },
      computed
    })
    Vue.config.silent = silent
  
    // enable strict mode for new vm
    if (store.strict) {
      enableStrictMode(store)
    }
  
    if (oldVm) {
      if (hot) {
        // dispatch changes in all subscribed watchers
        // to force getter re-evaluation for hot reloading.
        store._withCommit(() => {
          oldVm._data.$$state = null
        })
      }
      Vue.nextTick(() => oldVm.$destroy())
    }
  }
  
  function installModule (store, rootState, path, module, hot) {
    const isRoot = !path.length
    const namespace = store._modules.getNamespace(path)
  
    // register in namespace map
    if (module.namespaced) {
      store._modulesNamespaceMap[namespace] = module
    }
  
    // set state
    if (!isRoot && !hot) {
      const parentState = getNestedState(rootState, path.slice(0, -1))
      const moduleName = path[path.length - 1]
      store._withCommit(() => {
        Vue.set(parentState, moduleName, module.state)
      })
    }
  
    const local = module.context = makeLocalContext(store, namespace, path)
  
    module.forEachMutation((mutation, key) => {
      const namespacedType = namespace + key
      registerMutation(store, namespacedType, mutation, local)
    })
  
    module.forEachAction((action, key) => {
      const namespacedType = namespace + key
      registerAction(store, namespacedType, action, local)
    })
  
    module.forEachGetter((getter, key) => {
      const namespacedType = namespace + key
      registerGetter(store, namespacedType, getter, local)
    })
  
    module.forEachChild((child, key) => {
      installModule(store, rootState, path.concat(key), child, hot)
    })
  }
  
  /**
   * make localized dispatch, commit, getters and state
   * if there is no namespace, just use root ones
   */
  function makeLocalContext (store, namespace, path) {
    const noNamespace = namespace === ''
  
    const local = {
      dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
        const args = unifyObjectStyle(_type, _payload, _options)
        const { payload, options } = args
        let { type } = args
  
        if (!options || !options.root) {
          type = namespace + type
          if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
            console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
            return
          }
        }
  
        return store.dispatch(type, payload)
      },
  
      commit: noNamespace ? store.commit : (_type, _payload, _options) => {
        const args = unifyObjectStyle(_type, _payload, _options)
        const { payload, options } = args
        let { type } = args
  
        if (!options || !options.root) {
          type = namespace + type
          if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
            console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
            return
          }
        }
  
        store.commit(type, payload, options)
      }
    }
  
    // getters and state object must be gotten lazily
    // because they will be changed by vm update
    Object.defineProperties(local, {
      getters: {
        get: noNamespace
          ? () => store.getters
          : () => makeLocalGetters(store, namespace)
      },
      state: {
        get: () => getNestedState(store.state, path)
      }
    })
  
    return local
  }
  
  function makeLocalGetters (store, namespace) {
    const gettersProxy = {}
  
    const splitPos = namespace.length
    Object.keys(store.getters).forEach(type => {
      // skip if the target getter is not match this namespace
      if (type.slice(0, splitPos) !== namespace) return
  
      // extract local getter type
      const localType = type.slice(splitPos)
  
      // Add a port to the getters proxy.
      // Define as getter property because
      // we do not want to evaluate the getters in this time.
      Object.defineProperty(gettersProxy, localType, {
        get: () => store.getters[type],
        enumerable: true
      })
    })
  
    return gettersProxy
  }
  
  function registerMutation (store, type, handler, local) {
    const entry = store._mutations[type] || (store._mutations[type] = [])
    entry.push(function wrappedMutationHandler (payload) {
      handler(local.state, payload)
    })
  }
  
  function registerAction (store, type, handler, local) {
    const entry = store._actions[type] || (store._actions[type] = [])
    entry.push(function wrappedActionHandler (payload, cb) {
      let res = handler({
        dispatch: local.dispatch,
        commit: local.commit,
        getters: local.getters,
        state: local.state,
        rootGetters: store.getters,
        rootState: store.state
      }, payload, cb)
      if (!isPromise(res)) {
        res = Promise.resolve(res)
      }
      if (store._devtoolHook) {
        return res.catch(err => {
          store._devtoolHook.emit('vuex:error', err)
          throw err
        })
      } else {
        return res
      }
    })
  }
  
  function registerGetter (store, type, rawGetter, local) {
    if (store._wrappedGetters[type]) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] duplicate getter key: ${type}`)
      }
      return
    }
    store._wrappedGetters[type] = function wrappedGetter (store) {
      return rawGetter(
        local.state, // local state
        local.getters, // local getters
        store.state, // root state
        store.getters // root getters
      )
    }
  }
  
  function enableStrictMode (store) {
    store._vm.$watch(function () { return this._data.$$state }, () => {
      if (process.env.NODE_ENV !== 'production') {
        assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
      }
    }, { deep: true, sync: true })
  }
  
  function getNestedState (state, path) {
    return path.length
      ? path.reduce((state, key) => state[key], state)
      : state
  }
  
  function unifyObjectStyle (type, payload, options) {
    if (isObject(type) && type.type) {
      options = payload
      payload = type
      type = type.type
    }
  
    if (process.env.NODE_ENV !== 'production') {
      assert(typeof type === 'string', `Expects string as the type, but found ${typeof type}.`)
    }
  
    return { type, payload, options }
  }
  
  export function install (_Vue) {
    if (Vue) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(
          '[vuex] already installed. Vue.use(Vuex) should be called only once.'
        )
      }
      return
    }
    Vue = _Vue
    applyMixin(Vue)
  }
  
  // auto install in dist mode
  if (typeof window !== 'undefined' && window.Vue) {
    install(window.Vue)
  }
  