JSPM

resolve-redux

0.1.0-alpha.55303bb4
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 747
  • Score
    100M100P100Q82497F
  • License MIT

This package serves as a helper for creating the Redux storage.

Package Exports

  • resolve-redux

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (resolve-redux) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

🔩 resolve-redux npm version

This package contains tools to integrate reSolve with Redux .

📑 Table of Contents

🛠 Utils

  • sendCommandMiddleware

    It is a Redux middleware used to send a command to the server side. It takes an object with the following field:

    • sendCommand - a function used to send a command to the server side. It takes command and returns the Promise object that will be resolved when the command is handled by the server. If the function is not specified, the command will be posted to /api/commands url.

    Example:

    import axios from 'axios'
    import { createStore, applyMiddleware } from 'redux'
    import { sendCommandMiddleware } from 'resolve-redux'
    import reducer from '../reducers'
    
    const middleware = [
      sendCommandMiddleware({
        sendCommand: async command => axios.post(`${process.env.ROOT_DIR}/api/commands`, command)
      })
    ]
    
    export default initialState => createStore(reducer, initialState, applyMiddleware(...middleware))
  • setSubscriptionMiddleware

    It is a Redux middleware used to get events from bus. It is used with actions.setSubscription to subscribe to required event types. It takes an object with the following field:

    • rootDirPath - URL where socket is placed. If URL is not specified, the process.env.ROOT_DIR value or an empty string will be used. The process.env.ROOT_DIR value is passed by resolve-scripts.

    Example:

    import axios from 'axios'
    import { createStore, applyMiddleware } from 'redux'
    import { setSubscriptionMiddleware } from 'resolve-redux'
    import reducer from '../reducers'
    
    const middleware = [
      setSubscriptionMiddleware({
        rootDirPath: '/my-path'
      })
    ]
    
    export default initialState => createStore(reducer, initialState, applyMiddleware(...middleware))
  • createReducer

    Generates a standard Redux reducer from a reSolve read model. It takes two arguments:

    • read-model - a reSolve read model to be converted to a Redux reducer.
    • extendReducer - another reducer to be combined with a new one.

    This reducer includes handling of the reSolve's merge and replaceState actions.

  • createActions

    Generates Redux actions from a reSolve aggregate. This function uses the reSolve's sendCommand action to pass a command from Redux to the server side. Generated actions are named as aggregate's commands. This function takes two arguments:

    • aggregate - reSolve aggregate.
    • extendActions - actions to extend or redefine resulting actions.
  • actions

    A plain object used to send special actions to be handled by other utils. It implements the following functions:

    • merge

      Produces an action handled by a reducer which the createReducer function generates. It takes two arguments:

      • readModelName - name of a read model whose state should be updated.
      • state - state to merge with the existing state of the specified read model.
    • sendCommand

      Sends a command to the server side. It takes the object with the following required arguments:

      • command
      • aggregateId
      • aggregateName
      • payload

      This action is handled by sendCommandMiddleware automatically.

    • setSubscription

      Subscribes to new events from the server side. This function takes two arguments:

      • eventTypes - array of event types
      • aggregateIds - array of aggregateId

      Returns an action handled by setSubscriptionMiddleware. It is useful to subscribe to new events from bus in real-time.

    • replaceState

      Produces an action handled by a reducer which the createReducer function generates. This function is very similar to merge, but the specified read model state is replaced with a new state instead of merging. This function takes two arguments:

      • readModelName - name of a read model whose state should be updated.
      • state - new state to replace the existing state of the specified read model.

💻 Basic Usage

How to Create Redux Store

import { createStore, applyMiddleware } from 'redux'
import axios from 'axios'
import {
  createReducer,
  sendCommandMiddleware,
  setSubscriptionMiddleware
} from 'resolve-redux'

const aggregate = {
  name: 'User',
  commands: {
    createUser: (state, { aggregateId, payload }) => ({
      type: 'UserCreated',
      aggregateId,
      payload
    }),
    removeUser: (state, { aggregateId }) => ({
      type: 'UserRemoved',
      aggregateId
    })
  }
}

const readModel = {
  name: 'Users',
  initialState: [],
  projection: {
    UserCreated(state, event) {
      return state.concat({
        ...event.payload,
        id: event.aggregateId
      })
    },
    UserRemoved(state, event) {
      return state.filter(item =>
        item.id !== event.aggregateId
      )
    }
  }
}

const reducer = createReducer(readModel)

const store = createStore(
  reducer,
  readModel.initialState,
  applyMiddleware(
    sendCommandMiddleware({
      sendCommand: command => axios.post('/api/commands', command)
    }),
    setSubscriptionMiddleware({
      rootDirPath: process.env.ROOT_DIR
    })
  )
)

How to Generate Action from Aggregate

import { createActions } from 'resolve-redux'
import { connect, bindActionCreators } from 'redux'

import App from './components/App'

export const aggregate {
  name: 'User',
  commands: {
    createUser: (state, { aggregateId, payload }) => ({
      type: 'UserCreated',
      aggregateId,
      payload
    })
  }
}

export const customActions = {
  deleteAll: () => ({
    type: 'DELETE_ALL'
  })
}

function mapDispatchToProps(dispatch) {
  actions: bindActionCreators(createActions(aggregate, customActions))
}

export default connect(() => {}, mapDispatchToProps)(App)

How to Send Commands to Server

import { actions } from 'resolve-redux';

export function sendCommandAddTodoItem(aggregateId) {
    return {
        type: 'SEND_COMMAND_ADD_TODO_ITEM',
        aggregateId,
        aggregateName: 'TodoList',
        payload: { name: 'todo-list' },
        command: {
            type: 'TodoListItemAdd',
        },
    };
}

store.dispatch(sendCommandAddTodoItem('aggregateId'));
// or
store.dispatch(actions.sendCommand({
    aggregateId: 'aggregateId',
    aggregateName: 'TodoList',
    payload: { name: 'todo-list' },
    command: {
        type: 'TodoListItemRemove',
    },
}));

🖥 Advanced Usage

Support for Optimistic Updates

const readModel = {
  name: 'TodoList',
  initialState: [],
  projection: {
    TodoListItemUpdateText(state, event) {
      return state.concat({
        ...event.payload,
        id: event.aggregateId
      })
    }
  }
}

const reducer = createReducer(readModel, (state, action) => {
  switch (action.type) {
    case 'SEND_COMMAND_TODO_UPDATE_TEXT': {
      if(!action.command.error) {
        // Optimistic update
        return state.map(item => {
          if(item.id === event.aggregateId) {
            return {
              ...item,
              text: action.payload.text,
              textBeforeOptimisticUpdate: item.text
            }
          }
        })
      } else {
        // Revert optimistic update
        return state.map(item => {
          if(item.id === event.aggregateId) {
            return {
              ...item,
              text: item.textBeforeOptimisticUpdate
            }
          }
        })
      }
    }
    default:
      return state
  }
})