repo_description: 🗃️ Centralized State Management for Vue.js.
repo_topics: javascript state-management time-travel vue vuex
code: import Module from './module'
  import { assert, forEachValue } from '../util'
  
  export default class ModuleCollection {
    constructor (rawRootModule) {
      // register root module (Vuex.Store options)
      this.register([], rawRootModule, false)
    }
  
    get (path) {
      return path.reduce((module, key) => {
        return module.getChild(key)
      }, this.root)
    }
  
    getNamespace (path) {
      let module = this.root
      return path.reduce((namespace, key) => {
        module = module.getChild(key)
        return namespace + (module.namespaced ? key + '/' : '')
      }, '')
    }
  
    update (rawRootModule) {
      update([], this.root, rawRootModule)
    }
  
    register (path, rawModule, runtime = true) {
      if (__DEV__) {
        assertRawModule(path, rawModule)
      }
  
      const newModule = new Module(rawModule, runtime)
      if (path.length === 0) {
        this.root = newModule
      } else {
        const parent = this.get(path.slice(0, -1))
        parent.addChild(path[path.length - 1], newModule)
      }
  
      // register nested modules
      if (rawModule.modules) {
        forEachValue(rawModule.modules, (rawChildModule, key) => {
          this.register(path.concat(key), rawChildModule, runtime)
        })
      }
    }
  
    unregister (path) {
      const parent = this.get(path.slice(0, -1))
      const key = path[path.length - 1]
      const child = parent.getChild(key)
  
      if (!child) {
        if (__DEV__) {
          console.warn(
            `[vuex] trying to unregister module '${key}', which is ` +
            `not registered`
          )
        }
        return
      }
  
      if (!child.runtime) {
        return
      }
  
      parent.removeChild(key)
    }
  
    isRegistered (path) {
      const parent = this.get(path.slice(0, -1))
      const key = path[path.length - 1]
  
      if (parent) {
        return parent.hasChild(key)
      }
  
      return false
    }
  }
  
  function update (path, targetModule, newModule) {
    if (__DEV__) {
      assertRawModule(path, newModule)
    }
  
    // update target module
    targetModule.update(newModule)
  
    // update nested modules
    if (newModule.modules) {
      for (const key in newModule.modules) {
        if (!targetModule.getChild(key)) {
          if (__DEV__) {
            console.warn(
              `[vuex] trying to add a new module '${key}' on hot reloading, ` +
              'manual reload is needed'
            )
          }
          return
        }
        update(
          path.concat(key),
          targetModule.getChild(key),
          newModule.modules[key]
        )
      }
    }
  }
  
  const functionAssert = {
    assert: value => typeof value === 'function',
    expected: 'function'
  }
  
  const objectAssert = {
    assert: value => typeof value === 'function' ||
      (typeof value === 'object' && typeof value.handler === 'function'),
    expected: 'function or object with "handler" function'
  }
  
  const assertTypes = {
    getters: functionAssert,
    mutations: functionAssert,
    actions: objectAssert
  }
  
  function assertRawModule (path, rawModule) {
    Object.keys(assertTypes).forEach(key => {
      if (!rawModule[key]) return
  
      const assertOptions = assertTypes[key]
  
      forEachValue(rawModule[key], (value, type) => {
        assert(
          assertOptions.assert(value),
          makeAssertionMessage(path, key, type, value, assertOptions.expected)
        )
      })
    })
  }
  
  function makeAssertionMessage (path, key, type, value, expected) {
    let buf = `${key} should be ${expected} but "${key}.${type}"`
    if (path.length > 0) {
      buf += ` in module "${path.join('.')}"`
    }
    buf += ` is ${JSON.stringify(value)}.`
    return buf
  }
  