createStore.js

import { createStore as createStoreVanilla } from 'redux'
import { __controllers } from './combineReducers'
import Controller, {
  __store,
  __mountPath,
  __mountPathString
} from './Controller'

/**
 * Wrapper around vanilla `createStore()` from `redux` package. Creates the
 * regular Redux store and extends it with `getController()` function.
 */
function createStore (reducers, preloadedState, enhancer) {
  const store = createStoreVanilla(reducers, preloadedState, enhancer)

  // Extract controller from reducers function property and
  // flatten them for path-based look up
  function _flattenControllers (thisControllers, thisPath) {
    const result = { }

    const controllerKeys = Object.keys(thisControllers)
    for (let i = 0; i < controllerKeys.length; ++i) {
      const key = controllerKeys[i]

      if (Controller.is(thisControllers[key])) {
        // Assume this is a Controller
        const controller = thisControllers[key]
        const mountPath = thisPath.concat(key)
        const mountPathString = mountPath.join('.')

        controller[__store] = store
        controller[__mountPath] = mountPath
        controller[__mountPathString] = mountPathString

        result[mountPathString] = thisControllers[key]
      } else {
        // Object with more nested controllers
        Object.assign(result,
            _flattenControllers(thisControllers[key], thisPath.concat(key)))
      }
    }

    return result
  }

  const controllersByPathString = reducers[__controllers]
      ? _flattenControllers(reducers[__controllers], [])
      : { }

  const controllersArray = Object.keys(controllersByPathString)
      .map(key => controllersByPathString[key])

  function getController (path) {
    if (Array.isArray(path)) {
      path = path.join('.')
    } else if (path === undefined) {
      return controllersByPathString
    }

    return controllersByPathString[path]
  }

  Object.assign(store, {
    getController
  })

  // Call Controller.afterCreateStore() hook
  for (let i = 0; i < controllersArray.length; ++i) {
    controllersArray[i].afterCreateStore()
  }

  return store
}

export default createStore

/**
 * Regular Redux store object extended with [getController()]{@link external:Store.getController}.
 * @external Store
 */

/**
 * A regular Redux store is extended with this function, if created with {@link createStore}.
 * @function external:Store.getController
 * @param {(string|Array.<string>)=} path Path to get {@link Controller} instance mounted at that
 *   path or `undefined`.
 * @returns Instance of {@link Controller}, `undefined` or the object containing all
 *   [controllers]{@link Controller} with mount path strings as the keys.
 * @instance
 */