From 712276d5c56d3209eff9ba19fc0a976fc3163dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ole=C5=9B?= Date: Tue, 11 Apr 2017 23:39:08 +0200 Subject: [PATCH 01/10] Add combineExecutors, reduceExecutors, improve docs --- README.md | 124 ++++++++++++++++++++++++---- package.json | 3 +- src/ActionLike.ts | 5 ++ src/Executor.ts | 9 +-- src/combineExecutors.ts | 39 ++++----- src/createExecutableStore.ts | 28 ------- src/handleCommand.ts | 10 +-- src/handleCommands.ts | 14 ++++ src/index.ts | 5 +- src/mountExecutor.ts | 6 +- src/reduceExecutors.ts | 32 ++++++++ test/combineExecutors.spec.ts | 126 ++++++++++++++++------------- test/createExecutableStore.spec.ts | 45 ----------- test/handleCommand.spec.ts | 2 +- test/handleCommands.spec.ts | 51 ++++++++++++ test/isCommand.spec.ts | 2 +- test/reduceExecutors.spec.ts | 95 ++++++++++++++++++++++ yarn.lock | 20 +++-- 18 files changed, 426 insertions(+), 190 deletions(-) create mode 100644 src/ActionLike.ts delete mode 100644 src/createExecutableStore.ts create mode 100644 src/handleCommands.ts create mode 100644 src/reduceExecutors.ts delete mode 100644 test/createExecutableStore.spec.ts create mode 100644 test/handleCommands.spec.ts create mode 100644 test/reduceExecutors.spec.ts diff --git a/README.md b/README.md index 65e0e6a..fdfd25a 100644 --- a/README.md +++ b/README.md @@ -16,19 +16,7 @@ This assumes that you’re using [npm](http://npmjs.com/) package manager with a [Webpack](http://webpack.github.io/) or [Browserify](http://browserify.org/) to consume [CommonJS modules](http://webpack.github.io/docs/commonjs.html). -To enable Redux Executor, use `createExecutableStore`: -```js -import { createExecutableStore } from 'redux-executor'; -import rootReducer from './reducers/index'; -import rootExecutor from './executors/index'; - -const store = createExecutableStore( - rootReducer, - rootExecutor -); -``` - -or if you have more complicated store creation, use `createStore` with `createExecutorEnhancer`: +To enable Redux Executor, use `createExecutorEnhancer` with `createStore`: ```js import { createStore } from 'redux'; import { createExecutorEnhancer } from 'redux-executor'; @@ -42,7 +30,31 @@ const store = createStore( ``` ## Motivation ## -TODO +There are many clever solutions to deal with side-effects in redux application like [redux-thunk](https://github.com/gaearon/redux-thunk) +or [redux-saga](https://github.com/redux-saga/redux-saga). The goal of this library is to be simpler than **redux-saga** +and also easier to test and more pure than **redux-thunk**. + +## Concepts ## +### An Executor ### +It may sounds a little bit scary but there is nothing to fear - executor is very pure and simple function. +``` +type Executor = (command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined) => Promise | void; +``` +Like you see above, executor takes an action (called **command** in executors), enhanced `dispatch` function and state. +It can return a `Promise` to provide custom execution flow. + +### A Command ### +Command is an **action** with specific `type` format - `COMMAND_TYPE()` (like function call, instead of `COMMAND_TYPE`). The idea behind +is that it's more clean to split actions to two types: normal actions (we will call it _events_) that tells +what **has happened** and _commands_ that tells what **should happen**. + +Events are declarative like: `{ type: 'USER_CLICKED_FOO' }`, commands are imperative like: `{ type: 'FETCH_FOO()' }`. + +The reaction for event is state reduction (by reducer) which is pure, the reaction for command is executor call which is +unpure. + +Another thing is that events changes state (by reducer), commands not. Because of that command dispatch doesn't call +store listeners (for example it doesn't re-render React application). ## Composition ## TODO @@ -50,9 +62,91 @@ TODO ## Narrowing ## TODO +## API ## +#### combineExecutors #### +``` +type ExecutorsMap = { + [K in keyof S]: Executor; +}; + +function combineExecutors(map: ExecutorsMap): Executor; +``` +Binds executors to state branches and combines them to one executor. Executors will be called in +sequence but promise will be resolved in parallel (by `Promise.all`) Useful for re-usable executors. + +#### createExecutorEnchancer #### +``` +type StoreExecutableEnhancer = (next: StoreEnhancerStoreCreator) => StoreEnhancerStoreExecutableCreator; +type StoreEnhancerStoreExecutableCreator = (reducer: Reducer, preloadedState: S) => ExecutableStore; + +function createExecutorEnhancer(executor: Executor): StoreExecutableEnhancer; +``` +Creates new [redux enhancer](http://redux.js.org/docs/Glossary.html#store-enhancer) that extends redux store api (see [ExecutableStore](#executable-store)) + +#### ExecutableDispatch #### +``` +interface ExecutableDispatch extends Dispatch { + (action: A): A & { promise?: Promise }; +} +``` +It's type of enhanced dispatch method that can add `promise` field to returned action if you dispatch command. + +#### ExecutableStore #### +``` +interface ExecutableStore extends Store { + dispatch: ExecutableDispatch; + replaceExecutor(nextExecutor: Executor): void; +} +``` +It's type of enhanced store that has enhanced dispatch method (see [ExecutableDispatch](#executable-dispatch)) and +`replaceExecutor` method (like `replaceReducer`). + +#### Executor #### +``` +type Executor = (command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined) => Promise | void; +``` +See [Concepts / An Executor](#an-executor) + +#### handleCommand #### +``` +function handleCommand(type: string, executor: Executor): Executor; +``` +Limit executor to given command type (inspired by [redux-actions](https://github.com/acdlite/redux-actions)). + +#### handleCommands #### +``` +type ExecutorPerCommandMap = { + [type: string]: Executor; +}; + +function handleCommands(map: ExecutorPerCommandMap): Executor; +``` +Similar to `handleCommand` but works for multiple commands at once. +Map is an object where key is a command type, value is an executor (inspired by [redux-actions](https://github.com/acdlite/redux-actions)). + +#### isCommand #### +``` +function isCommand(object: any): boolean; +``` +Checks if given object is an command (`object.type` ends with `()` string). + +#### mountExecutor #### +``` +function mountExecutor(selector: (state: S1 | undefined) => S2, executor: Executor): Executor; +``` +Mounts executor to some state branch. Useful for re-usable executors. + +#### reduceExecutors #### +``` +function reduceExecutors(...executors: Executor[]): Executor; +``` +Reduces multiple executors to one. Executors will be called in sequence but promise will be resolved in parallel +(by `Promise.all`). Useful for re-usable executors. + + ## Code Splitting ## Redux Executor provides `replaceExecutor` method on `ExecutableStore` interface (store created by Redux Executor). It's similar to -`replaceReducer` - it changes executor and dispatches `{ type: '@@executor/INIT', command: true }`. +`replaceReducer` - it changes executor and dispatches `{ type: '@@executor/INIT()' }`. ## Typings ## If you are using [TypeScript](https://www.typescriptlang.org/), you don't have to install typings - they are provided in npm package. diff --git a/package.json b/package.json index 94f84f9..6da301e 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "devDependencies": { "@types/mocha": "^2.2.38", "chai": "^3.5.0", + "chai-as-promised": "^6.0.0", "chai-spies": "^0.7.1", "coveralls": "^2.11.15", "cross-env": "^3.1.4", @@ -40,7 +41,7 @@ "karma-coverage": "^1.1.1", "karma-es6-shim": "^1.0.0", "karma-mocha": "^1.3.0", - "karma-phantomjs-launcher": "1.0.2", + "karma-phantomjs-launcher": "^1.0.4", "karma-typescript": "^2.1.7", "karma-webpack": "^2.0.1", "lodash": "^4.17.4", diff --git a/src/ActionLike.ts b/src/ActionLike.ts new file mode 100644 index 0000000..dd24e29 --- /dev/null +++ b/src/ActionLike.ts @@ -0,0 +1,5 @@ +import { Action } from 'redux'; + +export interface ActionLike extends Action { + [key: string]: any; +} diff --git a/src/Executor.ts b/src/Executor.ts index 401149c..09ab696 100644 --- a/src/Executor.ts +++ b/src/Executor.ts @@ -1,13 +1,8 @@ -import { Action } from 'redux'; +import { ActionLike } from './ActionLike'; import { ExecutableDispatch } from './ExecutableDispatch'; /** * Executor is an simple function that executes some side effects on given command. * They can return promise if side effect is asynchronous. */ -export type Executor = (command: A, dispatch: ExecutableDispatch, state: S) => Promise | void; - -/** - * It's executor limited to given action (command) type. - */ -export type NarrowExecutor = (command: A, dispatch: ExecutableDispatch, state: S) => Promise | void; +export type Executor = (command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined) => Promise | void; diff --git a/src/combineExecutors.ts b/src/combineExecutors.ts index 875d55e..db84ae1 100644 --- a/src/combineExecutors.ts +++ b/src/combineExecutors.ts @@ -1,32 +1,25 @@ -import { Action } from 'redux'; import { Executor } from './Executor'; +import { ActionLike } from './ActionLike'; import { ExecutableDispatch } from './ExecutableDispatch'; +export type ExecutorsMap = { + [K in keyof S]?: Executor; +}; + /** - * Combine executors to get one that will call wrapped. + * Combine executors to bind them to the local state. + * It allows to create reusable executors. * - * @param executors Executors to combine - * @returns Executor that wraps all given executors + * @param map Map of executors bounded to state. + * @returns Combined executor */ -export function combineExecutors(...executors: Executor[]): Executor { - // check executors type in runtime - const invalidExecutorsIndexes: number[] = executors - .map((executor, index) => executor instanceof Function ? -1 : index) - .filter(index => index !== -1); - - if (invalidExecutorsIndexes.length) { - throw new Error( - `Invalid arguments: ${invalidExecutorsIndexes.join(', ')} in combineExecutors call.\n` + - `Executors should be a 'function' type, ` + - `'${invalidExecutorsIndexes.map(index => typeof executors[index]).join(`', '`)}' types passed.` - ); - } - - return function combinedExecutor(command: A, dispatch: ExecutableDispatch, state: S): Promise { +export function combineExecutors(map: ExecutorsMap): Executor { + return function combinedExecutor(command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined): Promise { return Promise.all( - executors - .map(executor => executor(command, dispatch, state)) - .filter(promise => !!promise) - ) as Promise; + Object.keys(map).map((key: keyof S) => map[key]!(command, dispatch, state ? state[key] : undefined) || Promise.resolve()) + ).then( + /* istanbul ignore next */ + () => undefined + ); }; } diff --git a/src/createExecutableStore.ts b/src/createExecutableStore.ts deleted file mode 100644 index 8a7d841..0000000 --- a/src/createExecutableStore.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { StoreEnhancer, Reducer, compose, createStore } from 'redux'; -import { Executor } from './Executor'; -import { ExecutableStore } from './ExecutableStore'; -import { createExecutorEnhancer } from './createExecutorEnhancer'; - -/** - * Create store that will be able to handle executors and commands. - * - * @param reducer Main redux reducer - * @param executor Main redux executor - * @param preloadedState State that should be initialized - * @param enhancer Additional enhancer - * @returns Executable Store that will handle commands and executors - */ -export function createExecutableStore( - reducer: Reducer, - executor: Executor, - preloadedState?: S, - enhancer?: StoreEnhancer -): ExecutableStore { - if (enhancer) { - enhancer = compose(createExecutorEnhancer(executor), enhancer); - } else { - enhancer = createExecutorEnhancer(executor); - } - - return createStore(reducer, preloadedState, enhancer) as ExecutableStore; -} diff --git a/src/handleCommand.ts b/src/handleCommand.ts index 214fdad..6e1fb1a 100644 --- a/src/handleCommand.ts +++ b/src/handleCommand.ts @@ -1,6 +1,6 @@ -import { Action } from 'redux'; -import { Executor, NarrowExecutor } from './Executor'; +import { ActionLike } from './ActionLike'; +import { Executor } from './Executor'; import { ExecutableDispatch } from './ExecutableDispatch'; /** @@ -10,7 +10,7 @@ import { ExecutableDispatch } from './ExecutableDispatch'; * @param executor Wrapped executor * @returns Executor that runs wrapped executor only for commands with given type. */ -export function handleCommand(type: string, executor: NarrowExecutor): Executor { +export function handleCommand(type: string, executor: Executor): Executor { if (!type || type.length < 3 || ')' !== type[type.length - 1] || '(' !== type[type.length - 2]) { throw new Error(`Expected type to be valid command type with '()' ending. Given '${type}' type. Maybe typo?`); } @@ -19,9 +19,9 @@ export function handleCommand(type: string, executor: Narro throw new Error('Expected the executor to be a function.'); } - return function wideExecutor(command: Action, dispatch: ExecutableDispatch, state: S): Promise | void { + return function wideExecutor(command: ActionLike, dispatch: ExecutableDispatch, state: S): Promise | void { if (command && command.type === type) { - return executor(command as A, dispatch, state); + return executor(command, dispatch, state); } }; } diff --git a/src/handleCommands.ts b/src/handleCommands.ts new file mode 100644 index 0000000..c9f5dd4 --- /dev/null +++ b/src/handleCommands.ts @@ -0,0 +1,14 @@ + +import { Executor } from './Executor'; +import { handleCommand } from './handleCommand'; +import { reduceExecutors } from './reduceExecutors'; + +export type ExecutorPerCommandMap = { + [type: string]: Executor; +}; + +export function handleCommands(map: ExecutorPerCommandMap): Executor { + return reduceExecutors( + ...Object.keys(map).map(type => handleCommand(type, map[type])) + ); +} diff --git a/src/index.ts b/src/index.ts index 78dfaec..090c527 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,13 @@ // typings export { ExecutableDispatch } from './ExecutableDispatch'; export { ExecutableStore } from './ExecutableStore'; -export { Executor, NarrowExecutor } from './Executor'; +export { Executor } from './Executor'; // implementation +export { reduceExecutors } from './reduceExecutors'; export { combineExecutors } from './combineExecutors'; -export { createExecutableStore } from './createExecutableStore'; export { createExecutorEnhancer, EXECUTOR_INIT } from './createExecutorEnhancer'; export { isCommand } from './isCommand'; export { handleCommand } from './handleCommand'; +export { handleCommands } from './handleCommands'; export { mountExecutor } from './mountExecutor'; diff --git a/src/mountExecutor.ts b/src/mountExecutor.ts index b8f2f3f..e860b53 100644 --- a/src/mountExecutor.ts +++ b/src/mountExecutor.ts @@ -1,4 +1,4 @@ -import { Action } from 'redux'; +import { ActionLike } from './ActionLike'; import { Executor } from './Executor'; import { ExecutableDispatch } from './ExecutableDispatch'; @@ -9,7 +9,7 @@ import { ExecutableDispatch } from './ExecutableDispatch'; * @param executor Executor that runs on substate * @returns Executor that runs on state */ -export function mountExecutor(selector: (state: S1) => S2, executor: Executor): Executor { +export function mountExecutor(selector: (state: S1 | undefined) => S2 | undefined, executor: Executor): Executor { if (typeof selector !== 'function') { throw new Error('Expected the selector to be a function.'); } @@ -18,7 +18,7 @@ export function mountExecutor(selector: (state: S1) => S2, executor: Exe throw new Error('Expected the executor to be a function.'); } - return function mountedExecutor(command: A, dispatch: ExecutableDispatch, state: S1): Promise | void { + return function mountedExecutor(command: ActionLike, dispatch: ExecutableDispatch, state: S1 | undefined): Promise | void { return executor(command, dispatch, selector(state)); }; } diff --git a/src/reduceExecutors.ts b/src/reduceExecutors.ts new file mode 100644 index 0000000..8770129 --- /dev/null +++ b/src/reduceExecutors.ts @@ -0,0 +1,32 @@ +import { ActionLike } from './ActionLike'; +import { Executor } from './Executor'; +import { ExecutableDispatch } from './ExecutableDispatch'; + +/** + * Reduce executors to get one that will call wrapped. + * + * @param executors Executors to reduce + * @returns Executor that wraps all given executors + */ +export function reduceExecutors(...executors: Executor[]): Executor { + // check executors type in runtime + const invalidExecutorsIndexes: number[] = executors + .map((executor, index) => executor instanceof Function ? -1 : index) + .filter(index => index !== -1); + + if (invalidExecutorsIndexes.length) { + throw new Error( + `Invalid arguments: ${invalidExecutorsIndexes.join(', ')} in combineExecutors call.\n` + + `Executors should be a 'function' type, ` + + `'${invalidExecutorsIndexes.map(index => typeof executors[index]).join(`', '`)}' types passed.` + ); + } + + return function reducedExecutor(command: ActionLike, dispatch: ExecutableDispatch, state: S): Promise { + return Promise.all( + executors + .map(executor => executor(command, dispatch, state)) + .filter(promise => !!promise) + ) as Promise; + }; +} diff --git a/test/combineExecutors.spec.ts b/test/combineExecutors.spec.ts index 36ef210..8cf456d 100644 --- a/test/combineExecutors.spec.ts +++ b/test/combineExecutors.spec.ts @@ -1,10 +1,12 @@ import * as chai from 'chai'; import * as spies from 'chai-spies'; +import * as promised from 'chai-as-promised'; import { expect } from 'chai'; import { combineExecutors } from '../src/index'; chai.use(spies); +chai.use(promised); describe('combineExecutors', () => { it('should export combineExecutors function', () => { @@ -12,84 +14,100 @@ describe('combineExecutors', () => { }); it('should return valid executor for combination of two executors', () => { - let promiseAResolve, promiseAReject; - let promiseBResolve, promiseBReject; + function executorResolved() { + return Promise.resolve(); + } - function executorA() { - return new Promise((resolve, reject) => { promiseAResolve = resolve; promiseAReject = reject; }); + function executorRejected() { + return Promise.reject(new Error()); } - function executorB() { - return new Promise((resolve, reject) => { promiseBResolve = resolve; promiseBReject = reject; }); + function executorVoid() { } - const executorAB = combineExecutors(executorA, executorB); + const executorABC = combineExecutors({ + a: executorResolved, + b: executorResolved, + c: executorVoid + }); - expect(executorAB).to.be.function; - let promise = executorAB({ type: 'FOO()' }, () => {}, {}); + expect(executorABC).to.be.function; + let promiseABC: Promise = executorABC({ type: 'FOO()' }, () => {}, {}) as Promise; let thenSpy = chai.spy(); let catchSpy = chai.spy(); - expect(promise).to.exist; - expect((promise as Promise).then).to.be.function; - expect((promise as Promise).catch).to.be.function; - - (promise as Promise).then(thenSpy).catch(catchSpy); - - // check promises combination - expect(thenSpy).to.not.have.been.called; - expect(catchSpy).to.not.have.been.called; + expect(promiseABC).to.exist; + expect(promiseABC.then).to.be.function; + expect(promiseABC.catch).to.be.function; - promiseAResolve(); - - expect(thenSpy).to.not.have.been.called; - expect(catchSpy).to.not.have.been.called; - - promiseBResolve(); - - expect(thenSpy).to.have.been.called; - expect(catchSpy).to.have.been.called; + expect(promiseABC).to.be.fulfilled; + expect(promiseABC).to.become(undefined); }); it('should return executor that rejects on children reject', () => { - let promiseAResolve, promiseAReject; - let promiseBResolve, promiseBReject; - - function executorA() { - return new Promise((resolve, reject) => { promiseAResolve = resolve; promiseAReject = reject; }); + function executorResolved() { + return Promise.resolve(); } - function executorB() { - return new Promise((resolve, reject) => { promiseBResolve = resolve; promiseBReject = reject; }); + function executorRejected() { + return Promise.reject(new Error()); } - const executorAB = combineExecutors(executorA, executorB); + function executorVoid() { + } - expect(executorAB).to.be.function; - let promise = executorAB({ type: 'FOO()' }, () => {}, {}); + const executorABC = combineExecutors({ + a: executorResolved, + b: executorRejected, + c: executorVoid + }); - let thenSpy = chai.spy(); - let catchSpy = chai.spy(); + expect(executorABC).to.be.function; + let promise: Promise = executorABC({ type: 'FOO()' }, () => {}, {}) as Promise; expect(promise).to.exist; - expect((promise as Promise).then).to.be.function; - expect((promise as Promise).catch).to.be.function; - - (promise as Promise).then(thenSpy).catch(catchSpy); + expect(promise.then).to.be.function; + expect(promise.catch).to.be.function; - // check promises combination - expect(thenSpy).to.not.have.been.called; - expect(catchSpy).to.not.have.been.called; - - promiseAReject(); - - expect(thenSpy).to.not.have.been.called; - expect(catchSpy).to.have.been.called; + expect(promise).to.be.rejected; }); - it('should throw an exception for call with invalid argument', () => { - expect(() => { (combineExecutors as any)({ 'foo': 'bar' }); }).to.throw(Error); - expect(() => { (combineExecutors as any)([function() {}, undefined]); }).to.throw(Error); + it('should pass sub-state to sub-executors', () => { + const state = { + a: 'foo', + b: 'bar' + }; + const executorA = chai.spy(); + const executorB = chai.spy(); + const dispatch = chai.spy(); + + const executorAB = combineExecutors({ + a: executorA, + b: executorB + }); + + executorAB({ type: 'FOO()' }, dispatch, state); + expect(executorA).to.have.been.called.with({ type: 'FOO()' }, dispatch, state.a); + expect(executorB).to.have.been.called.with({ type: 'FOO()' }, dispatch, state.b); + }); + it('should pass sub-state to sub-executors for undefined state', () => { + const executorA = chai.spy(); + const executorB = chai.spy(); + const dispatch = chai.spy(); + + const executorAB = combineExecutors({ + a: executorA, + b: executorB + }); + + executorAB({ type: 'FOO()' }, dispatch, undefined); + expect(executorA).to.have.been.called.with({ type: 'FOO()' }, dispatch, undefined); + expect(executorB).to.have.been.called.with({ type: 'FOO()' }, dispatch, undefined); }); + // + // it('should throw an exception for call with invalid argument', () => { + // expect(() => { (reduceExecutors as any)({ 'foo': 'bar' }); }).to.throw(Error); + // expect(() => { (reduceExecutors as any)([function() {}, undefined]); }).to.throw(Error); + // }); }); diff --git a/test/createExecutableStore.spec.ts b/test/createExecutableStore.spec.ts deleted file mode 100644 index 18c9677..0000000 --- a/test/createExecutableStore.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ - -import { expect } from 'chai'; -import { createExecutableStore } from '../src/index'; - -describe('createExecutableStore', () => { - - it('should export createExecutableStore function', () => { - expect(createExecutableStore).to.be.function; - }); - - it('should create new redux store without enhancer', () => { - function dumbReducer(state) { - return state; - } - function dumbExecutor() { - } - const dumbState = {}; - const detectableStore = createExecutableStore(dumbReducer, dumbExecutor, dumbState); - - expect(detectableStore).to.be.object; - expect(detectableStore.dispatch).to.be.function; - expect(detectableStore.subscribe).to.be.function; - expect(detectableStore.getState).to.be.function; - expect(detectableStore.replaceReducer).to.be.function; - expect(detectableStore.replaceExecutor).to.be.function; - }); - - it('should create new redux store with enhancer', () => { - function dumbReducer(state) { - return state; - } - function dumbExecutor() { - } - const dumbState = {}; - const dumbEnhancer = function(next) { return next; }; - const detectableStore = createExecutableStore(dumbReducer, dumbExecutor, dumbState, dumbEnhancer); - - expect(detectableStore).to.be.object; - expect(detectableStore.dispatch).to.be.function; - expect(detectableStore.subscribe).to.be.function; - expect(detectableStore.getState).to.be.function; - expect(detectableStore.replaceReducer).to.be.function; - expect(detectableStore.replaceExecutor).to.be.function; - }); -}); diff --git a/test/handleCommand.spec.ts b/test/handleCommand.spec.ts index 4228049..b12f112 100644 --- a/test/handleCommand.spec.ts +++ b/test/handleCommand.spec.ts @@ -6,7 +6,7 @@ import { handleCommand } from '../src/index'; chai.use(spies); -describe('combineExecutors', () => { +describe('handleCommand', () => { it('should export handleCommand function', () => { expect(handleCommand).to.be.function; }); diff --git a/test/handleCommands.spec.ts b/test/handleCommands.spec.ts new file mode 100644 index 0000000..b9c92cd --- /dev/null +++ b/test/handleCommands.spec.ts @@ -0,0 +1,51 @@ + +import * as chai from 'chai'; +import * as spies from 'chai-spies'; +import { expect } from 'chai'; +import { handleCommands } from '../src/index'; + +chai.use(spies); + +describe('handleCommands', () => { + it('should export handleCommand function', () => { + expect(handleCommands).to.be.function; + }); + + it('should return executor that runs only for given command types', () => { + const executorSpyA = chai.spy(); + const executorSpyB = chai.spy(); + const dispatchSpy = chai.spy(); + const dumbState = {}; + + const targetedExecutor = handleCommands({ + 'COMMAND_TYPE_A()': executorSpyA, + 'COMMAND_TYPE_B()': executorSpyB, + }); + + expect(targetedExecutor).to.be.function; + expect(executorSpyA).to.not.have.been.called; + expect(executorSpyB).to.not.have.been.called; + + // expect that executor will bypass this command + targetedExecutor({ type: 'ANOTHER_COMMAND_TYPE()' }, dispatchSpy, dumbState); + expect(executorSpyA).to.not.have.been.called; + expect(executorSpyB).to.not.have.been.called; + expect(dispatchSpy).to.not.have.been.called; + + // expect that executor will bypass similar non command + targetedExecutor({ type: 'COMMAND_TYPE_A' }, dispatchSpy, dumbState); + expect(executorSpyA).to.not.have.been.called; + expect(executorSpyB).to.not.have.been.called; + expect(dispatchSpy).to.not.have.been.called; + + // expect that executor will call wrapped executor A + targetedExecutor({ type: 'COMMAND_TYPE_A()' }, dispatchSpy, dumbState); + expect(executorSpyA).to.have.been.called.with({ type: 'COMMAND_TYPE_A()' }, dispatchSpy, dumbState); + expect(executorSpyB).to.not.have.been.called; + expect(dispatchSpy).to.have.been.called; + + // expect that executor will call wrapped executor B + targetedExecutor({ type: 'COMMAND_TYPE_B()' }, dispatchSpy, dumbState); + expect(executorSpyB).to.have.been.called.with({ type: 'COMMAND_TYPE_B()' }, dispatchSpy, dumbState); + }); +}); diff --git a/test/isCommand.spec.ts b/test/isCommand.spec.ts index 9d3b85b..20c601b 100644 --- a/test/isCommand.spec.ts +++ b/test/isCommand.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { isCommand } from '../src/index'; -describe('combineExecutors', () => { +describe('reduceExecutors', () => { it('should export isCommand function', () => { expect(isCommand).to.be.function; }); diff --git a/test/reduceExecutors.spec.ts b/test/reduceExecutors.spec.ts new file mode 100644 index 0000000..6db58f8 --- /dev/null +++ b/test/reduceExecutors.spec.ts @@ -0,0 +1,95 @@ + +import * as chai from 'chai'; +import * as spies from 'chai-spies'; +import { expect } from 'chai'; +import { reduceExecutors } from '../src/index'; + +chai.use(spies); + +describe('reduceExecutors', () => { + it('should export reduceExecutors function', () => { + expect(reduceExecutors).to.be.function; + }); + + it('should return valid executor for reduction of two executors', () => { + let promiseAResolve, promiseAReject; + let promiseBResolve, promiseBReject; + + function executorA() { + return new Promise((resolve, reject) => { promiseAResolve = resolve; promiseAReject = reject; }); + } + + function executorB() { + return new Promise((resolve, reject) => { promiseBResolve = resolve; promiseBReject = reject; }); + } + + const executorAB = reduceExecutors(executorA, executorB); + + expect(executorAB).to.be.function; + let promise = executorAB({ type: 'FOO()' }, () => {}, {}); + + let thenSpy = chai.spy(); + let catchSpy = chai.spy(); + + expect(promise).to.exist; + expect((promise as Promise).then).to.be.function; + expect((promise as Promise).catch).to.be.function; + + (promise as Promise).then(thenSpy).catch(catchSpy); + + // check promises combination + expect(thenSpy).to.not.have.been.called; + expect(catchSpy).to.not.have.been.called; + + promiseAResolve(); + + expect(thenSpy).to.not.have.been.called; + expect(catchSpy).to.not.have.been.called; + + promiseBResolve(); + + expect(thenSpy).to.have.been.called; + expect(catchSpy).to.have.been.called; + }); + + it('should return executor that rejects on children reject', () => { + let promiseAResolve, promiseAReject; + let promiseBResolve, promiseBReject; + + function executorA() { + return new Promise((resolve, reject) => { promiseAResolve = resolve; promiseAReject = reject; }); + } + + function executorB() { + return new Promise((resolve, reject) => { promiseBResolve = resolve; promiseBReject = reject; }); + } + + const executorAB = reduceExecutors(executorA, executorB); + + expect(executorAB).to.be.function; + let promise = executorAB({ type: 'FOO()' }, () => {}, {}); + + let thenSpy = chai.spy(); + let catchSpy = chai.spy(); + + expect(promise).to.exist; + expect((promise as Promise).then).to.be.function; + expect((promise as Promise).catch).to.be.function; + + (promise as Promise).then(thenSpy).catch(catchSpy); + + // check promises combination + expect(thenSpy).to.not.have.been.called; + expect(catchSpy).to.not.have.been.called; + + promiseAReject(); + + expect(thenSpy).to.not.have.been.called; + expect(catchSpy).to.have.been.called; + }); + + it('should throw an exception for call with invalid argument', () => { + expect(() => { (reduceExecutors as any)({ 'foo': 'bar' }); }).to.throw(Error); + expect(() => { (reduceExecutors as any)([function() {}, undefined]); }).to.throw(Error); + }); +}); diff --git a/yarn.lock b/yarn.lock index e64dd16..15d4586 100644 --- a/yarn.lock +++ b/yarn.lock @@ -560,6 +560,12 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" +chai-as-promised@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-6.0.0.tgz#1a02a433a6f24dafac63b9c96fa1684db1aa8da6" + dependencies: + check-error "^1.0.2" + chai-spies@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/chai-spies/-/chai-spies-0.7.1.tgz#343d99f51244212e8b17e64b93996ff7b2c2a9b1" @@ -582,6 +588,10 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + chokidar@^1.4.1, chokidar@^1.4.3: version "1.6.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2" @@ -1912,9 +1922,9 @@ karma-mocha@^1.3.0: dependencies: minimist "1.2.0" -karma-phantomjs-launcher@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.2.tgz#19e1041498fd75563ed86730a22c1fe579fa8fb1" +karma-phantomjs-launcher@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.4.tgz#d23ca34801bda9863ad318e3bb4bd4062b13acd2" dependencies: lodash "^4.0.1" phantomjs-prebuilt "^2.1.7" @@ -2512,7 +2522,7 @@ oauth-sign@~0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" -object-assign@4.1.0, object-assign@^4.0.1, object-assign@^4.1.0: +object-assign@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" @@ -2520,7 +2530,7 @@ object-assign@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" -object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" From 18cbc09be4c106ff46018b35ab7313ea3b01f0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ole=C5=9B?= Date: Wed, 12 Apr 2017 20:04:14 +0200 Subject: [PATCH 02/10] 0.5.3 --- README.md | 80 ++++++++++++++++++++++++++++++++++++++++++---------- package.json | 2 +- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index fdfd25a..752757e 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,39 @@ There are many clever solutions to deal with side-effects in redux application l or [redux-saga](https://github.com/redux-saga/redux-saga). The goal of this library is to be simpler than **redux-saga** and also easier to test and more pure than **redux-thunk**. +Typical usage of executor is to fetch some external resource, for example list of posts. It can look like this: +```js +import { handleCommand } from 'redux-executor'; +import { postApi } from './api/postApi'; +import { postsRequested, postsResolved, postsRejected } from './actions/postActions'; + +// postsExecutor.js +function fetchPostsExecutor(command, dispatch, state) { + dispatch(postsRequested()); + + return postApi.list(command.payload.page) + .then((posts) => dispatch(postsResolved(posts))) + .catch((error) => dispatch(postsRejected(error))); +} + +export default handleCommand('FETCH_POSTS()', fetchPostsExecutor); + +// somwhere else in code +dispatch(fetchPosts(this.state.page)); +``` +So what is the difference between executor and thunk? With executors you have separation between side-effect _request_ and +side-effect _call_. It means that you can omit second phase and don't call side-effect (if you not bind executor to the store). +With this design it's very easy to write unit tests. If you use [Redux DevTools](https://github.com/gaearon/redux-devtools) +it will be also easier to debug - all commands will be logged in debugger. + +I recommend to use redux-executor with [redux-detector](https://github.com/piotr-oles/redux-detector) library. +In this combination you can for example detect if client is on given url and dispatch fetch command. +All, excluding executors, will be pure. + ## Concepts ## ### An Executor ### It may sounds a little bit scary but there is nothing to fear - executor is very pure and simple function. -``` +```typescript type Executor = (command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined) => Promise | void; ``` Like you see above, executor takes an action (called **command** in executors), enhanced `dispatch` function and state. @@ -57,14 +86,35 @@ Another thing is that events changes state (by reducer), commands not. Because o store listeners (for example it doesn't re-render React application). ## Composition ## -TODO - -## Narrowing ## -TODO +You can pass only one executor to the store, but with `combineExecutors` and `reduceExecutors` you can mix them to +one executor. For example: +```js +import { combineExecutors, reduceExecutors } from 'redux-executor'; +import { fooExecutor } from './fooExecutor'; +import { barExecutor } from './barExecutor'; +import { anotherExecutor } from './anotherExecutor'; + +// our state has shape: +// { +// foo: [], +// bar: 1 +// } +// +// We want to bind `fooExecutor` and `anotherExecutor` to `state.foo` branch (they should run in sequence) +// and also `barExecutor` to `state.bar` branch. + +export default combineExecutors({ + foo: reduceExecutors( + fooExecutor, + anotherExecutor + ), + bar: barExecutor +}); +``` ## API ## #### combineExecutors #### -``` +```typescript type ExecutorsMap = { [K in keyof S]: Executor; }; @@ -75,7 +125,7 @@ Binds executors to state branches and combines them to one executor. Executors w sequence but promise will be resolved in parallel (by `Promise.all`) Useful for re-usable executors. #### createExecutorEnchancer #### -``` +```typescript type StoreExecutableEnhancer = (next: StoreEnhancerStoreCreator) => StoreEnhancerStoreExecutableCreator; type StoreEnhancerStoreExecutableCreator = (reducer: Reducer, preloadedState: S) => ExecutableStore; @@ -84,7 +134,7 @@ function createExecutorEnhancer(executor: Executor): StoreExecutableEnhanc Creates new [redux enhancer](http://redux.js.org/docs/Glossary.html#store-enhancer) that extends redux store api (see [ExecutableStore](#executable-store)) #### ExecutableDispatch #### -``` +```typescript interface ExecutableDispatch extends Dispatch { (action: A): A & { promise?: Promise }; } @@ -92,7 +142,7 @@ interface ExecutableDispatch extends Dispatch { It's type of enhanced dispatch method that can add `promise` field to returned action if you dispatch command. #### ExecutableStore #### -``` +```typescript interface ExecutableStore extends Store { dispatch: ExecutableDispatch; replaceExecutor(nextExecutor: Executor): void; @@ -102,19 +152,19 @@ It's type of enhanced store that has enhanced dispatch method (see [ExecutableDi `replaceExecutor` method (like `replaceReducer`). #### Executor #### -``` +```typescript type Executor = (command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined) => Promise | void; ``` See [Concepts / An Executor](#an-executor) #### handleCommand #### -``` +```typescript function handleCommand(type: string, executor: Executor): Executor; ``` Limit executor to given command type (inspired by [redux-actions](https://github.com/acdlite/redux-actions)). #### handleCommands #### -``` +```typescript type ExecutorPerCommandMap = { [type: string]: Executor; }; @@ -125,19 +175,19 @@ Similar to `handleCommand` but works for multiple commands at once. Map is an object where key is a command type, value is an executor (inspired by [redux-actions](https://github.com/acdlite/redux-actions)). #### isCommand #### -``` +```typescript function isCommand(object: any): boolean; ``` Checks if given object is an command (`object.type` ends with `()` string). #### mountExecutor #### -``` +```typescript function mountExecutor(selector: (state: S1 | undefined) => S2, executor: Executor): Executor; ``` Mounts executor to some state branch. Useful for re-usable executors. #### reduceExecutors #### -``` +```typescript function reduceExecutors(...executors: Executor[]): Executor; ``` Reduces multiple executors to one. Executors will be called in sequence but promise will be resolved in parallel diff --git a/package.json b/package.json index 6da301e..e1cadf1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redux-executor", - "version": "0.5.2", + "version": "0.5.3", "description": "Redux enhancer for handling side effects.", "main": "lib/index.js", "types": "lib/index.d.ts", From 33da336a78da7e60973397fc46fc93272a0fc1fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ole=C5=9B?= Date: Wed, 12 Apr 2017 23:23:44 +0200 Subject: [PATCH 03/10] Redux DevTools integration instruction --- README.md | 100 +++- package.json | 24 +- src/createExecutorEnhancer.ts | 1 - webpack.config.js | 23 - yarn.lock | 954 +++++++++------------------------- 5 files changed, 366 insertions(+), 736 deletions(-) diff --git a/README.md b/README.md index 752757e..0033dfb 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,23 @@ const store = createStore( ); ``` +#### Redux DevTools #### +To use redux-executor with Redux DevTools, you have to be careful about enhancers order. It's because redux-executor blocks +commands to next enhancers so it has to be placed after DevTools (to see commands). + +```js +const devToolsCompose = window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? + window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose; + +const enhancer = compose( + devToolsCompose( + // ...some enhancers + applyMiddleware(/* ...some middlewares */) + ), + createExecutorEnhancer(rootExecutor) +); +``` + ## Motivation ## There are many clever solutions to deal with side-effects in redux application like [redux-thunk](https://github.com/gaearon/redux-thunk) or [redux-saga](https://github.com/redux-saga/redux-saga). The goal of this library is to be simpler than **redux-saga** @@ -38,7 +55,8 @@ Typical usage of executor is to fetch some external resource, for example list o ```js import { handleCommand } from 'redux-executor'; import { postApi } from './api/postApi'; -import { postsRequested, postsResolved, postsRejected } from './actions/postActions'; +// import action creators +import { postsRequested, postsResolved, postsRejected } from './actions/postActions'; // postsExecutor.js function fetchPostsExecutor(command, dispatch, state) { @@ -112,6 +130,86 @@ export default combineExecutors({ }); ``` +## Execution order ## +Sometimes we want to dispatch actions in proper order. To do this, we have to return promise from executors we want +to include to our execution order. If we dispatch **command**, dispatch method will return action (it's redux behaviour) with +additional `promise` field that contains promise of our side-effects. Keep in mind that this promise is the result of +calling all combined or reduced executors (built-in implementations uses `Promise.all`, see [reduceExecutors](#reduceexecutors), +[combineExecutors](#combineexecutors)). Because of that you should not rely on promise content - in fact it should +be undefined. + +Lets say that we want to run `firstCommand` and then `secondCommand` and `thirdCommand` in parallel. +The easiest solution is: +```js +// import action creators +import { firstCommand, secondCommand, thirdCommand } from './commands/exampleCommands'; + +function firstThenNextExecutor(command, dispatch, state) { + return dispatch(firstCommand()).promise + .then(() => Promise.all([ + dispatch(secondCommand()).promise, + dispatch(thirdCommand()).promise + ])); +} + +export default handleCommand('FIRST_THEN_NEXT()', firstThenNextExecutor); +``` + +To be more declarative and to reduce boilerplate code, you can create generic `sequenceCommandExecutor` and +`parallelCommandExecutor`. + +```js +// executionFlowExecutors.js +import { handleCommand } from 'redux-executor'; + +export const sequenceCommandExecutor = handleCommand( + 'SEQUENCE()', + (command, dispatch) => command.payload.reduce( + (promise, command) => promise.then(() => dispatch(command).promise || Promise.resolve()), + Promise.resolve() + ) +); + +export const parallelCommandExecutor = handleCommand( + 'PARALLEL()', + (command, dispatch) => Promise.all( + command.payload.map(command => dispatch(command).promise || Promise.resolve()) + ).then(() => undefined) // we should return Promise because we should not rely on promise result +); +``` + +With this commands we can create action creator instead of executor for `firstCommand`, `secondCommand` and +`thirdCommand` example. +```js +// import action creators +import { firstCommand, secondCommand, thirdCommand } from './commands/exampleCommands'; +import { sequenceCommand, parallelCommand } from './commands/executionOrderCommands'; + +export default function firstThenNext() { + return sequenceCommand([ + firstCommand(), + parallelCommand([ + secondCommand(), + thirdCommand() + ]) + ]); +} +// it will return +// { +// type: 'SEQUENCE()', +// payload: [ +// { type: 'FIRST()' }, +// { +// type: 'PARALLEL()', +// payload: [ +// { type: 'SECOND()' }, +// { type: 'THIRD()' } +// ] +// } +// ] +// } +``` + ## API ## #### combineExecutors #### ```typescript diff --git a/package.json b/package.json index e1cadf1..27ccd07 100644 --- a/package.json +++ b/package.json @@ -29,30 +29,30 @@ "url": "https://github.com/piotr-oles/redux-executor/issues" }, "devDependencies": { - "@types/mocha": "^2.2.38", + "@types/mocha": "^2.2.40", "chai": "^3.5.0", "chai-as-promised": "^6.0.0", "chai-spies": "^0.7.1", - "coveralls": "^2.11.15", - "cross-env": "^3.1.4", - "karma": "^1.3.0", + "coveralls": "^2.13.0", + "cross-env": "^4.0.0", + "karma": "^1.6.0", "karma-chai": "^0.1.0", "karma-cli": "^1.0.1", "karma-coverage": "^1.1.1", "karma-es6-shim": "^1.0.0", "karma-mocha": "^1.3.0", "karma-phantomjs-launcher": "^1.0.4", - "karma-typescript": "^2.1.7", - "karma-webpack": "^2.0.1", + "karma-typescript": "^3.0.0", + "karma-webpack": "^2.0.3", "lodash": "^4.17.4", "mocha": "^3.2.0", "redux": "^3.1.0", - "rimraf": "^2.5.4", - "ts-loader": "^2.0.0", - "tslint": "^4.4.2", - "tslint-loader": "^3.4.2", - "typescript": "^2.1.6", - "webpack": "2.2.1" + "rimraf": "^2.6.1", + "ts-loader": "^2.0.3", + "tslint": "^5.1.0", + "tslint-loader": "^3.5.2", + "typescript": "^2.2.2", + "webpack": "^2.3.3" }, "peerDependencies": { "redux": "^3.1.0" diff --git a/src/createExecutorEnhancer.ts b/src/createExecutorEnhancer.ts index b83b689..111e6a3 100644 --- a/src/createExecutorEnhancer.ts +++ b/src/createExecutorEnhancer.ts @@ -25,7 +25,6 @@ export function createExecutorEnhancer(executor: Executor): StoreExecutabl const store: Store = next(reducer, preloadedState); // set initial values in this scope - let prevState: S | undefined = preloadedState; let currentExecutor: Executor = executor; // executable store adds `replaceExecutor` method to it's interface diff --git a/webpack.config.js b/webpack.config.js index 10c2600..8042bce 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,8 +2,6 @@ var path = require('path'); var webpack = require('webpack'); var process = require('process'); -var env = process.env.NODE_ENV; - var config = { context: __dirname, target: 'web', @@ -56,25 +54,4 @@ var config = { } }; -if ('production' === env) { - config.plugins.push( - new webpack.optimize.UglifyJsPlugin({ - sourceMap: true, - compressor: { - pure_getters: true, - unsafe: true, - unsafe_comps: true, - warnings: false, - screw_ie8: false - }, - mangle: { - screw_ie8: false - }, - output: { - screw_ie8: false - } - }) - ); -} - module.exports = config; diff --git a/yarn.lock b/yarn.lock index 15d4586..0fa0da2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,16 +2,9 @@ # yarn lockfile v1 -"@types/mocha@^2.2.38": - version "2.2.39" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.39.tgz#f68d63db8b69c38e9558b4073525cf96c4f7a829" - -JSONStream@^1.0.3: - version "1.3.0" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.0.tgz#680ab9ac6572a8a1a207e0b38721db1c77b215e5" - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" +"@types/mocha@^2.2.40": + version "2.2.40" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.40.tgz#9811dd800ece544cd84b5b859917bf584a150c4c" abbrev@1, abbrev@1.0.x: version "1.0.9" @@ -30,18 +23,6 @@ acorn-dynamic-import@^2.0.0: dependencies: acorn "^4.0.3" -acorn@^1.0.3: - version "1.2.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014" - -acorn@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7" - -acorn@^3.1.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" - acorn@^4.0.3, acorn@^4.0.4: version "4.0.11" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0" @@ -73,12 +54,6 @@ amdefine@1.0.0, amdefine@>=0.0.4, amdefine@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.0.tgz#fd17474700cb5cc9c2b709f0be9d23ce3c198c33" -ansi-align@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-1.1.0.tgz#2f0c1658829739add5ebb15e6b0c6e3423f016ba" - dependencies: - string-width "^1.0.1" - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -125,22 +100,10 @@ array-differ@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" -array-filter@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" - array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" -array-map@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" - -array-reduce@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" - array-slice@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" @@ -181,13 +144,7 @@ assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" -assert@^1.1.1, assert@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - dependencies: - util "0.10.3" - -assert@~1.3.0: +assert@^1.1.1, assert@~1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/assert/-/assert-1.3.0.tgz#03939a622582a812cc202320a0b9a56c9b815849" dependencies: @@ -197,12 +154,6 @@ assertion-error@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" -astw@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astw/-/astw-2.0.0.tgz#08121ac8288d35611c0ceec663f6cd545604897d" - dependencies: - acorn "^1.0.3" - async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -211,7 +162,7 @@ async@1.x, async@^1.4.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.1.2: +async@^2.1.2, async@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4" dependencies: @@ -237,7 +188,7 @@ aws4@^1.2.1: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -babel-code-frame@^6.20.0: +babel-code-frame@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" dependencies: @@ -307,18 +258,18 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" -body-parser@^1.12.4: - version "1.16.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.16.0.tgz#924a5e472c6229fb9d69b85a20d5f2532dec788b" +body-parser@^1.16.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.1.tgz#75b3bc98ddd6e7e0d8ffe750dfaca5c66993fa47" dependencies: bytes "2.4.0" content-type "~1.0.2" - debug "2.6.0" + debug "2.6.1" depd "~1.1.0" - http-errors "~1.5.1" + http-errors "~1.6.1" iconv-lite "0.4.15" on-finished "~2.3.0" - qs "6.2.1" + qs "6.4.0" raw-body "~2.2.0" type-is "~1.6.14" @@ -328,20 +279,6 @@ boom@2.x.x: dependencies: hoek "2.x.x" -boxen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-0.6.0.tgz#8364d4248ac34ff0ef1b2f2bf49a6c60ce0d81b6" - dependencies: - ansi-align "^1.1.0" - camelcase "^2.1.0" - chalk "^1.1.1" - cli-boxes "^1.0.0" - filled-array "^1.0.0" - object-assign "^4.0.1" - repeating "^2.0.0" - string-width "^1.0.1" - widest-line "^1.0.0" - brace-expansion@^1.0.0: version "1.1.6" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" @@ -367,17 +304,7 @@ brorand@^1.0.1: version "1.0.7" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.0.7.tgz#6677fa5e4901bdbf9c9ec2a748e28dca407a9bfc" -browser-pack@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.0.2.tgz#f86cd6cef4f5300c8e63e07a4d512f65fbff4531" - dependencies: - JSONStream "^1.0.3" - combine-source-map "~0.7.1" - defined "^1.0.0" - through2 "^2.0.0" - umd "^3.0.0" - -browser-resolve@^1.11.0, browser-resolve@^1.7.0: +browser-resolve@^1.11.0: version "1.11.2" resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" dependencies: @@ -438,58 +365,6 @@ browserify-zlib@^0.1.4, browserify-zlib@~0.1.2: dependencies: pako "~0.2.0" -browserify@^13.1.1: - version "13.3.0" - resolved "https://registry.yarnpkg.com/browserify/-/browserify-13.3.0.tgz#b5a9c9020243f0c70e4675bec8223bc627e415ce" - dependencies: - JSONStream "^1.0.3" - assert "^1.4.0" - browser-pack "^6.0.1" - browser-resolve "^1.11.0" - browserify-zlib "~0.1.2" - buffer "^4.1.0" - cached-path-relative "^1.0.0" - concat-stream "~1.5.1" - console-browserify "^1.1.0" - constants-browserify "~1.0.0" - crypto-browserify "^3.0.0" - defined "^1.0.0" - deps-sort "^2.0.0" - domain-browser "~1.1.0" - duplexer2 "~0.1.2" - events "~1.1.0" - glob "^7.1.0" - has "^1.0.0" - htmlescape "^1.1.0" - https-browserify "~0.0.0" - inherits "~2.0.1" - insert-module-globals "^7.0.0" - labeled-stream-splicer "^2.0.0" - module-deps "^4.0.8" - os-browserify "~0.1.1" - parents "^1.0.1" - path-browserify "~0.0.0" - process "~0.11.0" - punycode "^1.3.2" - querystring-es3 "~0.2.0" - read-only-stream "^2.0.0" - readable-stream "^2.0.2" - resolve "^1.1.4" - shasum "^1.0.0" - shell-quote "^1.6.1" - stream-browserify "^2.0.0" - stream-http "^2.0.0" - string_decoder "~0.10.0" - subarg "^1.0.0" - syntax-error "^1.1.1" - through2 "^2.0.0" - timers-browserify "^1.0.1" - tty-browserify "~0.0.0" - url "~0.11.0" - util "~0.10.1" - vm-browserify "~0.0.1" - xtend "^4.0.0" - buffer-shims@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" @@ -518,10 +393,6 @@ bytes@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" -cached-path-relative@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.0.tgz#d1094c577fbd9a8b8bd43c96af6188aa205d05f4" - callsite@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" @@ -537,7 +408,7 @@ camelcase@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" -camelcase@^2.0.0, camelcase@^2.1.0: +camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" @@ -545,10 +416,6 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" -capture-stack-trace@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" - caseless@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" @@ -613,10 +480,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1: dependencies: inherits "^2.0.1" -cli-boxes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" - cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -659,15 +522,6 @@ combine-lists@^1.0.0: dependencies: lodash "^4.5.0" -combine-source-map@~0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.7.2.tgz#0870312856b307a87cc4ac486f3a9a62aeccc09e" - dependencies: - convert-source-map "~1.1.0" - inline-source-map "~0.6.0" - lodash.memoize "~3.0.3" - source-map "~0.5.3" - combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" @@ -708,34 +562,12 @@ concat-stream@1.5.0: readable-stream "~2.0.0" typedarray "~0.0.5" -concat-stream@~1.5.0, concat-stream@~1.5.1: - version "1.5.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266" - dependencies: - inherits "~2.0.1" - readable-stream "~2.0.0" - typedarray "~0.0.5" - -configstore@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1" - dependencies: - dot-prop "^3.0.0" - graceful-fs "^4.1.2" - mkdirp "^0.5.0" - object-assign "^4.0.1" - os-tmpdir "^1.0.0" - osenv "^0.1.0" - uuid "^2.0.1" - write-file-atomic "^1.1.2" - xdg-basedir "^2.0.0" - -connect@^3.3.5: - version "3.5.0" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.5.0.tgz#b357525a0b4c1f50599cd983e1d9efeea9677198" +connect@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.0.tgz#f09a4f7dcd17324b663b725c815bdb1c4158a46e" dependencies: - debug "~2.2.0" - finalhandler "0.5.0" + debug "2.6.1" + finalhandler "1.0.0" parseurl "~1.3.1" utils-merge "1.0.0" @@ -757,10 +589,6 @@ content-type@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" -convert-source-map@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" - cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" @@ -773,9 +601,9 @@ core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" -coveralls@^2.11.15: - version "2.11.16" - resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.11.16.tgz#da9061265142ddee954f68379122be97be8ab4b1" +coveralls@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.13.0.tgz#df933876e8c6f478efb04f4d3ab70dc96b7e5a8e" dependencies: js-yaml "3.6.1" lcov-parse "0.0.10" @@ -790,12 +618,6 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-error-class@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" - dependencies: - capture-stack-trace "^1.0.0" - create-hash@^1.1.0, create-hash@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.2.tgz#51210062d7bb7479f6c65bb41a92208b1d61abad" @@ -812,17 +634,19 @@ create-hmac@^1.1.0, create-hmac@^1.1.2: create-hash "^1.1.0" inherits "^2.0.1" -cross-env@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-3.1.4.tgz#56e8bca96f17908a6eb1bc2012ca126f92842130" +cross-env@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-4.0.0.tgz#16083862d08275a4628b0b243b121bedaa55dd80" dependencies: - cross-spawn "^3.0.1" + cross-spawn "^5.1.0" + is-windows "^1.0.0" -cross-spawn@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" +cross-spawn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" dependencies: lru-cache "^4.0.1" + shebang-command "^1.2.0" which "^1.2.9" cryptiles@2.x.x: @@ -862,6 +686,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +date-format@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/date-format/-/date-format-0.0.0.tgz#09206863ab070eb459acea5542cbd856b11966b3" + date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" @@ -873,7 +701,7 @@ dateformat@^1.0.11, dateformat@^1.0.6: get-stdin "^4.0.1" meow "^3.3.0" -debug@0.7.4: +debug@0.7.4, debug@^0.7.2: version "0.7.4" resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" @@ -889,9 +717,9 @@ debug@2.3.3: dependencies: ms "0.7.2" -debug@2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" +debug@2.6.1, debug@^2.2.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351" dependencies: ms "0.7.2" @@ -913,10 +741,6 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -925,19 +749,10 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -depd@~1.1.0: +depd@1.1.0, depd@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" -deps-sort@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.0.tgz#091724902e84658260eb910748cccd1af6e21fb5" - dependencies: - JSONStream "^1.0.3" - shasum "^1.0.0" - subarg "^1.0.0" - through2 "^2.0.0" - des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -945,13 +760,6 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -detective@^4.0.0, detective@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.3.2.tgz#77697e2e7947ac3fe7c8e26a6d6f115235afa91c" - dependencies: - acorn "^3.1.0" - defined "^1.0.0" - di@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" @@ -960,7 +768,7 @@ diff@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" -diff@^3.0.1: +diff@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" @@ -985,24 +793,12 @@ domain-browser@^1.1.1, domain-browser@~1.1.0: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" -dot-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" - dependencies: - is-obj "^1.0.0" - duplexer2@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" dependencies: readable-stream "~1.1.9" -duplexer2@^0.1.2, duplexer2@^0.1.4, duplexer2@~0.1.0, duplexer2@~0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - dependencies: - readable-stream "^2.0.2" - ecc-jsbn@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" @@ -1026,9 +822,13 @@ emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" -engine.io-client@1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.2.tgz#c38767547f2a7d184f5752f6f0ad501006703766" +encodeurl@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" + +engine.io-client@1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.3.tgz#1798ed93451246453d4c6f635d7a201fe940d5ab" dependencies: component-emitter "1.2.1" component-inherit "0.0.3" @@ -1039,7 +839,7 @@ engine.io-client@1.8.2: parsejson "0.0.3" parseqs "0.0.5" parseuri "0.0.5" - ws "1.1.1" + ws "1.1.2" xmlhttprequest-ssl "1.5.3" yeast "0.1.2" @@ -1054,16 +854,16 @@ engine.io-parser@1.3.2: has-binary "0.1.7" wtf-8 "1.0.0" -engine.io@1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.2.tgz#6b59be730b348c0125b0a4589de1c355abcf7a7e" +engine.io@1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.3.tgz#8de7f97895d20d39b85f88eeee777b2bd42b13d4" dependencies: accepts "1.3.3" base64id "1.0.0" cookie "0.3.1" debug "2.3.3" engine.io-parser "1.3.2" - ws "1.1.1" + ws "1.1.2" enhanced-resolve@^3.0.0: version "3.1.0" @@ -1228,18 +1028,16 @@ fill-range@^2.1.0: repeat-element "^1.1.2" repeat-string "^1.5.2" -filled-array@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filled-array/-/filled-array-1.1.0.tgz#c3c4f6c663b923459a9aa29912d2d031f1507f84" - -finalhandler@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.0.tgz#e9508abece9b6dba871a6942a1d7911b91911ac7" +finalhandler@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.0.tgz#b5691c2c0912092f18ac23e9416bde5cd7dc6755" dependencies: - debug "~2.2.0" + debug "2.6.1" + encodeurl "~1.0.1" escape-html "~1.0.3" on-finished "~2.3.0" - statuses "~1.3.0" + parseurl "~1.3.1" + statuses "~1.3.1" unpipe "~1.0.0" find-up@^1.0.0: @@ -1313,10 +1111,6 @@ fstream@^1.0.0, fstream@^1.0.2, fstream@~1.0.10: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" - gauge@~2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.3.tgz#1c23855f962f17b3ad3d0dc7443f304542edfe09" @@ -1388,7 +1182,7 @@ glob@^5.0.15, glob@~5.0.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.5, glob@^7.1.0, glob@^7.1.1: +glob@^7.0.5, glob@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: @@ -1405,27 +1199,7 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" -got@^5.0.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" - dependencies: - create-error-class "^3.0.1" - duplexer2 "^0.1.4" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - node-status-codes "^1.0.0" - object-assign "^4.0.1" - parse-json "^2.1.0" - pinkie-promise "^2.0.0" - read-all-stream "^3.0.0" - readable-stream "^2.0.5" - timed-out "^3.0.0" - unzip-response "^1.0.2" - url-parse-lax "^1.0.0" - -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -1515,12 +1289,6 @@ has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" -has@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" - dependencies: - function-bind "^1.0.2" - hash.js@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.0.3.tgz#1332ff00156c0a0ffdd8236013d07b77a0451573" @@ -1551,16 +1319,13 @@ hosted-git-info@^2.1.4: version "2.2.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.2.0.tgz#7a0d097863d886c0fabbdcd37bf1758d8becf8a5" -htmlescape@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" - -http-errors@~1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" +http-errors@~1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" dependencies: + depd "1.1.0" inherits "2.0.3" - setprototypeof "1.0.2" + setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" http-proxy@^1.13.0: @@ -1590,10 +1355,6 @@ ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -1623,25 +1384,6 @@ ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" -inline-source-map@~0.6.0: - version "0.6.2" - resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5" - dependencies: - source-map "~0.5.3" - -insert-module-globals@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.0.1.tgz#c03bf4e01cb086d5b5e5ace8ad0afe7889d638c3" - dependencies: - JSONStream "^1.0.3" - combine-source-map "~0.7.1" - concat-stream "~1.5.1" - is-buffer "^1.1.0" - lexical-scope "^1.2.0" - process "~0.11.0" - through2 "^2.0.0" - xtend "^4.0.0" - interpret@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" @@ -1660,7 +1402,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.0.2, is-buffer@^1.1.0: +is-buffer@^1.0.2: version "1.1.4" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b" @@ -1715,10 +1457,6 @@ is-my-json-valid@^2.12.4: jsonpointer "^4.0.0" xtend "^4.0.0" -is-npm@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" - is-number@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806" @@ -1729,10 +1467,6 @@ is-number@^2.0.2, is-number@^2.1.0: dependencies: kind-of "^3.0.2" -is-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - is-posix-bracket@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" @@ -1745,15 +1479,7 @@ is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" -is-redirect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" - -is-retry-allowed@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" - -is-stream@^1.0.0, is-stream@^1.0.1: +is-stream@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -1765,7 +1491,11 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" -isarray@0.0.1, isarray@~0.0.1: +is-windows@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.0.tgz#c61d61020c3ebe99261b781bd3d1622395f547f8" + +isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -1845,12 +1575,6 @@ json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" -json-stable-stringify@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45" - dependencies: - jsonify "~0.0.0" - json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -1873,10 +1597,6 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" -jsonparse@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.0.tgz#85fc245b1d9259acc6941960b905adf64e7de0e8" - jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" @@ -1929,35 +1649,38 @@ karma-phantomjs-launcher@^1.0.4: lodash "^4.0.1" phantomjs-prebuilt "^2.1.7" -karma-typescript@^2.1.7: - version "2.1.7" - resolved "https://registry.yarnpkg.com/karma-typescript/-/karma-typescript-2.1.7.tgz#5e49a31cf8ef41ac69c439b569a24f0ad9a15973" +karma-typescript@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/karma-typescript/-/karma-typescript-3.0.0.tgz#517abd4fa2e36fdab4f191975226b58e1de5a76a" dependencies: acorn "^4.0.4" amdefine "1.0.0" assert "~1.3.0" + async "^2.1.4" + base64-js "^1.0.2" browser-resolve "^1.11.0" - browserify "^13.1.1" browserify-zlib "~0.1.2" buffer "^4.1.0" console-browserify "^1.1.0" constants-browserify "~1.0.0" crypto-browserify "^3.0.0" - detective "^4.3.2" + diff "^3.2.0" domain-browser "~1.1.0" es6-promise "^4.0.5" events "~1.1.0" glob "^7.1.1" gulp-util "3.0.7" https-browserify "~0.0.0" + ieee754 "^1.1.4" + isarray "^1.0.0" istanbul "0.4.5" karma-coverage "^1.1.1" - lodash.clonedeep "^4.5.0" - lodash.debounce "^4.0.8" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.union "^4.6.0" + lodash "^4.17.4" + log4js "^1.1.1" + magic-string "^0.19.0" + minimatch "^3.0.3" os-browserify "~0.1.1" + pad "^1.1.0" path-browserify "~0.0.0" process "~0.11.0" punycode "^1.3.2" @@ -1976,9 +1699,9 @@ karma-typescript@^2.1.7: util "~0.10.1" vm-browserify "~0.0.1" -karma-webpack@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.2.tgz#bd38350af5645c9644090770939ebe7ce726f864" +karma-webpack@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.3.tgz#39cebf5ca2580139b27f9ae69b78816b9c82fae6" dependencies: async "~0.9.0" loader-utils "^0.2.5" @@ -1986,16 +1709,16 @@ karma-webpack@^2.0.1: source-map "^0.1.41" webpack-dev-middleware "^1.0.11" -karma@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/karma/-/karma-1.4.1.tgz#41981a71d54237606b0a3ea8c58c90773f41650e" +karma@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/karma/-/karma-1.6.0.tgz#0e871d4527d5eac56c41d181f03c5c0a7e6dbf3e" dependencies: bluebird "^3.3.0" - body-parser "^1.12.4" + body-parser "^1.16.1" chokidar "^1.4.1" colors "^1.1.0" combine-lists "^1.0.0" - connect "^3.3.5" + connect "^3.6.0" core-js "^2.2.0" di "^0.0.1" dom-serialize "^2.2.0" @@ -2007,16 +1730,16 @@ karma@^1.3.0: lodash "^3.8.0" log4js "^0.6.31" mime "^1.3.4" - minimatch "^3.0.0" + minimatch "^3.0.2" optimist "^0.6.1" qjobs "^1.1.4" range-parser "^1.2.0" - rimraf "^2.3.3" + rimraf "^2.6.0" safe-buffer "^5.0.1" - socket.io "1.7.2" + socket.io "1.7.3" source-map "^0.5.3" - tmp "0.0.28" - useragent "^2.1.10" + tmp "0.0.31" + useragent "^2.1.12" kew@~0.7.0: version "0.7.0" @@ -2034,28 +1757,10 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -labeled-stream-splicer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz#a52e1d138024c00b86b1c0c91f677918b8ae0a59" - dependencies: - inherits "^2.0.1" - isarray "~0.0.1" - stream-splicer "^2.0.0" - -latest-version@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b" - dependencies: - package-json "^2.0.0" - lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" -lazy-req@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac" - lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -2073,12 +1778,6 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lexical-scope@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/lexical-scope/-/lexical-scope-1.2.0.tgz#fcea5edc704a4b3a8796cdca419c3a0afaf22df4" - dependencies: - astw "^2.0.0" - load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -2093,7 +1792,7 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@^0.2.16, loader-utils@^0.2.5, loader-utils@^0.2.6, loader-utils@^0.2.7: +loader-utils@^0.2.16, loader-utils@^0.2.5: version "0.2.16" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d" dependencies: @@ -2102,6 +1801,14 @@ loader-utils@^0.2.16, loader-utils@^0.2.5, loader-utils@^0.2.6, loader-utils@^0. json5 "^0.5.0" object-assign "^4.0.1" +loader-utils@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + lodash-es@^4.2.1: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7" @@ -2153,10 +1860,6 @@ lodash._root@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - lodash.create@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" @@ -2165,10 +1868,6 @@ lodash.create@3.1.1: lodash._basecreate "^3.0.0" lodash._isiterateecall "^3.0.0" -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - lodash.escape@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" @@ -2183,14 +1882,6 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - lodash.keys@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -2199,10 +1890,6 @@ lodash.keys@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" -lodash.memoize@~3.0.3: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" - lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" @@ -2228,10 +1915,6 @@ lodash.templatesettings@^3.0.0: lodash._reinterpolate "^3.0.0" lodash.escape "^3.0.0" -lodash.union@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" - lodash@^3.8.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" @@ -2251,6 +1934,14 @@ log4js@^0.6.31: readable-stream "~1.0.2" semver "~4.3.3" +log4js@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-1.1.1.tgz#c21d29c7604089e4f255833e7f94b3461de1ff43" + dependencies: + debug "^2.2.0" + semver "^5.3.0" + streamroller "^0.4.0" + longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" @@ -2268,10 +1959,6 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" -lowercase-keys@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" - lru-cache@2.2.x: version "2.2.4" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d" @@ -2283,6 +1970,12 @@ lru-cache@^4.0.1: pseudomap "^1.0.1" yallist "^2.0.0" +magic-string@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.19.0.tgz#198948217254e3e0b93080e01146b7c73b2a06b2" + dependencies: + vlq "^0.2.1" + map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -2356,7 +2049,7 @@ minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2: +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" dependencies: @@ -2376,7 +2069,7 @@ mkdirp@0.5.0: dependencies: minimist "0.0.8" -mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -2398,26 +2091,6 @@ mocha@^3.2.0: mkdirp "0.5.1" supports-color "3.1.2" -module-deps@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-4.0.8.tgz#55fd70623399706c3288bef7a609ff1e8c0ed2bb" - dependencies: - JSONStream "^1.0.3" - browser-resolve "^1.7.0" - cached-path-relative "^1.0.0" - concat-stream "~1.5.0" - defined "^1.0.0" - detective "^4.0.0" - duplexer2 "^0.1.2" - inherits "^2.0.1" - parents "^1.0.0" - readable-stream "^2.0.2" - resolve "^1.1.3" - stream-combiner2 "^1.1.1" - subarg "^1.0.0" - through2 "^2.0.0" - xtend "^4.0.0" - ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" @@ -2482,10 +2155,6 @@ node-pre-gyp@^0.6.29: tar "~2.2.1" tar-pack "~3.3.0" -node-status-codes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" - nopt@3.x, nopt@~3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" @@ -2593,46 +2262,24 @@ os-browserify@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.1.2.tgz#49ca0293e0b19590a5f5de10c7f265a617d8fe54" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - os-locale@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" dependencies: lcid "^1.0.0" -os-tmpdir@^1.0.0, os-tmpdir@~1.0.1: +os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" -osenv@^0.1.0: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -package-json@^2.0.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb" - dependencies: - got "^5.0.0" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" +pad@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pad/-/pad-1.1.0.tgz#7a7d185200ebac32f9f12ee756c3a1d087b3190b" pako@~0.2.0: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" -parents@^1.0.0, parents@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751" - dependencies: - path-platform "~0.11.15" - parse-asn1@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.0.0.tgz#35060f6d5015d37628c770f4e091a0b5a278bc23" @@ -2652,7 +2299,7 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" -parse-json@^2.1.0, parse-json@^2.2.0: +parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" dependencies: @@ -2694,9 +2341,9 @@ path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" -path-platform@~0.11.15: - version "0.11.15" - resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2" +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" path-type@^1.0.0: version "1.1.0" @@ -2748,10 +2395,6 @@ prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" -prepend-http@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" @@ -2798,9 +2441,9 @@ qjobs@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.1.5.tgz#659de9f2cf8dcc27a1481276f205377272382e73" -qs@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" +qs@6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" qs@~6.3.0: version "6.3.0" @@ -2837,7 +2480,7 @@ raw-body@~2.2.0: iconv-lite "0.4.15" unpipe "1.0.0" -rc@^1.0.1, rc@^1.1.6, rc@~1.1.6: +rc@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9" dependencies: @@ -2846,19 +2489,6 @@ rc@^1.0.1, rc@^1.1.6, rc@~1.1.6: minimist "^1.2.0" strip-json-comments "~1.0.4" -read-all-stream@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" - dependencies: - pinkie-promise "^2.0.0" - readable-stream "^2.0.0" - -read-only-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0" - dependencies: - readable-stream "^2.0.2" - read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -2874,7 +2504,16 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.0: +readable-stream@^1.1.7, readable-stream@~1.1.9: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.0: version "2.2.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" dependencies: @@ -2895,15 +2534,6 @@ readable-stream@~1.0.2: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@~1.1.9: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readable-stream@~2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -2959,18 +2589,6 @@ regex-cache@^0.4.2: is-equal-shallow "^0.1.3" is-primitive "^2.0.0" -registry-auth-token@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.1.0.tgz#997c08256e0c7999837b90e944db39d8a790276b" - dependencies: - rc "^1.1.6" - -registry-url@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" - dependencies: - rc "^1.0.1" - remap-istanbul@^0.8.4: version "0.8.4" resolved "https://registry.yarnpkg.com/remap-istanbul/-/remap-istanbul-0.8.4.tgz#b4bfdfdbc90efa635e9a28b1f4a116e22c8c2697" @@ -3050,17 +2668,29 @@ resolve@1.1.7, resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7: +resolve@^1.1.6: version "1.2.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.2.0.tgz#9589c3f2f6149d1417a40becc1663db6ec6bc26c" +resolve@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.2.tgz#1f0442c9e0cbb8136e87b9305f932f46c7f28235" + dependencies: + path-parse "^1.0.5" + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.3.3, rimraf@^2.4.4, rimraf@^2.5.4, rimraf@~2.5.1, rimraf@~2.5.4: +rimraf@2, rimraf@^2.4.4, rimraf@^2.6.0, rimraf@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" + dependencies: + glob "^7.0.5" + +rimraf@~2.5.1, rimraf@~2.5.4: version "2.5.4" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" dependencies: @@ -3074,13 +2704,7 @@ safe-buffer@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" -semver-diff@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" - dependencies: - semver "^5.0.3" - -"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@~5.3.0: +"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -3100,40 +2724,30 @@ setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" -setprototypeof@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08" +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" -sha.js@^2.3.6, sha.js@~2.4.4: +sha.js@^2.3.6: version "2.4.8" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f" dependencies: inherits "^2.0.1" -shasum@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" dependencies: - json-stable-stringify "~0.0.0" - sha.js "~2.4.4" + shebang-regex "^1.0.0" -shell-quote@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" - dependencies: - array-filter "~0.0.0" - array-map "~0.0.0" - array-reduce "~0.0.0" - jsonify "~0.0.0" +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" -slide@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" - sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" @@ -3147,15 +2761,15 @@ socket.io-adapter@0.5.0: debug "2.3.3" socket.io-parser "2.3.1" -socket.io-client@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.2.tgz#39fdb0c3dd450e321b7e40cfd83612ec533dd644" +socket.io-client@1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.3.tgz#b30e86aa10d5ef3546601c09cde4765e381da377" dependencies: backo2 "1.0.2" component-bind "1.0.0" component-emitter "1.2.1" debug "2.3.3" - engine.io-client "1.8.2" + engine.io-client "1.8.3" has-binary "0.1.7" indexof "0.0.1" object-component "0.0.3" @@ -3172,21 +2786,21 @@ socket.io-parser@2.3.1: isarray "0.0.1" json3 "3.3.2" -socket.io@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.2.tgz#83bbbdf2e79263b378900da403e7843e05dc3b71" +socket.io@1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.3.tgz#b8af9caba00949e568e369f1327ea9be9ea2461b" dependencies: debug "2.3.3" - engine.io "1.8.2" + engine.io "1.8.3" has-binary "0.1.7" object-assign "4.1.0" socket.io-adapter "0.5.0" - socket.io-client "1.7.2" + socket.io-client "1.7.3" socket.io-parser "2.3.1" -source-list-map@~0.1.7: - version "0.1.8" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" +source-list-map@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.1.tgz#1a33ac210ca144d1e561f906ebccab5669ff4cb4" source-map@>=0.5.6, source-map@^0.5.1, source-map@^0.5.3, source-map@~0.5.1, source-map@~0.5.3: version "0.5.6" @@ -3247,7 +2861,7 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" -"statuses@>= 1.3.1 < 2", statuses@~1.3.0: +"statuses@>= 1.3.1 < 2", statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -3258,13 +2872,6 @@ stream-browserify@^2.0.0, stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-combiner2@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" - dependencies: - duplexer2 "~0.1.0" - readable-stream "^2.0.2" - stream-http@^2.0.0, stream-http@^2.3.1: version "2.6.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.6.3.tgz#4c3ddbf9635968ea2cfd4e48d43de5def2625ac3" @@ -3275,12 +2882,14 @@ stream-http@^2.0.0, stream-http@^2.3.1: to-arraybuffer "^1.0.0" xtend "^4.0.0" -stream-splicer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.0.tgz#1b63be438a133e4b671cc1935197600175910d83" +streamroller@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-0.4.0.tgz#a273f1f91994549a2ddd112ccaa2d1dd23cb758c" dependencies: - inherits "^2.0.1" - readable-stream "^2.0.2" + date-format "^0.0.0" + debug "^0.7.2" + mkdirp "^0.5.1" + readable-stream "^1.1.7" string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" @@ -3320,12 +2929,6 @@ strip-json-comments@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" -subarg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" - dependencies: - minimist "^1.1.0" - supports-color@3.1.2, supports-color@^3.1.0: version "3.1.2" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" @@ -3340,12 +2943,6 @@ symbol-observable@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" -syntax-error@^1.1.1: - version "1.1.6" - resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.1.6.tgz#b4549706d386cc1c1dc7c2423f18579b6cade710" - dependencies: - acorn "^2.7.0" - tapable@^0.2.5, tapable@~0.2.5: version "0.2.6" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d" @@ -3382,18 +2979,10 @@ through2@2.0.1, through2@^2.0.0: readable-stream "~2.0.0" xtend "~4.0.0" -"through@>=2.2.7 <3": - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - time-stamp@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.0.1.tgz#9f4bd23559c9365966f3302dbba2b07c6b99b151" -timed-out@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" - timers-browserify@^1.0.1: version "1.4.2" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" @@ -3406,15 +2995,15 @@ timers-browserify@^2.0.2: dependencies: setimmediate "^1.0.4" -tmp@0.0.28: - version "0.0.28" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120" +tmp@0.0.29: + version "0.0.29" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" dependencies: os-tmpdir "~1.0.1" -tmp@0.0.29, tmp@0.0.x: - version "0.0.29" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" +tmp@0.0.31, tmp@0.0.x: + version "0.0.31" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" dependencies: os-tmpdir "~1.0.1" @@ -3436,37 +3025,42 @@ trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" -ts-loader@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-2.0.0.tgz#26f382b51951bf5db4c88ea5f3b156c1cfdd813f" +ts-loader@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-2.0.3.tgz#89b8c87598f048df065766e07e1538f0eaeb1165" dependencies: colors "^1.0.3" enhanced-resolve "^3.0.0" - loader-utils "^0.2.6" + loader-utils "^1.0.2" semver "^5.0.1" -tslint-loader@^3.4.2: - version "3.4.2" - resolved "https://registry.yarnpkg.com/tslint-loader/-/tslint-loader-3.4.2.tgz#f26424b4dbfe1718acb96469e807038b1d2a644a" +tslint-loader@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/tslint-loader/-/tslint-loader-3.5.2.tgz#ef2f512c05a96fb1a22d94c32d22fb7975ef9917" dependencies: - loader-utils "^0.2.7" + loader-utils "^1.0.2" mkdirp "^0.5.1" object-assign "^4.1.1" rimraf "^2.4.4" semver "^5.3.0" -tslint@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-4.4.2.tgz#b14cb79ae039c72471ab4c2627226b940dda19c6" +tslint@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.1.0.tgz#51a47baeeb58956fcd617bd2cf00e2ef0eea2ed9" dependencies: - babel-code-frame "^6.20.0" + babel-code-frame "^6.22.0" colors "^1.1.2" - diff "^3.0.1" + diff "^3.2.0" findup-sync "~0.3.0" glob "^7.1.1" optimist "~0.6.0" - resolve "^1.1.7" - update-notifier "^1.0.2" + resolve "^1.3.2" + semver "^5.3.0" + tsutils "^1.4.0" + +tsutils@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.6.0.tgz#1fd7fac2a61369ed99cd3997f0fbb437128850f2" tty-browserify@0.0.0, tty-browserify@~0.0.0: version "0.0.0" @@ -3505,11 +3099,11 @@ typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.1.6.tgz#40c7e6e9e5da7961b7718b55505f9cac9487a607" +typescript@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.2.tgz#606022508479b55ffa368b58fee963a03dfd7b0c" -uglify-js@^2.6, uglify-js@^2.7.5: +uglify-js@^2.6: version "2.7.5" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" dependencies: @@ -3518,6 +3112,15 @@ uglify-js@^2.6, uglify-js@^2.7.5: uglify-to-browserify "~1.0.0" yargs "~3.10.0" +uglify-js@^2.8.5: + version "2.8.22" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.22.tgz#d54934778a8da14903fa29a326fb24c0ab51a1a0" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -3530,37 +3133,10 @@ ultron@1.0.x: version "1.0.2" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" -umd@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.1.tgz#8ae556e11011f63c2596708a8837259f01b3d60e" - unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" -unzip-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" - -update-notifier@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-1.0.3.tgz#8f92c515482bd6831b7c93013e70f87552c7cf5a" - dependencies: - boxen "^0.6.0" - chalk "^1.0.0" - configstore "^2.0.0" - is-npm "^1.0.0" - latest-version "^2.0.0" - lazy-req "^1.1.0" - semver-diff "^2.0.0" - xdg-basedir "^2.0.0" - -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - dependencies: - prepend-http "^1.0.1" - url@^0.11.0, url@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -3568,9 +3144,9 @@ url@^0.11.0, url@~0.11.0: punycode "1.3.2" querystring "0.2.0" -useragent@^2.1.10: - version "2.1.12" - resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.1.12.tgz#aa7da6cdc48bdc37ba86790871a7321d64edbaa2" +useragent@^2.1.12: + version "2.1.13" + resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.1.13.tgz#bba43e8aa24d5ceb83c2937473e102e21df74c10" dependencies: lru-cache "2.2.x" tmp "0.0.x" @@ -3589,10 +3165,6 @@ utils-merge@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" -uuid@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" - uuid@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" @@ -3618,6 +3190,10 @@ vinyl@^0.5.0: clone-stats "^0.0.1" replace-ext "0.0.1" +vlq@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.1.tgz#14439d711891e682535467f8587c5630e4222a6c" + vm-browserify@0.0.4, vm-browserify@~0.0.1: version "0.0.4" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" @@ -3628,9 +3204,9 @@ void-elements@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" -watchpack@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.2.0.tgz#15d4620f1e7471f13fcb551d5c030d2c3eb42dbb" +watchpack@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87" dependencies: async "^2.1.2" chokidar "^1.4.3" @@ -3645,16 +3221,16 @@ webpack-dev-middleware@^1.0.11: path-is-absolute "^1.0.0" range-parser "^1.0.3" -webpack-sources@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.4.tgz#ccc2c817e08e5fa393239412690bb481821393cd" +webpack-sources@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.3.tgz#17c62bfaf13c707f9d02c479e0dcdde8380697fb" dependencies: - source-list-map "~0.1.7" + source-list-map "^1.1.1" source-map "~0.5.3" -webpack@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.2.1.tgz#7bb1d72ae2087dd1a4af526afec15eed17dda475" +webpack@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.3.3.tgz#eecc083c18fb7bf958ea4f40b57a6640c5a0cc78" dependencies: acorn "^4.0.4" acorn-dynamic-import "^2.0.0" @@ -3672,9 +3248,9 @@ webpack@2.2.1: source-map "^0.5.3" supports-color "^3.1.0" tapable "~0.2.5" - uglify-js "^2.7.5" - watchpack "^1.2.0" - webpack-sources "^0.1.4" + uglify-js "^2.8.5" + watchpack "^1.3.1" + webpack-sources "^0.2.3" yargs "^6.0.0" which-module@^1.0.0: @@ -3693,12 +3269,6 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.1" -widest-line@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c" - dependencies: - string-width "^1.0.1" - window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" @@ -3726,17 +3296,9 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -write-file-atomic@^1.1.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.1.tgz#7d45ba32316328dd1ec7d90f60ebc0d845bb759a" - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - slide "^1.1.5" - -ws@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018" +ws@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.2.tgz#8a244fa052401e08c9886cf44a85189e1fd4067f" dependencies: options ">=0.0.5" ultron "1.0.x" @@ -3745,12 +3307,6 @@ wtf-8@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a" -xdg-basedir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2" - dependencies: - os-homedir "^1.0.0" - xmlhttprequest-ssl@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" From d3372464455356afb6909bb7ca8c9ee356840ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ole=C5=9B?= Date: Wed, 12 Apr 2017 23:24:39 +0200 Subject: [PATCH 04/10] Remove examples - not up to date --- example/parallelExecution.ts | 20 -------------------- example/sequenceExecution.ts | 21 --------------------- 2 files changed, 41 deletions(-) delete mode 100644 example/parallelExecution.ts delete mode 100644 example/sequenceExecution.ts diff --git a/example/parallelExecution.ts b/example/parallelExecution.ts deleted file mode 100644 index 73dd8c6..0000000 --- a/example/parallelExecution.ts +++ /dev/null @@ -1,20 +0,0 @@ - -import { Action } from 'redux'; -import { Executor, handleCommand } from 'redux-executor'; - -/** - * ParallelCommand is a command that will execute all commands from payload in parallel. - * As a payload of ParallelCommand is a list of commands, you can put there another - * ParallelCommand or SequenceCommand and build nested tree of execution. - */ -interface ParallelCommand extends Action { - type: 'PARALLEL()'; - payload: Action[]; -} - -export const parallelCommandExecutor: Executor = handleCommand( - 'PARALLEL()', - (command, dispatch) => Promise.all( - command.payload.map(command => dispatch(command).promise || Promise.resolve()) - ).then(() => undefined) -); diff --git a/example/sequenceExecution.ts b/example/sequenceExecution.ts deleted file mode 100644 index 32a05a6..0000000 --- a/example/sequenceExecution.ts +++ /dev/null @@ -1,21 +0,0 @@ - -import { Action } from 'redux'; -import { Executor, handleCommand } from 'redux-executor'; - -/** - * SequenceCommand is a command that will execute all commands from payload in given order. - * As a payload of SequenceCommand is a list of commands, you can put there another - * SequenceCommand or ParallelCommand and build nested tree of execution. - */ -interface SequenceCommand extends Action { - type: 'SEQUENCE()'; - payload: Action[]; -} - -export const sequenceCommandExecutor: Executor = handleCommand( - 'SEQUENCE()', - (command, dispatch) => command.payload.reduce( - (promise, command) => promise.then(() => dispatch(command).promise || Promise.resolve()), - Promise.resolve() - ) -); From c91f0be0d015144e9d324ccce769ea31d7b4a5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ole=C5=9B?= Date: Wed, 12 Apr 2017 23:41:40 +0200 Subject: [PATCH 05/10] Improve documentation --- README.md | 76 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 0033dfb..b95bebe 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,17 @@ Redux [enhancer](http://redux.js.org/docs/api/createStore.html) for handling sid **Warning: API is not stable yet, will be from version 1.0** -## Installation ## +## Table of Contents +1. [Instalation](#instalation) +1. [Motivation](#motivation) +1. [Concepts](#concepts) +1. [Composition](#composition) +1. [Execution order](#execution-order) +1. [API](#api) +1. [Code Splitting](#code-splitting) +1. [License](#license) + +## Installation Redux Executor requires **Redux 3.1.0 or later.** ```sh npm install --save redux-executor @@ -29,9 +39,9 @@ const store = createStore( ); ``` -#### Redux DevTools #### -To use redux-executor with Redux DevTools, you have to be careful about enhancers order. It's because redux-executor blocks -commands to next enhancers so it has to be placed after DevTools (to see commands). +#### Redux DevTools +To use Redux Executor with [Redux DevTools](https://github.com/gaearon/redux-devtools), you have to be careful about enhancers order. +It's because Redux Executor do not pass commands to next enhancers so it has to be placed after DevTools (to see commands). ```js const devToolsCompose = window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? @@ -46,10 +56,10 @@ const enhancer = compose( ); ``` -## Motivation ## +## Motivation There are many clever solutions to deal with side-effects in redux application like [redux-thunk](https://github.com/gaearon/redux-thunk) -or [redux-saga](https://github.com/redux-saga/redux-saga). The goal of this library is to be simpler than **redux-saga** -and also easier to test and more pure than **redux-thunk**. +or [redux-saga](https://github.com/redux-saga/redux-saga). The goal of this library is to be simpler than **redux-saga**, +easier to test and more pure than **redux-thunk**. Typical usage of executor is to fetch some external resource, for example list of posts. It can look like this: ```js @@ -58,7 +68,7 @@ import { postApi } from './api/postApi'; // import action creators import { postsRequested, postsResolved, postsRejected } from './actions/postActions'; -// postsExecutor.js +// postExecutors.js function fetchPostsExecutor(command, dispatch, state) { dispatch(postsRequested()); @@ -73,7 +83,7 @@ export default handleCommand('FETCH_POSTS()', fetchPostsExecutor); dispatch(fetchPosts(this.state.page)); ``` So what is the difference between executor and thunk? With executors you have separation between side-effect _request_ and -side-effect _call_. It means that you can omit second phase and don't call side-effect (if you not bind executor to the store). +side-effect _call_. It means that you can omit second phase and not call side-effect (if you not bind executor to the store). With this design it's very easy to write unit tests. If you use [Redux DevTools](https://github.com/gaearon/redux-devtools) it will be also easier to debug - all commands will be logged in debugger. @@ -81,8 +91,8 @@ I recommend to use redux-executor with [redux-detector](https://github.com/piotr In this combination you can for example detect if client is on given url and dispatch fetch command. All, excluding executors, will be pure. -## Concepts ## -### An Executor ### +## Concepts +### An Executor It may sounds a little bit scary but there is nothing to fear - executor is very pure and simple function. ```typescript type Executor = (command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined) => Promise | void; @@ -90,7 +100,7 @@ type Executor = (command: ActionLike, dispatch: ExecutableDispatch, state: Like you see above, executor takes an action (called **command** in executors), enhanced `dispatch` function and state. It can return a `Promise` to provide custom execution flow. -### A Command ### +### A Command Command is an **action** with specific `type` format - `COMMAND_TYPE()` (like function call, instead of `COMMAND_TYPE`). The idea behind is that it's more clean to split actions to two types: normal actions (we will call it _events_) that tells what **has happened** and _commands_ that tells what **should happen**. @@ -103,7 +113,7 @@ unpure. Another thing is that events changes state (by reducer), commands not. Because of that command dispatch doesn't call store listeners (for example it doesn't re-render React application). -## Composition ## +## Composition You can pass only one executor to the store, but with `combineExecutors` and `reduceExecutors` you can mix them to one executor. For example: ```js @@ -130,7 +140,7 @@ export default combineExecutors({ }); ``` -## Execution order ## +## Execution order Sometimes we want to dispatch actions in proper order. To do this, we have to return promise from executors we want to include to our execution order. If we dispatch **command**, dispatch method will return action (it's redux behaviour) with additional `promise` field that contains promise of our side-effects. Keep in mind that this promise is the result of @@ -178,8 +188,7 @@ export const parallelCommandExecutor = handleCommand( ); ``` -With this commands we can create action creator instead of executor for `firstCommand`, `secondCommand` and -`thirdCommand` example. +With this executors we can create action creator instead of executor for the previous example. ```js // import action creators import { firstCommand, secondCommand, thirdCommand } from './commands/exampleCommands'; @@ -210,8 +219,8 @@ export default function firstThenNext() { // } ``` -## API ## -#### combineExecutors #### +## API +#### combineExecutors ```typescript type ExecutorsMap = { [K in keyof S]: Executor; @@ -222,16 +231,16 @@ function combineExecutors(map: ExecutorsMap): Executor; Binds executors to state branches and combines them to one executor. Executors will be called in sequence but promise will be resolved in parallel (by `Promise.all`) Useful for re-usable executors. -#### createExecutorEnchancer #### +#### createExecutorEnchancer ```typescript type StoreExecutableEnhancer = (next: StoreEnhancerStoreCreator) => StoreEnhancerStoreExecutableCreator; type StoreEnhancerStoreExecutableCreator = (reducer: Reducer, preloadedState: S) => ExecutableStore; function createExecutorEnhancer(executor: Executor): StoreExecutableEnhancer; ``` -Creates new [redux enhancer](http://redux.js.org/docs/Glossary.html#store-enhancer) that extends redux store api (see [ExecutableStore](#executable-store)) +Creates new [redux enhancer](http://redux.js.org/docs/Glossary.html#store-enhancer) that extends redux store api (see [ExecutableStore](#executablestore)) -#### ExecutableDispatch #### +#### ExecutableDispatch ```typescript interface ExecutableDispatch extends Dispatch { (action: A): A & { promise?: Promise }; @@ -239,29 +248,29 @@ interface ExecutableDispatch extends Dispatch { ``` It's type of enhanced dispatch method that can add `promise` field to returned action if you dispatch command. -#### ExecutableStore #### +#### ExecutableStore ```typescript interface ExecutableStore extends Store { dispatch: ExecutableDispatch; replaceExecutor(nextExecutor: Executor): void; } ``` -It's type of enhanced store that has enhanced dispatch method (see [ExecutableDispatch](#executable-dispatch)) and +It's type of store that has enhanced dispatch method (see [ExecutableDispatch](#executabledispatch)) and `replaceExecutor` method (like `replaceReducer`). -#### Executor #### +#### Executor ```typescript type Executor = (command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined) => Promise | void; ``` See [Concepts / An Executor](#an-executor) -#### handleCommand #### +#### handleCommand ```typescript function handleCommand(type: string, executor: Executor): Executor; ``` Limit executor to given command type (inspired by [redux-actions](https://github.com/acdlite/redux-actions)). -#### handleCommands #### +#### handleCommands ```typescript type ExecutorPerCommandMap = { [type: string]: Executor; @@ -272,32 +281,31 @@ function handleCommands(map: ExecutorPerCommandMap): Executor; Similar to `handleCommand` but works for multiple commands at once. Map is an object where key is a command type, value is an executor (inspired by [redux-actions](https://github.com/acdlite/redux-actions)). -#### isCommand #### +#### isCommand ```typescript function isCommand(object: any): boolean; ``` -Checks if given object is an command (`object.type` ends with `()` string). +Checks if given object is a command (`object.type` ends with `()` string). -#### mountExecutor #### +#### mountExecutor ```typescript function mountExecutor(selector: (state: S1 | undefined) => S2, executor: Executor): Executor; ``` Mounts executor to some state branch. Useful for re-usable executors. -#### reduceExecutors #### +#### reduceExecutors ```typescript function reduceExecutors(...executors: Executor[]): Executor; ``` Reduces multiple executors to one. Executors will be called in sequence but promise will be resolved in parallel (by `Promise.all`). Useful for re-usable executors. - -## Code Splitting ## +## Code Splitting Redux Executor provides `replaceExecutor` method on `ExecutableStore` interface (store created by Redux Executor). It's similar to `replaceReducer` - it changes executor and dispatches `{ type: '@@executor/INIT()' }`. -## Typings ## +## Typings If you are using [TypeScript](https://www.typescriptlang.org/), you don't have to install typings - they are provided in npm package. -## License ## +## License MIT From 08043be27b995dd2139702aad9fcc8db59f32876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ole=C5=9B?= Date: Wed, 12 Apr 2017 23:47:49 +0200 Subject: [PATCH 06/10] Improve documentation --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b95bebe..a24daac 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,14 @@ Redux [enhancer](http://redux.js.org/docs/api/createStore.html) for handling sid **Warning: API is not stable yet, will be from version 1.0** ## Table of Contents -1. [Instalation](#instalation) +1. [Instalation](#installation) 1. [Motivation](#motivation) 1. [Concepts](#concepts) 1. [Composition](#composition) 1. [Execution order](#execution-order) 1. [API](#api) 1. [Code Splitting](#code-splitting) +1. [Typings](#typings) 1. [License](#license) ## Installation @@ -24,7 +25,7 @@ npm install --save redux-executor ``` This assumes that you’re using [npm](http://npmjs.com/) package manager with a module bundler like [Webpack](http://webpack.github.io/) or [Browserify](http://browserify.org/) to consume -[CommonJS modules](http://webpack.github.io/docs/commonjs.html). +[UMD modules](https://github.com/umdjs/umd). To enable Redux Executor, use `createExecutorEnhancer` with `createStore`: ```js From bbe8535554d91f3207980fbf6f79962232b571ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ole=C5=9B?= Date: Wed, 12 Apr 2017 23:48:26 +0200 Subject: [PATCH 07/10] Self CR --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a24daac..ede07ec 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Redux [enhancer](http://redux.js.org/docs/api/createStore.html) for handling sid **Warning: API is not stable yet, will be from version 1.0** ## Table of Contents -1. [Instalation](#installation) +1. [Installation](#installation) 1. [Motivation](#motivation) 1. [Concepts](#concepts) 1. [Composition](#composition) From 3066053989ae1319b486a58d1f52dfae70ba7b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ole=C5=9B?= Date: Fri, 14 Apr 2017 00:11:58 +0200 Subject: [PATCH 08/10] Add isCommandType, change state to getState, improve tests --- README.md | 76 ++++++++++++++++++++++-- package.json | 4 ++ src/Executor.ts | 7 ++- src/GetState.ts | 2 + src/combineExecutors.ts | 10 +++- src/createExecutorEnhancer.ts | 2 +- src/handleCommand.ts | 8 ++- src/index.ts | 2 + src/isCommand.ts | 10 +--- src/isCommandType.ts | 15 +++++ src/mountExecutor.ts | 9 ++- src/reduceExecutors.ts | 9 ++- test/combineExecutors.spec.ts | 76 ++++++++++-------------- test/createExecutorEnhancer.spec.ts | 39 ++++++++----- test/handleCommand.spec.ts | 10 ++-- test/handleCommands.spec.ts | 14 ++--- test/isCommand.spec.ts | 3 +- test/isCommandType.spec.ts | 28 +++++++++ test/mountExecutor.spec.ts | 15 ++--- test/reduceExecutors.spec.ts | 91 +++++++++-------------------- tsconfig.json | 4 ++ types/lodash.isstring/index.d.ts | 7 +++ yarn.lock | 23 +++----- 23 files changed, 284 insertions(+), 180 deletions(-) create mode 100644 src/GetState.ts create mode 100644 src/isCommandType.ts create mode 100644 test/isCommandType.spec.ts create mode 100644 types/lodash.isstring/index.d.ts diff --git a/README.md b/README.md index ede07ec..e1fc74d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Redux [enhancer](http://redux.js.org/docs/api/createStore.html) for handling sid 1. [Motivation](#motivation) 1. [Concepts](#concepts) 1. [Composition](#composition) +1. [Narrowing](#narrowing) 1. [Execution order](#execution-order) 1. [API](#api) 1. [Code Splitting](#code-splitting) @@ -70,7 +71,7 @@ import { postApi } from './api/postApi'; import { postsRequested, postsResolved, postsRejected } from './actions/postActions'; // postExecutors.js -function fetchPostsExecutor(command, dispatch, state) { +function fetchPostsExecutor(command, dispatch, getState) { dispatch(postsRequested()); return postApi.list(command.payload.page) @@ -96,7 +97,7 @@ All, excluding executors, will be pure. ### An Executor It may sounds a little bit scary but there is nothing to fear - executor is very pure and simple function. ```typescript -type Executor = (command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined) => Promise | void; +type Executor = (command: Action, dispatch: ExecutableDispatch, getState: GetState) => Promise | void; ``` Like you see above, executor takes an action (called **command** in executors), enhanced `dispatch` function and state. It can return a `Promise` to provide custom execution flow. @@ -141,6 +142,55 @@ export default combineExecutors({ }); ``` +#### Mounting +To re-use executors we can also mount them to some state branch. To do this, use `mountExecutor` function +with state selector and executor. +```js +import { mountExecutor } from 'redux-executor'; + +// our state has shape: +// { +// foo: [1, 3], +// } +// +// We want to bind `fooExecutor` to the length of `state.foo` branch + +function fooExecutor(command, dispatch, getState) { + console.log(getState()); // > 2 +} + +export default mountExecutor((state) => state.foo.length, fooExecutor); +``` + +## Narrowing +By default executor runs for every command. To limit executor to given command type, you can write _if_ statement +or use `handleCommand`/`handleCommands` function. + +For example to limit `fooExecutor` to command `FOO()`, `barExecutor` to command `BAR()` and mix them into one executor: +```js +import { handleCommand, handleCommands, reduceExecutors } from 'redux-executor'; + +function fooExecutor(command, dispatch, getState) { + // foo executor logic +} + +function barExecutor(command, dispatch, getState) { + // bar executor logic +} + +// OPTION 1: handleExecutor + reduceExecutors +export default reduceExecutors( + handleCommand('FOO()', fooExecutor), + handleCommand('BAR()', barExecutor) +); + +// OPTION 2: handleCommands +export default handleCommands({ + 'FOO()': fooExecutor, + 'BAR()': barExecutor +}); +``` + ## Execution order Sometimes we want to dispatch actions in proper order. To do this, we have to return promise from executors we want to include to our execution order. If we dispatch **command**, dispatch method will return action (it's redux behaviour) with @@ -155,7 +205,7 @@ The easiest solution is: // import action creators import { firstCommand, secondCommand, thirdCommand } from './commands/exampleCommands'; -function firstThenNextExecutor(command, dispatch, state) { +function firstThenNextExecutor(command, dispatch, getState) { return dispatch(firstCommand()).promise .then(() => Promise.all([ dispatch(secondCommand()).promise, @@ -261,10 +311,22 @@ It's type of store that has enhanced dispatch method (see [ExecutableDispatch](# #### Executor ```typescript -type Executor = (command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined) => Promise | void; +type Executor = (command: Action, dispatch: ExecutableDispatch, getState: GetState) => Promise | void; ``` See [Concepts / An Executor](#an-executor) +#### EXECUTOR_INIT +```typescript +const EXECUTOR_INIT: string = '@@executor/INIT()'; +``` +Command of this type is dispatched on init and `replaceExecutor` call. + +#### GetState +```typescript +type GetState = () => S; +``` +Simple function to get current state (we don't provide state itself because it can change during async side-effects). + #### handleCommand ```typescript function handleCommand(type: string, executor: Executor): Executor; @@ -288,6 +350,12 @@ function isCommand(object: any): boolean; ``` Checks if given object is a command (`object.type` ends with `()` string). +#### isCommandType +```typescript +function isCommandType(type: string): boolean; +``` +Similar to `isCommand` but checks only `type` string. + #### mountExecutor ```typescript function mountExecutor(selector: (state: S1 | undefined) => S2, executor: Executor): Executor; diff --git a/package.json b/package.json index 27ccd07..355a49c 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "url": "https://github.com/piotr-oles/redux-executor/issues" }, "devDependencies": { + "@types/lodash": "^4.14.62", "@types/mocha": "^2.2.40", "chai": "^3.5.0", "chai-as-promised": "^6.0.0", @@ -56,5 +57,8 @@ }, "peerDependencies": { "redux": "^3.1.0" + }, + "dependencies": { + "lodash.isstring": "^4.0.1" } } diff --git a/src/Executor.ts b/src/Executor.ts index 09ab696..e6e9e4b 100644 --- a/src/Executor.ts +++ b/src/Executor.ts @@ -1,8 +1,13 @@ import { ActionLike } from './ActionLike'; +import { GetState } from './GetState'; import { ExecutableDispatch } from './ExecutableDispatch'; /** * Executor is an simple function that executes some side effects on given command. * They can return promise if side effect is asynchronous. */ -export type Executor = (command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined) => Promise | void; +export type Executor = ( + command: ActionLike, + dispatch: ExecutableDispatch, + getState: GetState +) => Promise | void; diff --git a/src/GetState.ts b/src/GetState.ts new file mode 100644 index 0000000..e89ef01 --- /dev/null +++ b/src/GetState.ts @@ -0,0 +1,2 @@ + +export type GetState = () => S; diff --git a/src/combineExecutors.ts b/src/combineExecutors.ts index db84ae1..204286f 100644 --- a/src/combineExecutors.ts +++ b/src/combineExecutors.ts @@ -1,6 +1,7 @@ import { Executor } from './Executor'; import { ActionLike } from './ActionLike'; import { ExecutableDispatch } from './ExecutableDispatch'; +import { GetState } from './GetState'; export type ExecutorsMap = { [K in keyof S]?: Executor; @@ -14,9 +15,14 @@ export type ExecutorsMap = { * @returns Combined executor */ export function combineExecutors(map: ExecutorsMap): Executor { - return function combinedExecutor(command: ActionLike, dispatch: ExecutableDispatch, state: S | undefined): Promise { + return function combinedExecutor(command: ActionLike, dispatch: ExecutableDispatch, getState: GetState): Promise { return Promise.all( - Object.keys(map).map((key: keyof S) => map[key]!(command, dispatch, state ? state[key] : undefined) || Promise.resolve()) + Object.keys(map).map( + (key: keyof S) => map[key]!( + command, + dispatch, + () => (getState() ? getState()![key] : undefined) + ) || Promise.resolve()) ).then( /* istanbul ignore next */ () => undefined diff --git a/src/createExecutorEnhancer.ts b/src/createExecutorEnhancer.ts index 111e6a3..8cb32d4 100644 --- a/src/createExecutorEnhancer.ts +++ b/src/createExecutorEnhancer.ts @@ -47,7 +47,7 @@ export function createExecutorEnhancer(executor: Executor): StoreExecutabl let promise: Promise | void = currentExecutor( action as any, executableStore.dispatch, - executableStore.getState() + executableStore.getState ); // return command with promise field to allow to synchronize dispatches diff --git a/src/handleCommand.ts b/src/handleCommand.ts index 6e1fb1a..42f0079 100644 --- a/src/handleCommand.ts +++ b/src/handleCommand.ts @@ -2,6 +2,8 @@ import { ActionLike } from './ActionLike'; import { Executor } from './Executor'; import { ExecutableDispatch } from './ExecutableDispatch'; +import { GetState } from './GetState'; +import { isCommandType } from './isCommandType'; /** * Wraps executor to handle only one type of command. @@ -11,7 +13,7 @@ import { ExecutableDispatch } from './ExecutableDispatch'; * @returns Executor that runs wrapped executor only for commands with given type. */ export function handleCommand(type: string, executor: Executor): Executor { - if (!type || type.length < 3 || ')' !== type[type.length - 1] || '(' !== type[type.length - 2]) { + if (!isCommandType(type)) { throw new Error(`Expected type to be valid command type with '()' ending. Given '${type}' type. Maybe typo?`); } @@ -19,9 +21,9 @@ export function handleCommand(type: string, executor: Executor): Executor< throw new Error('Expected the executor to be a function.'); } - return function wideExecutor(command: ActionLike, dispatch: ExecutableDispatch, state: S): Promise | void { + return function wideExecutor(command: ActionLike, dispatch: ExecutableDispatch, getState: GetState): Promise | void { if (command && command.type === type) { - return executor(command, dispatch, state); + return executor(command, dispatch, getState); } }; } diff --git a/src/index.ts b/src/index.ts index 090c527..20394fd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,12 +2,14 @@ export { ExecutableDispatch } from './ExecutableDispatch'; export { ExecutableStore } from './ExecutableStore'; export { Executor } from './Executor'; +export { GetState } from './GetState'; // implementation export { reduceExecutors } from './reduceExecutors'; export { combineExecutors } from './combineExecutors'; export { createExecutorEnhancer, EXECUTOR_INIT } from './createExecutorEnhancer'; export { isCommand } from './isCommand'; +export { isCommandType } from './isCommandType'; export { handleCommand } from './handleCommand'; export { handleCommands } from './handleCommands'; export { mountExecutor } from './mountExecutor'; diff --git a/src/isCommand.ts b/src/isCommand.ts index e14add2..787fd5d 100644 --- a/src/isCommand.ts +++ b/src/isCommand.ts @@ -1,15 +1,11 @@ +import { isCommandType } from './isCommandType'; + /** * Checks if given object is command (action with type that ends with () substring). * * @param object Object to check */ export function isCommand(object: any): boolean { - return !!( - object && - object.type && - object.type.length >= 3 && - ')' === object.type[object.type.length - 1] && // last char is ) - '(' === object.type[object.type.length - 2] // and before it there is ( char - ); + return !!object && isCommandType(object.type); } diff --git a/src/isCommandType.ts b/src/isCommandType.ts new file mode 100644 index 0000000..9551830 --- /dev/null +++ b/src/isCommandType.ts @@ -0,0 +1,15 @@ +import * as isString from 'lodash.isstring'; + +/** + * Checks if given type is a command type (ends with () substring). + * + * @param type String to check + */ +export function isCommandType(type: string): boolean { + return ( + isString(type) && + type.length >= 3 && + ')' === type[type.length - 1] && // last char is ) + '(' === type[type.length - 2] // and before it there is ( char + ); +} \ No newline at end of file diff --git a/src/mountExecutor.ts b/src/mountExecutor.ts index e860b53..c58eccd 100644 --- a/src/mountExecutor.ts +++ b/src/mountExecutor.ts @@ -1,6 +1,7 @@ import { ActionLike } from './ActionLike'; import { Executor } from './Executor'; import { ExecutableDispatch } from './ExecutableDispatch'; +import { GetState } from './GetState'; /** * Mount executor to operate on some substate. @@ -18,7 +19,11 @@ export function mountExecutor(selector: (state: S1 | undefined) => S2 | throw new Error('Expected the executor to be a function.'); } - return function mountedExecutor(command: ActionLike, dispatch: ExecutableDispatch, state: S1 | undefined): Promise | void { - return executor(command, dispatch, selector(state)); + return function mountedExecutor( + command: ActionLike, + dispatch: ExecutableDispatch, + getState: GetState + ): Promise | void { + return executor(command, dispatch, () => selector(getState())); }; } diff --git a/src/reduceExecutors.ts b/src/reduceExecutors.ts index 8770129..93f6cd9 100644 --- a/src/reduceExecutors.ts +++ b/src/reduceExecutors.ts @@ -1,6 +1,7 @@ import { ActionLike } from './ActionLike'; import { Executor } from './Executor'; import { ExecutableDispatch } from './ExecutableDispatch'; +import { GetState } from './GetState'; /** * Reduce executors to get one that will call wrapped. @@ -22,10 +23,14 @@ export function reduceExecutors(...executors: Executor[]): Executor { ); } - return function reducedExecutor(command: ActionLike, dispatch: ExecutableDispatch, state: S): Promise { + return function reducedExecutor( + command: ActionLike, + dispatch: ExecutableDispatch, + getState: GetState + ): Promise { return Promise.all( executors - .map(executor => executor(command, dispatch, state)) + .map(executor => executor(command, dispatch, getState)) .filter(promise => !!promise) ) as Promise; }; diff --git a/test/combineExecutors.spec.ts b/test/combineExecutors.spec.ts index 8cf456d..f536618 100644 --- a/test/combineExecutors.spec.ts +++ b/test/combineExecutors.spec.ts @@ -9,22 +9,24 @@ chai.use(spies); chai.use(promised); describe('combineExecutors', () => { + let executorResolved, executorRejected, executorVoid; + let dumbDispatch, dumbGetState, getUndefinedState; + + beforeEach(() => { + executorResolved = () => Promise.resolve(); + executorRejected = () => Promise.reject(new Error()); + executorVoid = () => undefined; + + dumbDispatch = () => undefined; + dumbGetState = () => ({}); + getUndefinedState = () => undefined; + }); + it('should export combineExecutors function', () => { expect(combineExecutors).to.be.function; }); it('should return valid executor for combination of two executors', () => { - function executorResolved() { - return Promise.resolve(); - } - - function executorRejected() { - return Promise.reject(new Error()); - } - - function executorVoid() { - } - const executorABC = combineExecutors({ a: executorResolved, b: executorResolved, @@ -32,10 +34,7 @@ describe('combineExecutors', () => { }); expect(executorABC).to.be.function; - let promiseABC: Promise = executorABC({ type: 'FOO()' }, () => {}, {}) as Promise; - - let thenSpy = chai.spy(); - let catchSpy = chai.spy(); + const promiseABC: Promise = executorABC({ type: 'FOO()' }, dumbDispatch, dumbGetState) as Promise; expect(promiseABC).to.exist; expect(promiseABC.then).to.be.function; @@ -46,17 +45,6 @@ describe('combineExecutors', () => { }); it('should return executor that rejects on children reject', () => { - function executorResolved() { - return Promise.resolve(); - } - - function executorRejected() { - return Promise.reject(new Error()); - } - - function executorVoid() { - } - const executorABC = combineExecutors({ a: executorResolved, b: executorRejected, @@ -64,7 +52,7 @@ describe('combineExecutors', () => { }); expect(executorABC).to.be.function; - let promise: Promise = executorABC({ type: 'FOO()' }, () => {}, {}) as Promise; + const promise: Promise = executorABC({ type: 'FOO()' }, dumbDispatch, dumbGetState) as Promise; expect(promise).to.exist; expect(promise.then).to.be.function; @@ -74,40 +62,38 @@ describe('combineExecutors', () => { }); it('should pass sub-state to sub-executors', () => { - const state = { + const getState = () => ({ a: 'foo', b: 'bar' + }); + const executorA = (command, dispatch, getState) => { + expect(getState()).to.be.equals('foo'); + }; + const executorB = (command, dispatch, getState) => { + expect(getState()).to.be.equals('bar'); }; - const executorA = chai.spy(); - const executorB = chai.spy(); - const dispatch = chai.spy(); const executorAB = combineExecutors({ a: executorA, b: executorB }); - executorAB({ type: 'FOO()' }, dispatch, state); - expect(executorA).to.have.been.called.with({ type: 'FOO()' }, dispatch, state.a); - expect(executorB).to.have.been.called.with({ type: 'FOO()' }, dispatch, state.b); + executorAB({ type: 'FOO()' }, dumbDispatch, getState); }); + it('should pass sub-state to sub-executors for undefined state', () => { - const executorA = chai.spy(); - const executorB = chai.spy(); - const dispatch = chai.spy(); + function executorA(command, dispatch, getState) { + expect(getState()).to.be.undefined; + } + function executorB(command, dispatch, getState) { + expect(getState()).to.be.undefined; + } const executorAB = combineExecutors({ a: executorA, b: executorB }); - executorAB({ type: 'FOO()' }, dispatch, undefined); - expect(executorA).to.have.been.called.with({ type: 'FOO()' }, dispatch, undefined); - expect(executorB).to.have.been.called.with({ type: 'FOO()' }, dispatch, undefined); + executorAB({ type: 'FOO()' }, dumbDispatch, getUndefinedState); }); - // - // it('should throw an exception for call with invalid argument', () => { - // expect(() => { (reduceExecutors as any)({ 'foo': 'bar' }); }).to.throw(Error); - // expect(() => { (reduceExecutors as any)([function() {}, undefined]); }).to.throw(Error); - // }); }); diff --git a/test/createExecutorEnhancer.spec.ts b/test/createExecutorEnhancer.spec.ts index ab8dec8..fed7c36 100644 --- a/test/createExecutorEnhancer.spec.ts +++ b/test/createExecutorEnhancer.spec.ts @@ -6,6 +6,14 @@ import { createExecutorEnhancer, EXECUTOR_INIT } from '../src/index'; chai.use(spies); describe('createExecutorEnhancer', () => { + let dumbReducer, dumbDispatch, dumbState, dumbGetState, dumbExecutor; + + beforeEach(() => { + dumbReducer = (state) => state; + dumbState = {}; + dumbGetState = () => ({}); + dumbDispatch = dumbExecutor = () => undefined; + }); it('should export createExecutorEnhancer function', () => { expect(createExecutorEnhancer).to.be.function; @@ -23,17 +31,11 @@ describe('createExecutorEnhancer', () => { }); it('should create enhancer that creates store with ExecutableStore interface', () => { - function dumbReducer(state) { - return state; - } - function dumbExecutor() { - } - const dumbState = {}; function createStore() { return { - dispatch: () => {}, + dispatch: dumbDispatch, subscribe: chai.spy(), - getState: () => dumbState, + getState: dumbGetState, replaceReducer: () => {} }; } @@ -60,20 +62,16 @@ describe('createExecutorEnhancer', () => { }); it('should create enhancer that creates store with valid replaceExecutor function', () => { - function dumbReducer(state) { - return state; - } - function dumbExecutor() { - } function nextExecutor() { return Promise.resolve(); } const dispatchSpy = chai.spy(); + function createStore() { return { dispatch: dispatchSpy, subscribe: chai.spy(), - getState: () => ({}), + getState: dumbGetState, replaceReducer: chai.spy(), replaceExecutor: chai.spy() }; @@ -88,6 +86,7 @@ describe('createExecutorEnhancer', () => { expect(dispatchSpy).to.not.have.been.called; const commandResult = executableStore.dispatch({ type: 'DETECTOR_COMMAND()' }); + expect(dispatchSpy).to.not.have.been.called; expect(commandResult).to.exist; expect(commandResult.promise).to.exist; @@ -95,7 +94,11 @@ describe('createExecutorEnhancer', () => { executableStore.replaceExecutor(nextExecutorSpy); expect(dispatchSpy).to.not.have.been.called; - expect(nextExecutorSpy).to.have.been.called.with({ type: EXECUTOR_INIT }, executableStore.dispatch, {}); + expect(nextExecutorSpy).to.have.been.called.with( + { type: EXECUTOR_INIT }, + executableStore.dispatch, + executableStore.getState + ); const nextCommandResult = executableStore.dispatch({ type: 'NEXT_DETECTOR_COMMAND()' }); expect(dispatchSpy).to.not.have.been.called; @@ -103,7 +106,11 @@ describe('createExecutorEnhancer', () => { expect(nextCommandResult.promise).to.exist; expect(nextCommandResult.promise.then).to.be.function; - expect(nextExecutorSpy).to.have.been.called.with({ type: 'NEXT_DETECTOR_COMMAND()' }, executableStore.dispatch, {}); + expect(nextExecutorSpy).to.have.been.called.with( + { type: 'NEXT_DETECTOR_COMMAND()' }, + executableStore.dispatch, + executableStore.getState + ); expect(dispatchSpy).to.not.have.been.called; executableStore.dispatch({ type: 'NON_COMMAND' }); diff --git a/test/handleCommand.spec.ts b/test/handleCommand.spec.ts index b12f112..8f853bb 100644 --- a/test/handleCommand.spec.ts +++ b/test/handleCommand.spec.ts @@ -12,9 +12,9 @@ describe('handleCommand', () => { }); it('should return executor that runs only for given command type', () => { + const dumbGetState = () => ({}); const executorSpy = chai.spy(); const dispatchSpy = chai.spy(); - const dumbState = {}; const targetedExecutor = handleCommand('COMMAND_TYPE()', executorSpy); @@ -22,18 +22,18 @@ describe('handleCommand', () => { expect(executorSpy).to.not.have.been.called; // expect that executor will bypass this command - targetedExecutor({ type: 'ANOTHER_COMMAND_TYPE()' }, dispatchSpy, dumbState); + targetedExecutor({ type: 'ANOTHER_COMMAND_TYPE()' }, dispatchSpy, dumbGetState); expect(executorSpy).to.not.have.been.called; expect(dispatchSpy).to.not.have.been.called; // expect that executor will bypass similar non command - targetedExecutor({ type: 'COMMAND_TYPE' }, dispatchSpy, dumbState); + targetedExecutor({ type: 'COMMAND_TYPE' }, dispatchSpy, dumbGetState); expect(executorSpy).to.not.have.been.called; expect(dispatchSpy).to.not.have.been.called; // expect that executor will call wrapped executor - targetedExecutor({ type: 'COMMAND_TYPE()' }, dispatchSpy, dumbState); - expect(executorSpy).to.have.been.called.with({ type: 'COMMAND_TYPE()' }, dispatchSpy, dumbState); + targetedExecutor({ type: 'COMMAND_TYPE()' }, dispatchSpy, dumbGetState); + expect(executorSpy).to.have.been.called.with({ type: 'COMMAND_TYPE()' }, dispatchSpy, dumbGetState); expect(dispatchSpy).to.have.been.called; }); diff --git a/test/handleCommands.spec.ts b/test/handleCommands.spec.ts index b9c92cd..9a81c81 100644 --- a/test/handleCommands.spec.ts +++ b/test/handleCommands.spec.ts @@ -12,10 +12,10 @@ describe('handleCommands', () => { }); it('should return executor that runs only for given command types', () => { + const dumbGetState = () => ({}); const executorSpyA = chai.spy(); const executorSpyB = chai.spy(); const dispatchSpy = chai.spy(); - const dumbState = {}; const targetedExecutor = handleCommands({ 'COMMAND_TYPE_A()': executorSpyA, @@ -27,25 +27,25 @@ describe('handleCommands', () => { expect(executorSpyB).to.not.have.been.called; // expect that executor will bypass this command - targetedExecutor({ type: 'ANOTHER_COMMAND_TYPE()' }, dispatchSpy, dumbState); + targetedExecutor({ type: 'ANOTHER_COMMAND_TYPE()' }, dispatchSpy, dumbGetState); expect(executorSpyA).to.not.have.been.called; expect(executorSpyB).to.not.have.been.called; expect(dispatchSpy).to.not.have.been.called; // expect that executor will bypass similar non command - targetedExecutor({ type: 'COMMAND_TYPE_A' }, dispatchSpy, dumbState); + targetedExecutor({ type: 'COMMAND_TYPE_A' }, dispatchSpy, dumbGetState); expect(executorSpyA).to.not.have.been.called; expect(executorSpyB).to.not.have.been.called; expect(dispatchSpy).to.not.have.been.called; // expect that executor will call wrapped executor A - targetedExecutor({ type: 'COMMAND_TYPE_A()' }, dispatchSpy, dumbState); - expect(executorSpyA).to.have.been.called.with({ type: 'COMMAND_TYPE_A()' }, dispatchSpy, dumbState); + targetedExecutor({ type: 'COMMAND_TYPE_A()' }, dispatchSpy, dumbGetState); + expect(executorSpyA).to.have.been.called.with({ type: 'COMMAND_TYPE_A()' }, dispatchSpy, dumbGetState); expect(executorSpyB).to.not.have.been.called; expect(dispatchSpy).to.have.been.called; // expect that executor will call wrapped executor B - targetedExecutor({ type: 'COMMAND_TYPE_B()' }, dispatchSpy, dumbState); - expect(executorSpyB).to.have.been.called.with({ type: 'COMMAND_TYPE_B()' }, dispatchSpy, dumbState); + targetedExecutor({ type: 'COMMAND_TYPE_B()' }, dispatchSpy, dumbGetState); + expect(executorSpyB).to.have.been.called.with({ type: 'COMMAND_TYPE_B()' }, dispatchSpy, dumbGetState); }); }); diff --git a/test/isCommand.spec.ts b/test/isCommand.spec.ts index 20c601b..ee31c30 100644 --- a/test/isCommand.spec.ts +++ b/test/isCommand.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { isCommand } from '../src/index'; -describe('reduceExecutors', () => { +describe('isCommand', () => { it('should export isCommand function', () => { expect(isCommand).to.be.function; }); @@ -21,6 +21,7 @@ describe('reduceExecutors', () => { expect(isCommand({ type: 'SOME_TYPE)' })).to.be.false; expect(isCommand({ type: 'SOME_TYPE(' })).to.be.false; expect(isCommand({ type: 'SOME_TYP(E)' })).to.be.false; + expect(isCommand({ type: 'SOME_TYPE()'.split('') as any })).to.be.false; expect(isCommand({ type: 'SOME_TYPE()' })).to.be.true; }); diff --git a/test/isCommandType.spec.ts b/test/isCommandType.spec.ts new file mode 100644 index 0000000..016c5dd --- /dev/null +++ b/test/isCommandType.spec.ts @@ -0,0 +1,28 @@ + +import { expect } from 'chai'; +import { isCommandType } from '../src/index'; + +describe('isCommandType', () => { + it('should export isCommandType function', () => { + expect(isCommandType).to.be.function; + }); + + it('should check if type is command type', () => { + expect(isCommandType(undefined)).to.be.false; + expect(isCommandType(null)).to.be.false; + expect(isCommandType(NaN as any)).to.be.false; + expect(isCommandType(false as any)).to.be.false; + expect(isCommandType(true as any)).to.be.false; + expect(isCommandType((() => {}) as any)).to.be.false; + expect(isCommandType(0 as any)).to.be.false; + expect(isCommandType({} as any)).to.be.false; + expect(isCommandType('SOME_TYPE')).to.be.false; + expect(isCommandType('SOME_TYPE( )')).to.be.false; + expect(isCommandType('SOME_TYPE)')).to.be.false; + expect(isCommandType('SOME_TYPE(' )).to.be.false; + expect(isCommandType('SOME_TYP(E)')).to.be.false; + expect(isCommandType('SOME_TYPE()'.split('') as any)).to.be.false; + + expect(isCommandType('SOME_TYPE()')).to.be.true; + }); +}); diff --git a/test/mountExecutor.spec.ts b/test/mountExecutor.spec.ts index 8d10503..28ac896 100644 --- a/test/mountExecutor.spec.ts +++ b/test/mountExecutor.spec.ts @@ -13,20 +13,21 @@ describe('mountExecutor', () => { }); it('should mount executor using selector', () => { - const state = { + const getState = () => ({ branchA: { subBranchB: { value: 1 } } - }; - function dumbDispatch(action) { - } - function executor(command, dispatch, state) { - if (state && state.value === 1 && command && command.type === 'COMMAND_THROUGH_MOUNT()') { + }); + const dumbDispatch = () => {}; + + function executor(command, dispatch, getState) { + if (getState() && getState().value === 1 && command && command.type === 'COMMAND_THROUGH_MOUNT()') { dispatch({type: 'SELECTORS_WORKED'}); } } + function selector(state) { return state.branchA.subBranchB; } @@ -39,7 +40,7 @@ describe('mountExecutor', () => { mountedExecutor( { type: 'COMMAND_THROUGH_MOUNT()' }, dumbDispatchSpy, - state + getState ); expect(dumbDispatchSpy).to.have.been.called.once.with({type: 'SELECTORS_WORKED'}); diff --git a/test/reduceExecutors.spec.ts b/test/reduceExecutors.spec.ts index 6db58f8..bca29de 100644 --- a/test/reduceExecutors.spec.ts +++ b/test/reduceExecutors.spec.ts @@ -1,94 +1,59 @@ import * as chai from 'chai'; import * as spies from 'chai-spies'; +import * as promised from 'chai-as-promised'; import { expect } from 'chai'; import { reduceExecutors } from '../src/index'; chai.use(spies); +chai.use(promised); describe('reduceExecutors', () => { + let executorResolved, executorRejected, executorVoid; + let dumbDispatch, dumbGetState; + + beforeEach(() => { + executorResolved = () => Promise.resolve(); + executorRejected = () => Promise.reject(new Error()); + executorVoid = () => undefined; + + dumbDispatch = () => undefined; + dumbGetState = () => ({}) + }); + it('should export reduceExecutors function', () => { expect(reduceExecutors).to.be.function; }); it('should return valid executor for reduction of two executors', () => { - let promiseAResolve, promiseAReject; - let promiseBResolve, promiseBReject; - - function executorA() { - return new Promise((resolve, reject) => { promiseAResolve = resolve; promiseAReject = reject; }); - } + const executor = reduceExecutors(executorResolved, executorVoid); - function executorB() { - return new Promise((resolve, reject) => { promiseBResolve = resolve; promiseBReject = reject; }); - } - - const executorAB = reduceExecutors(executorA, executorB); - - expect(executorAB).to.be.function; - let promise = executorAB({ type: 'FOO()' }, () => {}, {}); - - let thenSpy = chai.spy(); - let catchSpy = chai.spy(); + expect(executor).to.be.function; + let promise: Promise = executor({ type: 'FOO()' }, dumbDispatch, dumbGetState) as Promise; expect(promise).to.exist; - expect((promise as Promise).then).to.be.function; - expect((promise as Promise).catch).to.be.function; - - (promise as Promise).then(thenSpy).catch(catchSpy); - - // check promises combination - expect(thenSpy).to.not.have.been.called; - expect(catchSpy).to.not.have.been.called; + expect(promise.then).to.be.function; + expect(promise.catch).to.be.function; - promiseAResolve(); - - expect(thenSpy).to.not.have.been.called; - expect(catchSpy).to.not.have.been.called; - - promiseBResolve(); - - expect(thenSpy).to.have.been.called; - expect(catchSpy).to.have.been.called; + expect(promise).to.be.fulfilled; + expect(promise).to.become(undefined); }); it('should return executor that rejects on children reject', () => { - let promiseAResolve, promiseAReject; - let promiseBResolve, promiseBReject; - - function executorA() { - return new Promise((resolve, reject) => { promiseAResolve = resolve; promiseAReject = reject; }); - } - - function executorB() { - return new Promise((resolve, reject) => { promiseBResolve = resolve; promiseBReject = reject; }); - } + const executor = reduceExecutors(executorResolved, executorRejected, executorVoid); - const executorAB = reduceExecutors(executorA, executorB); - - expect(executorAB).to.be.function; - let promise = executorAB({ type: 'FOO()' }, () => {}, {}); - - let thenSpy = chai.spy(); - let catchSpy = chai.spy(); + expect(executor).to.be.function; + let promise: Promise = executor({ type: 'FOO()' }, dumbDispatch, dumbGetState) as Promise; expect(promise).to.exist; - expect((promise as Promise).then).to.be.function; - expect((promise as Promise).catch).to.be.function; - - (promise as Promise).then(thenSpy).catch(catchSpy); - - // check promises combination - expect(thenSpy).to.not.have.been.called; - expect(catchSpy).to.not.have.been.called; - - promiseAReject(); + expect(promise.then).to.be.function; + expect(promise.catch).to.be.function; - expect(thenSpy).to.not.have.been.called; - expect(catchSpy).to.have.been.called; + expect(promise).to.be.rejected; }); it('should throw an exception for call with invalid argument', () => { + expect(() => { (reduceExecutors as any)(undefined); }).to.throw(Error); expect(() => { (reduceExecutors as any)({ 'foo': 'bar' }); }).to.throw(Error); expect(() => { (reduceExecutors as any)([function() {}, undefined]); }).to.throw(Error); }); diff --git a/tsconfig.json b/tsconfig.json index 07af723..6056eef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,10 @@ "lib": [ "dom", "es6" + ], + "typeRoots": [ + "./node_modules/@types", + "./types" ] }, "include": [ diff --git a/types/lodash.isstring/index.d.ts b/types/lodash.isstring/index.d.ts new file mode 100644 index 0000000..26c4160 --- /dev/null +++ b/types/lodash.isstring/index.d.ts @@ -0,0 +1,7 @@ + +declare module 'lodash.isstring' { + import { isString } from 'lodash'; + const method: typeof isString; + + export = method; +} diff --git a/yarn.lock b/yarn.lock index 0fa0da2..3dab889 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,10 @@ # yarn lockfile v1 +"@types/lodash@^4.14.62": + version "4.14.62" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.62.tgz#8674f9861582148a60b7a89cb260f11378d11683" + "@types/mocha@^2.2.40": version "2.2.40" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.40.tgz#9811dd800ece544cd84b5b859917bf584a150c4c" @@ -168,10 +172,6 @@ async@^2.1.2, async@^2.1.4: dependencies: lodash "^4.14.0" -async@~0.2.6: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" - async@~0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" @@ -1882,6 +1882,10 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + lodash.keys@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -3103,16 +3107,7 @@ typescript@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.2.tgz#606022508479b55ffa368b58fee963a03dfd7b0c" -uglify-js@^2.6: - version "2.7.5" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" - dependencies: - async "~0.2.6" - source-map "~0.5.1" - uglify-to-browserify "~1.0.0" - yargs "~3.10.0" - -uglify-js@^2.8.5: +uglify-js@^2.6, uglify-js@^2.8.5: version "2.8.22" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.22.tgz#d54934778a8da14903fa29a326fb24c0ab51a1a0" dependencies: From 08645cd464f7eb65f98da7800049c55dc2902e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ole=C5=9B?= Date: Fri, 14 Apr 2017 00:16:59 +0200 Subject: [PATCH 09/10] Self CR --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1fc74d..ca38705 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ export const parallelCommandExecutor = handleCommand( ); ``` -With this executors we can create action creator instead of executor for the previous example. +With these executors we can create action creator instead of executor for the previous example. ```js // import action creators import { firstCommand, secondCommand, thirdCommand } from './commands/exampleCommands'; From ac7d8cc79d8a460adda13605b51ab5f1954e4376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ole=C5=9B?= Date: Thu, 20 Apr 2017 21:46:44 +0200 Subject: [PATCH 10/10] Version 0.6.0-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 355a49c..552f8d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redux-executor", - "version": "0.5.3", + "version": "0.6.0-0", "description": "Redux enhancer for handling side effects.", "main": "lib/index.js", "types": "lib/index.d.ts",