diff --git a/.babelrc b/.babelrc
index 7ea24c1..741aa4d 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,8 @@
{
"ignore": [],
+ "plugins": [
+ "dev-expression"
+ ],
"env": {
"test": {
"presets": [
diff --git a/README.md b/README.md
index bf5fdd8..83abfec 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,16 @@
Helpers for scaling and abstracting redux by co-locating actions, reducers and selectors.
-[](https://travis-ci.org/thomasdashney/redux-modular) [](https://codeclimate.com/github/thomasdashney/redux-modular/coverage) [](https://codeclimate.com/github/thomasdashney/redux-modular)
+[](https://travis-ci.org/thomasdashney/redux-modular) [](https://codeclimate.com/github/thomasdashney/redux-modular/coverage) [](https://codeclimate.com/github/thomasdashney/redux-modular)
+
+* [Install](#install)
+* [Usage Guide](#usage-guide)
+ * [Defining actions](#defining-actions)
+ * [Defining reducers](#defining-reducers)
+ * [Defining selectors](#defining-selectors)
+ * [Defining reusable redux logic](#defining-reusable-redux-logic)
+ * [Writing tests](#writing-tests)
+* [API](#api)
## Install
@@ -16,96 +25,299 @@ or
$ yarn add redux-modular
```
-## Usage
+## Usage Guide
+
+This guide uses a counter as an example, which starts at 0 and ends at 10. If you try to `increment` past the 10, it will stay at 10.
+
+Two selectors are provided: `value` which gets the current value of the counter, and `isComplete`, which return `true` if the counter has reached 10 (the maximum). Finally, there is a `reset` action for resetting back to the initial state of `0`.
-
-
-
+Here is how one might implement this using plain redux:
```js
-import { combineReducers, createStore } from 'redux'
-import { mount, createReducer } from 'redux-modular'
-
-/* Create an object containing the logic (actions, reducer, selectors) */
-
-const counter = {
- // mapping of action names to optional payload creators
- actions: {
- increment: null,
- decrement: null,
- set: (value) => ({ value })
- },
-
- // function mapping actions to reducers
- reducer: actions => createReducer(0, {
- [actions.increment]: state => state + 1,
- [actions.decrement]: state => state - 1,
- [actions.set]: (state, payload) => payload.value
- }),
-
- // function mapping local state selector to your selectors
- selectors: localStateSelector => ({
- counterValue: state => localStateSelector(state)
- })
+import { createStore, combineReducers } from 'redux'
+
+const INITIAL_STATE = 0
+const COUNTER_MAX = 10
+
+const COUNTER_TYPES = {
+ INCREMENT: 'increment (counter)',
+ RESET: 'reset (counter)'
}
-/* Instantiate the counter logic by mounting to redux paths */
+const counterActions = {
+ increment: (amount = 1) => ({ type: COUNTER_TYPES.INCREMENT, payload: { amount } }),
+ reset: () => ({ type: COUNTER_TYPES.RESET })
+}
+
+const counterReducer = (state = INITIAL_STATE, action) => {
+ switch (action.type) {
+ case COUNTER_TYPES.INCREMENT:
+ return Math.min(state + action.payload.amount, COUNTER_MAX)
+ case COUNTER_TYPES.RESET:
+ return INITIAL_STATE
+ default:
+ return state
+ }
+}
-const counter1 = mount('counter1', counter)
-const counter2 = mount('counter2', counter)
-const counter3 = mount(['nested', 'counter3'], counter)
+const counterSelectors = {
+ value: state => state.counter,
+ isComplete: state => state.counter === COUNTER_MAX
+}
-/* Add the reducers to your root reducer */
+// then the reducer would be mounted to the store at `state.counter`:
-const rootReducer = combineReducers({
- counter1: counter1.reducer,
- counter2: counter2.reducer,
- nested: combineReducers({
- counter3: counter3.reducer
+const store = createStore(
+ combineReducers({
+ counter: counterReducer
})
+)
+
+counterSelectors.value(store.getState()) // 0
+counterSelectors.isComplete(store.getState()) // true
+
+store.dispatch(counterActions.increment(10))
+counterSelectors.value(store.getState()) // 10
+counterSelectors.isComplete(store.getState()) // true
+
+store.dispatch(counterActions.reset())
+counterSelectors.value(store.getState()) // 0
+counterSelectors.isComplete(store.getState()) // false
+```
+
+Each section in this guide shows how each `redux-modular` helper function can be used to reduce code repetition and boilerplate.
+
+### Defining actions
+
+Using `createAction`, we can easily define `FSA-Compliant` action creator:
+
+```js
+import { createAction } from 'redux-modular'
+
+const counterActions = {
+ increment: createAction(COUNTER_TYPES.INCREMENT, (amount = 1) => ({ amount })),
+ reset: createAction(COUNTER_TYPES.RESET)
+}
+```
+
+`createAction` also does some extra work for us. If you call `toString()` on any of these action creators, it will return the action's type. This allows us to remove our `COUNTER_TYPES` constant declaration:
+
+```js
+import { createAction } from 'redux-modular'
+
+const counterActions = {
+ increment: createAction('increment (counter)', (amount = 1) => ({ amount })),
+ reset: createAction('reset (counter)')
+}
+
+counterActions.increment.toString() // 'increment (counter)'
+```
+
+Next, we can remove the repetition of namespacing our actions with the `(counter)` suffix. The `mountAction` helper will modify an action creator's action type using a given namespace:
+
+```js
+import { createAction, mountAction } from 'redux-modular'
+
+const namespace = 'counter'
+
+const counterActions = {
+ increment: mountAction(namespace, createAction('increment', (amount = 1) => ({ amount })))
+ reset: mountAction(createAction('reset')
+})
+
+counterActions.increment.toString() // 'increment (counter)'
+```
+
+Finally, we can use the `createActions` and `mountActions` helpers to reduce all of the above boilerplate:
+
+```js
+import { createActions, mountActions } from 'redux-modular'
+
+const counterActions = mountActions('counter', createActions({
+ increment: (amount = 1) => ({ amount }),
+ reset
+}))
+```
+
+### Defining reducers
+
+`createReducer` creates a reducer which switches based on action type, and passes the action `payload` directly to your sub-reducer function:
+
+```js
+import { createReducer } from 'redux-modular'
+
+const INITIAL_STATE = 0
+const COUNTER_MAX = 10
+
+const counterReducer = createReducer(INITIAL_STATE, {
+ [counterActions.increment]: (state, payload) => Math.min(state + payload.amount, COUNTER_MAX),
+ [counterActions.reset]: () => INITIAL_STATE
})
+```
+
+Note that, because we passed the action creators directly as the object keys, the `toString()` function will be called on them automatically.
+
+### Defining selectors
-const store = createStore(rootReducer)
+`mountSelector` will wrap a selector with a selector which first selects the state at a provided path:
-/* Use actions and selectors for each counter instance in your app */
+```js
+const isCompleteSelector = mountSelector('counter', counterState => counterState === COUNTER_MAX)
+
+isCompleteSelector({ counter: 5 }) // 5
+```
-const { actions, selectors } = counter1
+`mountSelectors` allows you to mount multiple selectors at once:
-console.log(selectors.counterValue(store.getState())) // prints `0`
+```js
+const counterSelectors = mountSelectors('counter', {
+ value: counterState => counterState,
+ isComplete: counterState => counterState === COUNTER_MAX
+})
+```
-store.dispatch(actions.increment())
-console.log(selectors.counterValue(store.getState())) // prints `1`
+If our logic lives multiple levels deep in the redux state tree, you can use [lodash.get](https://lodash.com/docs/4.17.10#get) syntax to perform a deep select:
-store.dispatch(actions.decrement())
-console.log(selectors.counterValue(store.getState())) // prints `0`
+```js
+const counterSelectors = mountSelectors('path.counter', {
+ value: counterState => counterState,
+ isComplete: counterState => counterState === COUNTER_MAX
+})
-store.dispatch(actions.set(5))
-console.log(selectors.counterValue(store.getState())) // prints `5`
+counterSelectors.value({ nested: { counter: 5 } }) // 5
```
-## Writing Tests
+### Defining reusable redux logic
-If you `mount` your logic to a path of `null`, you can test your state logic without any assumption of where it sits in your redux state.
+Putting the above examples together, we have reduced much boilerplate & repetition:
```js
-/* eslint-env jest */
+import { createActions, mountActions, createReducer, mountSelectors } from 'redux-modular'
-const counter = require('./counter')
+const REDUX_PATH = 'counter'
+const INITIAL_STATE = 0
+const COUNTER_MAX = 10
-const { actions, reducer, selectors } = mount(null, counter)
+const counterActions = mountActions(REDUX_PATH, createActions({
+ increment: (amount = 1) => ({ amount }),
+}))
-it('can increment', () => {
- const state = reducer(0, actions.increment())
- expect(selectors.counterValue(state)).toEqual(1)
+const counterReducer = createReducer(INITIAL_STATE, {
+ [counterActions.increment]: (state, payload) => Math.min(state + payload.amount, COUNTER_MAX),
+ [counterActions.reset]: () => INITIAL_STATE
})
-it('can decrement', () => {
- const state = reducer(0, actions.decrement())
- expect(selectors.counterValue(state)).toEqual(-1)
+const counterSelector = mountSelectors(REDUX_PATH, {
+ value: counterState => counterState,
+ isComplete: counterState => counterState === COUNTER_MAX
})
+```
+
+However, what if we wanted to reuse this logic in multiple places in the redux state tree? We can easily wrap these definitions in a factory function, with the path and counter max as parameters:
+
+```js
+// create-counter-logic.js
+import { createActions, mountActions } from 'redux-modular'
+
+const INITIAL_STATE = 0
+
+export default function createCounterLogic (path, counterMax) {
+ const actions = mountActions(path, createActions({
+ increment: (amount = 1) => ({ amount }),
+ }))
+
+ const reducer = createReducer(INITIAL_STATE, {
+ [counterActions.increment]: (state, payload) => Math.min(state + payload.amount, counterMax),
+ [counterActions.reset]: () => INITIAL_STATE
+ })
+
+ const selectors = mountSelectors(path, {
+ value: counterState => counterState,
+ isComplete: counterState => counterState === counterMax
+ })
+
+ return {
+ actions,
+ reducer,
+ selectors
+ }
+}
+```
-it('can be set to a number', () => {
- const state = reducer(0, actions.set(5))
- expect(selectors.counterValue(state)).toEqual(5)
+Now, we can quickly and easily instantiate our logic and mount it multiple places in our redux state tree:
+
+```js
+import { createStore,combineReducers } from 'redux'
+import createCounterLogic from './create-counter-logic.js'
+
+const counterTo5 = createCounterLogic('counterTo5', 0, 5)
+const counterTo10 = createCounterLogic('counterTo10', 0, 10)
+
+const store = createStore(
+ combineReducers({
+ counterTo5: counterTo5.reducer
+ nested: {
+ counterTo10: counterTo10.reducer
+ }
+ })
+)
+
+store.dispatch(counterTo5.actions.increment(5))
+counterTo5.selectors.value(store.getState()) // 5
+counterTo5.selectors.isComplete(store.getState()) // true
+
+counterTo10.selectors.isComplete(store.getState()) // false
+store.dispatch(counterTo5.actions.increment(10))
+counterTo10.selectors.value(store.getState()) // 10
+counterTo10.selectors.isComplete(store.getState()) // true
+```
+
+### Writing Tests
+
+An easy, minimal way to test your logic is by running `actions` through the `reducer`, and making assertions about the return value of `selectors`. Here is an example using our
+"counter" logic:
+
+```js
+import createCounterLogic from './create-counter-logic'
+
+const COUNTER_MAX = 5
+
+const {
+ counterActions,
+ counterSelectors,
+ counterReducer
+} = createCounterLogic(null, COUNTER_MAX)
+
+test('counter logic', () => {
+ let state = counterReducer(undefined, { type: '@@INIT' })
+ expect(counterSelectors.value(state)).toEqual(0)
+ expect(counterSelectors.isComplete(state)).toEqual(false)
+
+ state = counterReducer(state, counterActions.increment())
+ expect(counterSelectors.value(state)).toEqual(1)
+ expect(counterSelectors.isComplete(state)).toEqual(false)
+
+ state = counterReducer(state, counterActions.increment(4))
+ expect(counterSelectors.value(state)).toEqual(5)
+ expect(counterSelectors.isComplete(state)).toEqual(true)
+
+ state = counterReducer(state, counterActions.increment())
+ expect(counterSelectors.value(state)).toEqual(5) // shouldn't be able to increment past 5
})
```
+
+## API
+
+`createAction(String type, [Function payloadCreator]) : ActionCreator`
+
+`createActions(Object payloadCreatorMap) : Object`
+
+`mountAction(String|Array path, ActionCreator actionCreator) : ActionCreator`
+
+`mountActions(Object actionCreatorMap) : Object`
+
+`createReducer(Any initialState, Object) : Function reducer`
+
+`mountSelector(String|Array path, Function selector) : Function selector`
+
+`mountSelectors(String|Array path, Object selectorMap) : Object`
diff --git a/package-lock.json b/package-lock.json
index fb05d37..ac6c188 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "redux-modular",
- "version": "0.3.0",
+ "version": "0.4.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -57,7 +57,7 @@
"ansi-escapes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz",
- "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==",
+ "integrity": "sha1-7D6LTp+AZPwCw6ybZfHCdb2o75I=",
"dev": true
},
"ansi-regex": {
@@ -78,7 +78,7 @@
"anymatch": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
- "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==",
+ "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=",
"dev": true,
"requires": {
"micromatch": "2.3.11",
@@ -115,7 +115,7 @@
"arr-flatten": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
- "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+ "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=",
"dev": true
},
"array-equal": {
@@ -151,13 +151,13 @@
"astral-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
- "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+ "integrity": "sha1-bIw/uCfdQ+45GPJ7gngqt2WKb9k=",
"dev": true
},
"async": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz",
- "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==",
+ "integrity": "sha1-hDGQ/WtzV6C54clW7d3V7IRitU0=",
"dev": true,
"requires": {
"lodash": "4.17.4"
@@ -411,7 +411,7 @@
"babel-jest": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-21.2.0.tgz",
- "integrity": "sha512-O0W2qLoWu1QOoOGgxiR2JID4O6WSpxPiQanrkyi9SSlM0PJ60Ptzlck47lhtnr9YZO3zYOsxHwnyeWJ6AffoBQ==",
+ "integrity": "sha1-LOBZUZqTdKLEbyRVtvvvWtddhj4=",
"dev": true,
"requires": {
"babel-plugin-istanbul": "4.1.5",
@@ -436,6 +436,11 @@
"babel-runtime": "6.26.0"
}
},
+ "babel-plugin-dev-expression": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-dev-expression/-/babel-plugin-dev-expression-0.2.1.tgz",
+ "integrity": "sha1-1Ke+7++7UOPyc0mQqCokhs+eue4="
+ },
"babel-plugin-istanbul": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz",
@@ -450,7 +455,7 @@
"babel-plugin-jest-hoist": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-21.2.0.tgz",
- "integrity": "sha512-yi5QuiVyyvhBUDLP4ButAnhYzkdrUwWDtvUJv71hjH3fclhnZg4HkDeqaitcR2dZZx/E67kGkRcPVjtVu+SJfQ==",
+ "integrity": "sha1-LO9jclm9S2KKbKzgOd5fzRTbsAY=",
"dev": true
},
"babel-plugin-syntax-async-functions": {
@@ -793,7 +798,7 @@
"babel-preset-jest": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-21.2.0.tgz",
- "integrity": "sha512-hm9cBnr2h3J7yXoTtAVV0zg+3vg0Q/gT2GYuzlreTU0EPkJRtlNgKJJ3tBKEn0+VjAi3JykV6xCJkuUYttEEfA==",
+ "integrity": "sha1-/50rzgir2Y6KNtmopRibkXO4Vjg=",
"dev": true,
"requires": {
"babel-plugin-jest-hoist": "21.2.0",
@@ -870,7 +875,7 @@
"babylon": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
- "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
+ "integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM=",
"dev": true
},
"balanced-match": {
@@ -1003,7 +1008,7 @@
"ci-info": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.1.tgz",
- "integrity": "sha512-vHDDF/bP9RYpTWtUhpJRhCFdvvp3iDWvEbuDbWgvjUrNGV1MXJrE0MPcwGtEled04m61iwdBLUIHZtDgzWS4ZQ==",
+ "integrity": "sha1-R7RN8RjEjSWXtW00Ln4leRBgFxo=",
"dev": true
},
"cliui": {
@@ -1116,7 +1121,7 @@
"boom": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
- "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
+ "integrity": "sha1-XdnabuOl8wIHdDYpDLcX0/SlTgI=",
"dev": true,
"requires": {
"hoek": "4.2.0"
@@ -1151,7 +1156,7 @@
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
"dev": true,
"requires": {
"ms": "2.0.0"
@@ -1242,7 +1247,7 @@
"escodegen": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.0.tgz",
- "integrity": "sha512-v0MYvNQ32bzwoG2OSFzWAkuahDQHK92JBN0pTAALJ4RIxEZe766QJPDR8Hqy7XNUy5K3fnVL76OqYAdc4TZEIw==",
+ "integrity": "sha1-mBGi8mXcHNOJRCDuNxcGS2MriFI=",
"dev": true,
"requires": {
"esprima": "3.1.3",
@@ -1263,7 +1268,7 @@
"esprima": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
- "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
+ "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=",
"dev": true
},
"estraverse": {
@@ -1287,7 +1292,7 @@
"exec-sh": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.1.tgz",
- "integrity": "sha512-aLt95pexaugVtQerpmE51+4QfWrNc304uez7jvj6fWnN8GeEHpttB8F36n8N7uVhUMbH/1enbxQ9HImZ4w/9qg==",
+ "integrity": "sha1-FjuYpuiea2W0fCoo0hW8H2OYnDg=",
"dev": true,
"requires": {
"merge": "1.2.0"
@@ -1329,7 +1334,7 @@
"expect": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/expect/-/expect-21.2.1.tgz",
- "integrity": "sha512-orfQQqFRTX0jH7znRIGi8ZMR8kTNpXklTTz8+HGTpmTKZo3Occ6JNB5FXMb8cRuiiC/GyDqsr30zUa66ACYlYw==",
+ "integrity": "sha1-ADrCrHAFw8Kec7OKJy1K+t1tHXs=",
"dev": true,
"requires": {
"ansi-styles": "3.2.0",
@@ -1461,7 +1466,7 @@
"fsevents": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz",
- "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==",
+ "integrity": "sha1-MoK3E/s62A7eDp/PRhG1qm/AM/Q=",
"dev": true,
"optional": true,
"requires": {
@@ -2381,7 +2386,7 @@
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
"dev": true,
"requires": {
"fs.realpath": "1.0.0",
@@ -2414,7 +2419,7 @@
"globals": {
"version": "9.18.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
- "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
+ "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=",
"dev": true
},
"graceful-fs": {
@@ -2492,7 +2497,7 @@
"hawk": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
- "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
+ "integrity": "sha1-r02RTrBl+bXOTZ0RwcshJu7MMDg=",
"dev": true,
"requires": {
"boom": "4.3.1",
@@ -2504,7 +2509,7 @@
"hoek": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
- "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==",
+ "integrity": "sha1-ctnQdU9/4lyi0BrY+PmpRJqJUm0=",
"dev": true
},
"home-or-tmp": {
@@ -2520,7 +2525,7 @@
"hosted-git-info": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
- "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==",
+ "integrity": "sha1-bWDjSzq7yDEwYsO3mO+NkBoHrzw=",
"dev": true
},
"html-encoding-sniffer": {
@@ -2758,7 +2763,7 @@
"istanbul-lib-coverage": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz",
- "integrity": "sha512-0+1vDkmzxqJIn5rcoEqapSB4DmPxE31EtI2dF2aCkV5esN9EWHxZ0dwgDClivMXJqE7zaYQxq30hj5L0nlTN5Q==",
+ "integrity": "sha1-c7+5mIhSmUFck9OKPprfeEp3qdo=",
"dev": true
},
"istanbul-lib-hook": {
@@ -2839,7 +2844,7 @@
"jest": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest/-/jest-21.2.1.tgz",
- "integrity": "sha512-mXN0ppPvWYoIcC+R+ctKxAJ28xkt/Z5Js875padm4GbgUn6baeR5N4Ng6LjatIRpUQDZVJABT7Y4gucFjPryfw==",
+ "integrity": "sha1-yWTgtHODdooUOOPM88PUcDJ2BOE=",
"dev": true,
"requires": {
"jest-cli": "21.2.1"
@@ -2848,7 +2853,7 @@
"jest-cli": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-21.2.1.tgz",
- "integrity": "sha512-T1BzrbFxDIW/LLYQqVfo94y/hhaj1NzVQkZgBumAC+sxbjMROI7VkihOdxNR758iYbQykL2ZOWUBurFgkQrzdg==",
+ "integrity": "sha1-nFKLZinWUZERONIovbAzwVfsjAA=",
"dev": true,
"requires": {
"ansi-escapes": "3.0.0",
@@ -2887,7 +2892,7 @@
"jest-changed-files": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-21.2.0.tgz",
- "integrity": "sha512-+lCNP1IZLwN1NOIvBcV5zEL6GENK6TXrDj4UxWIeLvIsIDa+gf6J7hkqsW2qVVt/wvH65rVvcPwqXdps5eclTQ==",
+ "integrity": "sha1-Xb7srUL12ItIIzSQLOHLptl5jSk=",
"dev": true,
"requires": {
"throat": "4.1.0"
@@ -2896,7 +2901,7 @@
"jest-config": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-config/-/jest-config-21.2.1.tgz",
- "integrity": "sha512-fJru5HtlD/5l2o25eY9xT0doK3t2dlglrqoGpbktduyoI0T5CwuB++2YfoNZCrgZipTwPuAGonYv0q7+8yDc/A==",
+ "integrity": "sha1-x1hseerQvMHzjEAeVflk8TvypIA=",
"dev": true,
"requires": {
"chalk": "2.1.0",
@@ -2915,7 +2920,7 @@
"jest-diff": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-21.2.1.tgz",
- "integrity": "sha512-E5fu6r7PvvPr5qAWE1RaUwIh/k6Zx/3OOkZ4rk5dBJkEWRrUuSgbMt2EO8IUTPTd6DOqU3LW6uTIwX5FRvXoFA==",
+ "integrity": "sha1-RszLbKstAs6YvDFAEXZLuVsGW08=",
"dev": true,
"requires": {
"chalk": "2.1.0",
@@ -2927,13 +2932,13 @@
"jest-docblock": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz",
- "integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==",
+ "integrity": "sha1-UVKcOzDV/RWdpgwnzu3Blfr41BQ=",
"dev": true
},
"jest-environment-jsdom": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-21.2.1.tgz",
- "integrity": "sha512-mecaeNh0eWmzNrUNMWARysc0E9R96UPBamNiOCYL28k7mksb1d0q6DD38WKP7ABffjnXyUWJPVaWRgUOivwXwg==",
+ "integrity": "sha1-ONmYDIJZsqYI7CMt7uYommDZ1bQ=",
"dev": true,
"requires": {
"jest-mock": "21.2.0",
@@ -2944,7 +2949,7 @@
"jest-environment-node": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-21.2.1.tgz",
- "integrity": "sha512-R211867wx9mVBVHzrjGRGTy5cd05K7eqzQl/WyZixR/VkJ4FayS8qkKXZyYnwZi6Rxo6WEV81cDbiUx/GfuLNw==",
+ "integrity": "sha1-mMZ99WY8f74g9ueSrCJyx0DTuMg=",
"dev": true,
"requires": {
"jest-mock": "21.2.0",
@@ -2954,13 +2959,13 @@
"jest-get-type": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-21.2.0.tgz",
- "integrity": "sha512-y2fFw3C+D0yjNSDp7ab1kcd6NUYfy3waPTlD8yWkAtiocJdBRQqNoRqVfMNxgj+IjT0V5cBIHJO0z9vuSSZ43Q==",
+ "integrity": "sha1-9jdqudtLYNgeOfMHScbEZvQNSiM=",
"dev": true
},
"jest-haste-map": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-21.2.0.tgz",
- "integrity": "sha512-5LhsY/loPH7wwOFRMs+PT4aIAORJ2qwgbpMFlbWbxfN0bk3ZCwxJ530vrbSiTstMkYLao6JwBkLhCJ5XbY7ZHw==",
+ "integrity": "sha1-E2PwqLtDOPJPABgGVx7/eksv89g=",
"dev": true,
"requires": {
"fb-watchman": "2.0.0",
@@ -2974,7 +2979,7 @@
"jest-jasmine2": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-21.2.1.tgz",
- "integrity": "sha512-lw8FXXIEekD+jYNlStfgNsUHpfMWhWWCgHV7n0B7mA/vendH7vBFs8xybjQsDzJSduptBZJHqQX9SMssya9+3A==",
+ "integrity": "sha1-nMb8EIrM+pfv684QxDCFSKTqdZI=",
"dev": true,
"requires": {
"chalk": "2.1.0",
@@ -2990,7 +2995,7 @@
"jest-matcher-utils": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-21.2.1.tgz",
- "integrity": "sha512-kn56My+sekD43dwQPrXBl9Zn9tAqwoy25xxe7/iY4u+mG8P3ALj5IK7MLHZ4Mi3xW7uWVCjGY8cm4PqgbsqMCg==",
+ "integrity": "sha1-csgm6rpBoJOsK0Vl+GXrhHXeD2Q=",
"dev": true,
"requires": {
"chalk": "2.1.0",
@@ -3001,7 +3006,7 @@
"jest-message-util": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-21.2.1.tgz",
- "integrity": "sha512-EbC1X2n0t9IdeMECJn2BOg7buOGivCvVNjqKMXTzQOu7uIfLml+keUfCALDh8o4rbtndIeyGU8/BKfoTr/LVDQ==",
+ "integrity": "sha1-v+XUaSyEyCfR3PQYI3lVWPChrL4=",
"dev": true,
"requires": {
"chalk": "2.1.0",
@@ -3012,19 +3017,19 @@
"jest-mock": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-21.2.0.tgz",
- "integrity": "sha512-aZDfyVf0LEoABWiY6N0d+O963dUQSyUa4qgzurHR3TBDPen0YxKCJ6l2i7lQGh1tVdsuvdrCZ4qPj+A7PievCw==",
+ "integrity": "sha1-frB3DnMXloFl9h6ipygRMVNLPA8=",
"dev": true
},
"jest-regex-util": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-21.2.0.tgz",
- "integrity": "sha512-BKQ1F83EQy0d9Jen/mcVX7D+lUt2tthhK/2gDWRgLDJRNOdRgSp1iVqFxP8EN1ARuypvDflRfPzYT8fQnoBQFQ==",
+ "integrity": "sha1-Gx4z5jFDurw+Dy5sm1uh6zSy1TA=",
"dev": true
},
"jest-resolve": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-21.2.0.tgz",
- "integrity": "sha512-vefQ/Lr+VdNvHUZFQXWtOqHX3HEdOc2MtSahBO89qXywEbUxGPB9ZLP9+BHinkxb60UT2Q/tTDOS6rYc6Mwigw==",
+ "integrity": "sha1-BokTrSumogIY5f0yRx84dABd46Y=",
"dev": true,
"requires": {
"browser-resolve": "1.11.2",
@@ -3035,7 +3040,7 @@
"jest-resolve-dependencies": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-21.2.0.tgz",
- "integrity": "sha512-ok8ybRFU5ScaAcfufIQrCbdNJSRZ85mkxJ1EhUp8Bhav1W1/jv/rl1Q6QoVQHObNxmKnbHVKrfLZbCbOsXQ+bQ==",
+ "integrity": "sha1-niMeNx4ac2oa1OS5qEO8cr/gPQk=",
"dev": true,
"requires": {
"jest-regex-util": "21.2.0"
@@ -3044,7 +3049,7 @@
"jest-runner": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-21.2.1.tgz",
- "integrity": "sha512-Anb72BOQlHqF/zETqZ2K20dbYsnqW/nZO7jV8BYENl+3c44JhMrA8zd1lt52+N7ErnsQMd2HHKiVwN9GYSXmrg==",
+ "integrity": "sha1-GUcy4+UYv7PXy/wP1YcSRsfhpGc=",
"dev": true,
"requires": {
"jest-config": "21.2.1",
@@ -3062,7 +3067,7 @@
"jest-runtime": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-21.2.1.tgz",
- "integrity": "sha512-6omlpA3+NSE+rHwD0PQjNEjZeb2z+oRmuehMfM1tWQVum+E0WV3pFt26Am0DUfQkkPyTABvxITRjCUclYgSOsA==",
+ "integrity": "sha1-mdzhUwnGcEQu7i6+H/U6PL27tz4=",
"dev": true,
"requires": {
"babel-core": "6.26.0",
@@ -3095,7 +3100,7 @@
"jest-snapshot": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-21.2.1.tgz",
- "integrity": "sha512-bpaeBnDpdqaRTzN8tWg0DqOTo2DvD3StOemxn67CUd1p1Po+BUpvePAp44jdJ7Pxcjfg+42o4NHw1SxdCA2rvg==",
+ "integrity": "sha1-KeSfFiAkFuRzQ+dX5e/5SMB/17A=",
"dev": true,
"requires": {
"chalk": "2.1.0",
@@ -3109,7 +3114,7 @@
"jest-util": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-21.2.1.tgz",
- "integrity": "sha512-r20W91rmHY3fnCoO7aOAlyfC51x2yeV3xF+prGsJAUsYhKeV670ZB8NO88Lwm7ASu8SdH0S+U+eFf498kjhA4g==",
+ "integrity": "sha1-onSy9yawiXSU1pSmw9amGrgZu3g=",
"dev": true,
"requires": {
"callsites": "2.0.0",
@@ -3124,7 +3129,7 @@
"jest-validate": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-21.2.1.tgz",
- "integrity": "sha512-k4HLI1rZQjlU+EC682RlQ6oZvLrE5SCh3brseQc24vbZTxzT/k/3urar5QMCVgjadmSO7lECeGdc6YxnM3yEGg==",
+ "integrity": "sha1-zAy8plPNVJN7pPKhEXlndFMN08c=",
"dev": true,
"requires": {
"chalk": "2.1.0",
@@ -3136,13 +3141,12 @@
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
- "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
- "dev": true
+ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-yaml": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
- "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
+ "integrity": "sha1-LnhEFka9RoLpY/IrbpKCPDCcYtw=",
"dev": true,
"requires": {
"argparse": "1.0.9",
@@ -3339,7 +3343,6 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
"integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
- "dev": true,
"requires": {
"js-tokens": "3.0.2"
}
@@ -3347,7 +3350,7 @@
"lru-cache": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
- "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
+ "integrity": "sha1-Yi4y6CSItJJ5EUpPns9F581rulU=",
"dev": true,
"requires": {
"pseudomap": "1.0.2",
@@ -3423,7 +3426,7 @@
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
"dev": true,
"requires": {
"brace-expansion": "1.1.8"
@@ -3484,7 +3487,7 @@
"normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
- "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+ "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=",
"dev": true,
"requires": {
"hosted-git-info": "2.5.0",
@@ -3595,7 +3598,7 @@
"os-locale": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
- "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==",
+ "integrity": "sha1-QrwpAKa1uL0XN2yOiCtlr8zyS/I=",
"dev": true,
"requires": {
"execa": "0.7.0",
@@ -3612,7 +3615,7 @@
"p-cancelable": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz",
- "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==",
+ "integrity": "sha1-ueEjgAvOu3rBOkeb4ZW1B7mNMPo=",
"dev": true
},
"p-finally": {
@@ -3748,7 +3751,7 @@
"pretty-format": {
"version": "21.2.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-21.2.1.tgz",
- "integrity": "sha512-ZdWPGYAnYfcVP8yKA3zFjCn8s4/17TeYH28MXuC8vTp0o21eXjbFGcOAXZEaDaOFJjc3h2qa7HQNHNshhvoh2A==",
+ "integrity": "sha1-rlQH888hBmzQEaobpfzntqLt2zY=",
"dev": true,
"requires": {
"ansi-regex": "3.0.0",
@@ -3790,13 +3793,13 @@
"qs": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
- "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
+ "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=",
"dev": true
},
"randomatic": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
- "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
+ "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=",
"dev": true,
"requires": {
"is-number": "3.0.0",
@@ -3879,7 +3882,7 @@
"redux": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
- "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
+ "integrity": "sha1-BrcxIyFZAdJdBlvjQusCa8HIU3s=",
"dev": true,
"requires": {
"lodash": "4.17.4",
@@ -3891,19 +3894,19 @@
"regenerate": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz",
- "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==",
+ "integrity": "sha1-DDNtOYBVPXVcObWGrjsgqknIK38=",
"dev": true
},
"regenerator-runtime": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz",
- "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==",
+ "integrity": "sha1-flT+W1zNXWYk6mJVw0c74JC4AuE=",
"dev": true
},
"regenerator-transform": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz",
- "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==",
+ "integrity": "sha1-HkmWg3Ix2ot/PPQRTXG1aRoGgN0=",
"dev": true,
"requires": {
"babel-runtime": "6.26.0",
@@ -3914,7 +3917,7 @@
"regex-cache": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
- "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==",
+ "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=",
"dev": true,
"requires": {
"is-equal-shallow": "0.1.3"
@@ -3984,7 +3987,7 @@
"request": {
"version": "2.83.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
- "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==",
+ "integrity": "sha1-ygtl2gLtYpNYh4COb1EDgQNOM1Y=",
"dev": true,
"requires": {
"aws-sign2": "0.7.0",
@@ -4042,7 +4045,7 @@
"rimraf": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
- "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+ "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=",
"dev": true,
"requires": {
"glob": "7.1.2"
@@ -4051,13 +4054,13 @@
"rollup": {
"version": "0.50.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-0.50.0.tgz",
- "integrity": "sha512-7RqCBQ9iwsOBPkjYgoIaeUij606mSkDMExP0NT7QDI3bqkHYQHrQ83uoNIXwPcQm/vP2VbsUz3kiyZZ1qPlLTQ==",
+ "integrity": "sha1-TBWPTngObLM/8Nv8GEpSzFjNXzs=",
"dev": true
},
"rollup-plugin-babel": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-3.0.2.tgz",
- "integrity": "sha512-ALGPBFtwJZcYHsNPM6RGJlEncTzAARPvZOGjNPZgDe5hS5t6sJGjiOWibEFVEz5LQN7S7spvCBILaS4N1Cql2w==",
+ "integrity": "sha1-onZd6g6qiuzjUcmDVzMA0XSXSVs=",
"dev": true,
"requires": {
"rollup-pluginutils": "1.5.2"
@@ -4076,13 +4079,13 @@
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
- "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
+ "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=",
"dev": true
},
"sane": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/sane/-/sane-2.2.0.tgz",
- "integrity": "sha512-OSJxhHO0CgPUw3lUm3GhfREAfza45smvEI9ozuFrxKG10GHVo0ryW9FK5VYlLvxj0SV7HVKHW0voYJIRu27GWg==",
+ "integrity": "sha1-1tLi/KsA49KDyTuRK3w6IIRvHVY=",
"dev": true,
"requires": {
"anymatch": "1.3.2",
@@ -4106,13 +4109,13 @@
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+ "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=",
"dev": true
},
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
- "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
+ "integrity": "sha1-4FnAnYVx8FQII3M0M1BdOi8AsY4=",
"dev": true
},
"set-blocking": {
@@ -4139,7 +4142,7 @@
"shellwords": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
- "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
+ "integrity": "sha1-1rkYHBpI05cyTISHHvvPxz/AZUs=",
"dev": true
},
"signal-exit": {
@@ -4172,7 +4175,7 @@
"source-map-support": {
"version": "0.4.18",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
- "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==",
+ "integrity": "sha1-Aoam3ovkJkEzhZTpfM6nXwosWF8=",
"dev": true,
"requires": {
"source-map": "0.5.7"
@@ -4234,7 +4237,7 @@
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
- "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
"dev": true,
"requires": {
"is-fullwidth-code-point": "2.0.0",
@@ -4311,7 +4314,7 @@
"test-exclude": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.1.1.tgz",
- "integrity": "sha512-35+Asrsk3XHJDBgf/VRFexPgh3UyETv8IAn/LRTiZjVy6rjPVqdEk8dJcJYBzl1w0XCJM48lvTy8SfEsCWS4nA==",
+ "integrity": "sha1-TYSWSwlmsAh+zDNKLOAC09k0HiY=",
"dev": true,
"requires": {
"arrify": "1.0.1",
@@ -4422,7 +4425,7 @@
"uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
- "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==",
+ "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ=",
"dev": true
},
"validate-npm-package-license": {
@@ -4455,6 +4458,14 @@
"makeerror": "1.0.11"
}
},
+ "warning": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.1.tgz",
+ "integrity": "sha512-rAVtTNZw+cQPjvGp1ox0XC5Q2IBFyqoqh+QII4J/oguyu83Bax1apbo2eqB8bHRS+fqYUBagys6lqUoVwKSmXQ==",
+ "requires": {
+ "loose-envify": "1.3.1"
+ }
+ },
"watch": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz",
@@ -4476,7 +4487,7 @@
"webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
- "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "integrity": "sha1-qFWYCx8LazWbodXZ+zmulB+qY60=",
"dev": true
},
"whatwg-encoding": {
@@ -4509,7 +4520,7 @@
"which": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
- "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
+ "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=",
"dev": true,
"requires": {
"isexe": "2.0.0"
@@ -4537,7 +4548,7 @@
"worker-farm": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.5.0.tgz",
- "integrity": "sha512-DHRiUggxtbruaTwnLDm2/BRDKZIoOYvrgYUj5Bam4fU6Gtvc0FaEyoswFPBjMXAweGW2H4BDNIpy//1yXXuaqQ==",
+ "integrity": "sha1-rf3wzUBYFGXtCh9kj5c1cir9XI0=",
"dev": true,
"requires": {
"errno": "0.1.4",
@@ -4585,7 +4596,7 @@
"write-file-atomic": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz",
- "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==",
+ "integrity": "sha1-H/YVdcLipOjlENb6TiQ8zhg5mas=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11",
diff --git a/package.json b/package.json
index 6ede855..c343043 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,9 @@
},
"homepage": "https://github.com/thomasdashney/redux-modular#readme",
"dependencies": {
- "lodash.get": "^4.4.2"
+ "babel-plugin-dev-expression": "^0.2.1",
+ "lodash.get": "^4.4.2",
+ "warning": "^4.0.1"
},
"devDependencies": {
"babel-jest": "^21.2.0",
diff --git a/rollup.config.js b/rollup.config.js
index ca7daef..ce46866 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -12,5 +12,5 @@ export default {
exclude: 'node_modules/**' // only transpile our source code
})
],
- external: ['lodash.get']
+ external: ['lodash.get', 'warning']
}
diff --git a/src/create-action.js b/src/create-action.js
index 39ef6cd..5fcb6b9 100644
--- a/src/create-action.js
+++ b/src/create-action.js
@@ -15,6 +15,7 @@ export default function createAction (type, payloadCreator) {
}
actionCreator.toString = () => type
+ actionCreator.payloadCreator = payloadCreator
return actionCreator
}
diff --git a/src/create-actions.js b/src/create-actions.js
new file mode 100644
index 0000000..625cf84
--- /dev/null
+++ b/src/create-actions.js
@@ -0,0 +1,8 @@
+import createAction from './create-action'
+import mapValues from './util/map-values'
+
+export default function createActions (payloadCreators) {
+ return mapValues(payloadCreators, (payloadCreator, key) => {
+ return createAction(key, payloadCreator)
+ })
+}
diff --git a/src/globalize-actions.js b/src/globalize-actions.js
deleted file mode 100644
index 3da3836..0000000
--- a/src/globalize-actions.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import createAction from './create-action'
-
-const isArray = value => {
- return typeof value === 'object' &&
- value !== null &&
- value.constructor === Array
-}
-const isString = value => typeof value === 'string'
-
-export default function globalizeActions (pathToState, actions) {
- if (isArray(pathToState)) {
- pathToState = pathToState.join('.')
- } else if (pathToState !== null && !isString(pathToState)) {
- throw new Error('path must be a string or array')
- }
-
- return Object.keys(actions).reduce((prev, key) => {
- const type = pathToState ? `${key} (${pathToState})` : key
-
- return Object.assign({}, prev, {
- [key]: createAction(type, actions[key])
- })
- }, {})
-}
diff --git a/src/index.js b/src/index.js
index a2a084e..f09bef0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,3 +1,10 @@
-export { default as mount } from './mount'
-export { default as createReducer } from './create-reducer'
export { default as createAction } from './create-action'
+export { default as createActions } from './create-actions'
+export { default as mountAction } from './mount-action'
+export { default as mountActions } from './mount-actions'
+export { default as createReducer } from './create-reducer'
+export { default as mountSelector } from './mount-selector'
+export { default as mountSelectors } from './mount-selectors'
+
+// deprecated
+export { default as mount } from './mount'
diff --git a/src/mount-action.js b/src/mount-action.js
new file mode 100644
index 0000000..2388898
--- /dev/null
+++ b/src/mount-action.js
@@ -0,0 +1,19 @@
+import createAction from './create-action'
+import { validatePath } from './util/arg-validators'
+import { isArray } from './util/type-utils'
+
+export default function mountAction (path, action) {
+ if (path === null) {
+ return action
+ }
+
+ if (!validatePath(path)) {
+ throw new Error('path must be a string or array of strings')
+ }
+
+ if (isArray(path)) {
+ path = path.join('.')
+ }
+
+ return createAction(`${action.toString()} (${path})`, action.payloadCreator)
+}
diff --git a/src/mount-actions.js b/src/mount-actions.js
new file mode 100644
index 0000000..b8bab35
--- /dev/null
+++ b/src/mount-actions.js
@@ -0,0 +1,6 @@
+import mountAction from './mount-action'
+import mapValues from './util/map-values'
+
+export default function mountActions (path, actions) {
+ return mapValues(actions, action => mountAction(path, action))
+}
diff --git a/src/mount-selector.js b/src/mount-selector.js
new file mode 100644
index 0000000..dfadfb3
--- /dev/null
+++ b/src/mount-selector.js
@@ -0,0 +1,15 @@
+import get from 'lodash.get'
+
+export default function mountSelector (path, selector) {
+ if (!path) return selector
+
+ return state => {
+ const nestedState = get(state, path)
+
+ if (nestedState === undefined) {
+ throw new Error(`nested state ${path} does not exist`)
+ }
+
+ return selector(nestedState)
+ }
+}
diff --git a/src/mount-selectors.js b/src/mount-selectors.js
new file mode 100644
index 0000000..d12d020
--- /dev/null
+++ b/src/mount-selectors.js
@@ -0,0 +1,6 @@
+import mountSelector from './mount-selector'
+import mapValues from './util/map-values'
+
+export default function mountSelectors (path, selectors) {
+ return mapValues(selectors, (selector) => mountSelector(path, selector))
+}
diff --git a/src/mount.js b/src/mount.js
index 6d91c61..db1d97f 100644
--- a/src/mount.js
+++ b/src/mount.js
@@ -1,32 +1,46 @@
import get from 'lodash.get'
-import globalizeActions from './globalize-actions'
+import warning from 'warning'
+import createActions from './create-actions'
+import mountActions from './mount-actions'
-export default function (pathToState, logic) {
+export default function mount (pathToState, logic) {
+ warning(true, 'redux-modular mount() is deprecated')
+ validateArgs(...arguments)
+ return mountLogic(pathToState, logic)
+}
+
+function validateArgs (pathToState, logic) {
if (!logic) {
throw new Error('logic must be passed to mount')
}
+}
- let { actions, reducer, selectors } = logic
+function mountLogic (pathToState, logic) {
+ const result = {}
- if (actions) {
- actions = globalizeActions(pathToState, actions)
+ if (logic.actions) {
+ result.actions = createAndMountActions(pathToState, logic.actions)
}
- if (actions && reducer) {
- reducer = reducer(actions)
+ if (logic.actions && logic.reducer) {
+ result.reducer = logic.reducer(result.actions)
}
- if (selectors) {
- const localStateSelector = pathToState
+ if (logic.selectors) {
+ result.selectors = createSelectors(pathToState, logic.selectors)
+ }
+
+ return result
+}
+
+function createAndMountActions (pathToState, actions) {
+ return mountActions(pathToState, createActions(actions))
+}
+
+function createSelectors (pathToState, selectors) {
+ const localStateSelector = pathToState
? state => get(state, pathToState)
: state => state
- selectors = selectors(localStateSelector)
- }
-
- return {
- actions,
- reducer,
- selectors
- }
+ return selectors(localStateSelector)
}
diff --git a/src/util/arg-validators.js b/src/util/arg-validators.js
new file mode 100644
index 0000000..9964ca7
--- /dev/null
+++ b/src/util/arg-validators.js
@@ -0,0 +1,5 @@
+import { isArray, isString } from './type-utils'
+
+export const validatePath = path => {
+ return isString(path) || (isArray(path) && path.every(isString))
+}
diff --git a/src/util/map-values.js b/src/util/map-values.js
new file mode 100644
index 0000000..aa6cbf9
--- /dev/null
+++ b/src/util/map-values.js
@@ -0,0 +1,7 @@
+export default function mapValues (object, map) {
+ return Object.keys(object).reduce((prev, key) => {
+ return Object.assign(prev, {
+ [key]: map(object[key], key)
+ })
+ }, {})
+}
diff --git a/src/util/type-utils.js b/src/util/type-utils.js
new file mode 100644
index 0000000..2a82c2e
--- /dev/null
+++ b/src/util/type-utils.js
@@ -0,0 +1,7 @@
+export const isArray = value => {
+ return typeof value === 'object' &&
+ value !== null &&
+ value.constructor === Array
+}
+
+export const isString = value => typeof value === 'string'
diff --git a/test/create-actions.test.js b/test/create-actions.test.js
new file mode 100644
index 0000000..97ac295
--- /dev/null
+++ b/test/create-actions.test.js
@@ -0,0 +1,17 @@
+/* eslint-env jest */
+
+import createActions from '../src/create-actions'
+
+it('creates an object of action creators given an object of payload creators', () => {
+ let actions = createActions({
+ testAction: null
+ })
+
+ expect(actions).toHaveProperty('testAction')
+ expect(actions.testAction.toString()).toEqual('testAction')
+
+ actions = createActions({
+ testAction: value => value
+ })
+ expect(actions.testAction('testValue').payload).toEqual('testValue')
+})
diff --git a/test/globalize-actions.test.js b/test/globalize-actions.test.js
deleted file mode 100644
index a15a7f7..0000000
--- a/test/globalize-actions.test.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/* eslint-env jest */
-
-import globalizeActions from '../src/globalize-actions'
-
-it('creates actions with types including the state path', () => {
- let actions = globalizeActions('path.to.state', {
- increment: null
- })
- expect(actions).toHaveProperty('increment')
- expect(actions.increment.toString()).toEqual('increment (path.to.state)')
-
- actions = globalizeActions(['path', 'to', 'state'], {
- increment: null
- })
- expect(actions.increment.toString()).toEqual('increment (path.to.state)')
-})
-
-it('creates actions with payload creators', () => {
- const actions = globalizeActions('path.to.state', {
- increment: (param1, param2) => ({ param1, param2 })
- })
-
- const action = actions.increment('test1', 'test2')
- expect(action).toHaveProperty('payload')
- expect(action.payload).toEqual({
- param1: 'test1',
- param2: 'test2'
- })
-})
-
-it('does not include the state path if pathToState is null', () => {
- let actions = globalizeActions(null, {
- increment: null
- })
- expect(actions).toHaveProperty('increment')
- expect(actions.increment.toString()).toEqual('increment')
-})
-
-it('throws an error if pathToState is invalid', () => {
- const actions = { increment: () => null }
-
- expect(() => globalizeActions(5, actions)).toThrow()
- expect(() => globalizeActions({}, actions)).toThrow()
-})
diff --git a/test/index.test.js b/test/index.test.js
index 3a960b4..f6c02a8 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -2,8 +2,13 @@
import * as reduxModular from '../src/index'
-it('exports modularize and createReducer', () => {
+test('exported functions', () => {
expect(reduxModular).toHaveProperty('mount')
- expect(reduxModular).toHaveProperty('createReducer')
expect(reduxModular).toHaveProperty('createAction')
+ expect(reduxModular).toHaveProperty('createActions')
+ expect(reduxModular).toHaveProperty('mountAction')
+ expect(reduxModular).toHaveProperty('mountActions')
+ expect(reduxModular).toHaveProperty('createReducer')
+ expect(reduxModular).toHaveProperty('mountSelector')
+ expect(reduxModular).toHaveProperty('mountSelectors')
})
diff --git a/test/mount-action.test.js b/test/mount-action.test.js
new file mode 100644
index 0000000..77a77ce
--- /dev/null
+++ b/test/mount-action.test.js
@@ -0,0 +1,26 @@
+/* eslint-env jest */
+
+import createAction from '../src/create-action'
+import mountAction from '../src/mount-action'
+
+const action = createAction('test', () => 'payloadValue')
+
+it('mounts an action to a given path', () => {
+ const mountedAction = mountAction('path', action)
+ expect(mountedAction.toString()).toEqual('test (path)')
+ expect(mountedAction().payload).toEqual('payloadValue')
+})
+
+it('can mount to an array of strings', () => {
+ const mountedAction = mountAction(['nested', 'path'], action)
+ expect(mountedAction.toString()).toEqual('test (nested.path)')
+})
+
+it('can mount to a path of null', () => {
+ const mountedAction = mountAction(null, action)
+ expect(mountedAction).toEqual(action)
+})
+
+it('throws an error if the path is invalid', () => {
+ expect(() => mountAction(5, action)).toThrow()
+})
diff --git a/test/mount-actions.test.js b/test/mount-actions.test.js
new file mode 100644
index 0000000..78cd09a
--- /dev/null
+++ b/test/mount-actions.test.js
@@ -0,0 +1,21 @@
+/* eslint-env jest */
+
+import createActions from '../src/create-actions'
+import mountActions from '../src/mount-actions'
+
+const actions = createActions({
+ action1: null,
+ action2: value => value
+})
+
+it('mounts an object of actions to a given path', () => {
+ const mountedActions = mountActions('path', actions)
+ expect(mountedActions).toHaveProperty('action1')
+ expect(mountedActions).toHaveProperty('action2')
+ expect(mountedActions.action1.toString()).toEqual('action1 (path)')
+ expect(mountedActions.action2.toString()).toEqual('action2 (path)')
+})
+
+it('throws an error if the path is invalid', () => {
+ expect(() => mountActions(5, actions)).toThrow()
+})
diff --git a/test/mount-selector.test.js b/test/mount-selector.test.js
new file mode 100644
index 0000000..90cf92b
--- /dev/null
+++ b/test/mount-selector.test.js
@@ -0,0 +1,24 @@
+/* eslint-env jest */
+
+import mountSelector from '../src/mount-selector'
+
+it('wraps a given selector to a given path', () => {
+ const selector1 = mountSelector('path', state => state.key)
+ expect(selector1({ path: { key: 'value' } })).toEqual('value')
+
+ const selector2 = mountSelector('nested.path', state => state.key)
+ expect(selector2({ nested: { path: { key: 'value' } } })).toEqual('value')
+
+ const selector3 = mountSelector(['nested', 'path'], state => state.key)
+ expect(selector3({ nested: { path: { key: 'value' } } })).toEqual('value')
+})
+
+it('return the original selector if no path is provided', () => {
+ const selector = mountSelector(null, state => state.key)
+ expect(selector({ key: 'value' })).toEqual('value')
+})
+
+it('throws an error if the path to state does not exist', () => {
+ const selector = mountSelector('path', state => state)
+ expect(() => selector({})).toThrow()
+})
diff --git a/test/mount-selectors.test.js b/test/mount-selectors.test.js
new file mode 100644
index 0000000..d023614
--- /dev/null
+++ b/test/mount-selectors.test.js
@@ -0,0 +1,14 @@
+/* eslint-env jest */
+
+import mountSelectors from '../src/mount-selectors'
+
+const selectors = {
+ selector1: state => state.key1,
+ selector2: state => state.key2
+}
+
+it('mounts an object of selectors to a given path', () => {
+ const mountedSelectors = mountSelectors('path', selectors)
+ expect(mountedSelectors.selector1({ path: { key1: 'value' } })).toEqual('value')
+ expect(mountedSelectors.selector2({ path: { key2: 'value' } })).toEqual('value')
+})