Store.js

import Action from './Action.js'
import Dispatcher from './PromiseDispatcher.js'
import Vue from 'vue'

const StoreError = {
  DISPATCHER_NOT_INITIALIZED: 'Dispacher must be initialize fist\nBe sure to call .addStore before call dispatch',
  INVALID_ACTION: 'Action must be instance of Action or object-like type',
  INVALID_SUBSCRIBER: 'Subcribers must be functions'
}

/**
 * A Store is the basic state container,
 * it provides a reference to the global dispatcher,
 * the shared state  
 * and a way to dispatch and reduce actions.
 * 
 * It represent one stand-alone source of true.
 * 
 * *Remember*: It must be added to flue by calling [.addStore](SuperStore.html#addStore)
 *  or [.addStores](SuperStore.html#addStores)
 * @type {Object}
 */
class Store {
  /**
   * A name can be added to the store to make it referenceable from flue.
   * E.g
   * 
   * ```javascript
   * flue.addStore(new Store({ name: 'myStore' }))
   * // then it can be called from vue 
   * flue.refs.myStore
   * ```
   * At creation, the initial state can be passed to the Store directly.
   * @param {Object} config Configuration of the store 
   */
  constructor({ initialState = {}, name } = {}) {
    this.state = initialState
    this.name = name 
    this._dispatcher = null
    this.listeners = []
  }

  /**
   * Runtime initialization of the dispatcher
   * and the vm. At any point a store can become self-sufficient
   * by calling this function. The global dispatcher will be override
   * with the stores' local one and a Vue vm will be spawned to handle
   * the state reactivity
   */
  initialize() {
    this._dispatcher = new Dispatcher()    
    this._dispatcher.register(this.reduce)
    this.createVueVM()    
  }

   // start a Vue vm to make the state reactive
   createVueVM() {
    this.vm = new Vue({
      data: {
        state: this
      }
    })
  }

  /**
   * Get the current state
   * 
   * @return {Object} A copy of the current state
   */
  getState() {
    return JSON.parse(JSON.stringify(this.state))
  }

  /**
   * This function let the user subscribe to the state changing. 
   * Example: 
   * 
   * ```javascript
   * var unsubscribe = Store.subscribe(subscribeFunction)
   * unsubscribe()
   * ```
   * 
   * @param {Function} func A callback that will be called everytime the state is changed
   * @return {Function} A unsubscribe function.
   */
  subscribe(func) {
    if (typeof func != 'function')
      throw new Error(StoreError.INVALID_SUBSCRIBER)

    this.listeners.push(func)
    // return a unsubscribe function
    return () => this.listeners.splice(this.listeners.indexOf(func), 1)
  }

  notify() {
    this.listeners.forEach(listener => listener(this))
  }

  /**
   * This function is used to dispatch actions,
   * it is a wrapper around _dispatcher.dispatch
   * in order to provide more options to the client
   * @param  {Object} action Action instance, or an Action-like vanilla object
   */
  dispatch(action) {
    if (!this._dispatcher)
      throw new Error(StoreError.DISPATCHER_NOT_INITIALIZED)
    // nothing to dispatch
    if (!action)
      return
    // check if is an Action instance
    if (action instanceof Action) {
      return this._dispatcher.dispatch(action).then(() => this.notify())
    }
    // check if is a vanilla js object
    if (typeof action == 'object')
      // the type is required in order to identify the action
      if (action.type) {
        return this._dispatcher.dispatch(action).then(() => this.notify())
      }
    
    throw new Error(StoreError.INVALID_ACTION)
  }

  /**
   * Explicity create and dispatch an Action type
   * 
   * @param {String} type The type of the action
   * @param {Object} payload The payload of the action
   */
  dispatchAction(type, payload){
    this.dispatch(this.createAction(type,payload))
  }

  /**
   * This function can be called in order to create a Action instance
   * without have to import Action from Flue
   * 
   * @param {String} type The type of the action
   * @param {Object} payload The payload of the action
   * 
   * @return {Action} A newly created Action instance
   */
  createAction(type, payload) {
    return new Action(type, payload)
  }

  /**
   * Map each action to the correct function.
   * 
   * Example:
   * 
   * ```javascript
   * this.reduceMap(action, {
   *  ADD_FOOD_TO_SHOPPING_CART: this.addFoodToShoppingCart,
   *  REMOVE_FOOD_FROM_SHOPPING_CART: this.removeFoodFromShoppingCart,
   *  CHECK_OUT: this.checkOut
   * })
   * ```
   * 
   * @param  {Object} action            A action
   * @param  {Object} actionFunctionMap A dictionary with action type as key and function as value
   * @param {String} payloadKey        The key to access to the data part of an Action
   */
  reduceMap(action, actionFunctionMap, payloadKey = 'payload') {
    const func = actionFunctionMap[action.type]
    if (func && func.this !== this)
      func.bind(this)(action[payloadKey])
  }

  /**
   * The reduce function takes a action as input and reduce it
   * by switching behavior based on its type
   * 
   * @param {Object} action A action instance
   */
  reduce(action) {}
  /**
   * The store may expose a object of function that can be called
   * in order to dispatch Actions.
   * 
   * @param {Store} context The store itself is passed as context
   */
  actions(context) {}

}

export default Store