<|start_filename|>src/utils/compose.js<|end_filename|>
/*
 https://github.com/gaearon/redux/blob/breaking-changes-1.0/src/utils/compose.js
 */
/**
 * Composes functions from left to right.
 *
 * @param {...Function} funcs - The functions to compose.
 * @returns {Function} A function that passes its only argument to the first of
 * the `funcs`, then pipes its return value to the second one, and so on, until
 * the last of the `funcs` is called, and its result is returned.
 */
export default function compose(...funcs) {
  return funcs.reduceRight((composed, f) => f(composed));
}


<|start_filename|>examples/counter-rx/index.js<|end_filename|>
import {createStore, combineReducers} from 'rx-redux';
import thunkMiddleware from './rx-middleware/thunkMiddleware';
import promiseMiddleware from './rx-middleware/promiseMiddleware';
import delayMiddleware from './rx-middleware/delayMiddleware';
import { DUMMY_ACTION } from './constants/ActionTypes';
import * as reducers from './reducers';
import { render, getActionStream } from './view';

const reducer = combineReducers(reducers);
const store = createStore(reducer);

// stream states to view
store.state$.subscribe(state => render(state));

function applyRxMiddleware(action$, ...middleware) {
  return middleware.reduce((prev, cur) => {
    return prev.flatMap(cur(store.getState));
  }, action$);
}

const action$ = getActionStream();
const actionWithMiddleware$ = applyRxMiddleware(action$, thunkMiddleware, promiseMiddleware, delayMiddleware);
const cleanAction$ = actionWithMiddleware$.filter(action => action.type !== DUMMY_ACTION);

// stream actions to dispatcher
cleanAction$.subscribe(action => store.dispatcher$.onNext(action));

<|start_filename|>examples/counter/index.js<|end_filename|>
import {createStore, combineReducers, applyMiddleware} from 'rx-redux'
import thunkMiddleware from 'redux-thunk'
import promiseMiddleware from './middleware/promiseMiddleware'
import * as reducers from './reducers'
import { render, getActionStream } from './view'

const action$ = getActionStream();

const newCreateStore = applyMiddleware(thunkMiddleware, promiseMiddleware)(createStore);
const reducer = combineReducers(reducers);
const store = newCreateStore(reducer);

// stream states to view
store.state$.subscribe(state => render(state));

// stream actions to dispatcher
action$.subscribe(action => store.dispatcher$.onNext(action));

<|start_filename|>examples/counter/view/index.js<|end_filename|>
import Rx from 'rx'
import * as CounterActions from '../actions/CounterActions'

const {when, fromEvent} = Rx.Observable;

const render = (() => {
  const counter = document.getElementById('count');

  return (state) => {
    console.log('render UI with state:', state);
    counter.textContent = state.counter.toString();
  }
})();

function get(id) {
  return document.getElementById(id);
}

function getActionStream() {
  return when(
      fromEvent(get('btn-decrease'), 'click').thenDo(CounterActions.decrement),
      fromEvent(get('btn-increase'), 'click').thenDo(CounterActions.increment),
      fromEvent(get('btn-odd'), 'click').thenDo(CounterActions.incrementIfOdd),
      fromEvent(get('btn-timeout'), 'click').thenDo(CounterActions.incrementTimeout),
      fromEvent(get('btn-promise'), 'click').thenDo(CounterActions.incrementPromise)
  )
}

export default {
  render,
  getActionStream
};


<|start_filename|>src/utils/bindActionCreators.js<|end_filename|>
export default function bindActionCreators(actionCreators, dispatch) {
  return Object.keys(actionCreators).reduce((result, key) => {
    const actionCreator = actionCreators[key];
    if (typeof actionCreator === 'function') {
      result[key] = (...args) => dispatch(actionCreator(...args));
    }

    return result;
  }, {});
}


<|start_filename|>examples/counter-rx/rx-middleware/thunkMiddleware.js<|end_filename|>
import Rx from 'rx';

export default function thunkMiddleware(getState) {
  return action => {
    if(typeof action === 'function') {
      return Rx.Observable.just(action(getState));
    }

    return Rx.Observable.just(action);
  };
}


<|start_filename|>src/utils/applyMiddleware.js<|end_filename|>
import Rx from 'rx';
import compose from './compose';

function createNewDispatch(middleware, store) {
  const dispatchFunctions = middleware.map(m => m(store));
  dispatchFunctions.push(store.dispatch);

  return compose(...dispatchFunctions);
}

function createNewDispatcher(middleware, store) {
  const newDispatch = createNewDispatch(middleware, store);
  const newDispatcher$ = new Rx.Subject();
  newDispatcher$.subscribe(newDispatch);

  return newDispatcher$;
}

export default function applyMiddleware(...middleware) {
  return (createStore) => (reducer, initState) => {
    const store = createStore(reducer, initState);
    const newDispatcher$ = createNewDispatcher(middleware, store);

    store.dispatcher$ = newDispatcher$;
    store.dispatch = (action) => {
      newDispatcher$.onNext(action);
      return action;
    };

    return store;
  };
}


<|start_filename|>examples/counter-rx/index.html<|end_filename|>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>rx-redux</title>
</head>
<body>
    <h1>Counter demo with rx-redux</h1>
    <button id="btn-decrease">-</button>
    <span id="count"></span>
    <button id="btn-increase">+</button>
    <button id="btn-odd">Increment if odd</button>
    <button id="btn-timeout">Increment after 1 sec.</button>
    <button id="btn-promise">Increment from promise.</button>
    <script src="/bundle.js"></script>
</body>
</html>

<|start_filename|>src/utils/combineReducers.js<|end_filename|>
function pickReducer(reducers) {
  return Object.keys(reducers).reduce((result, key) => {
    if (typeof reducers[key] === 'function') {
      result[key] = reducers[key];
    }

    return result;
  }, {});
}

export default function combineReducers(reducers) {
  const finalReducers = pickReducer(reducers);
  const keys = Object.keys(finalReducers);

  return (state = {}, action = undefined) => {
    return keys.reduce((result, key) => {
      result[key] = finalReducers[key](state[key], action);
      return result;
    }, {});
  };
}


<|start_filename|>src/createStore.js<|end_filename|>
import Rx from 'rx';
import isPlainObject from './utils/isPlainObject';

export default function createStore(reducer, initState) {
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.');
  }

  const initAction = {type: '@@rx-redux/INIT_' + (new Date()).getTime()};
  const listeners = [];
  const dispatcher$ = new Rx.Subject();

  let currentReducer = reducer;
  let state = currentReducer(initState, initAction);

  function callListeners() {
    listeners.forEach(listener => listener());
  }

  function dispatch(action) {
    dispatcher$.onNext(action);
    return action;
  }

  function subscribe(listener) {
    listeners.push(listener);

    return function unsubscribe() {
      const index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  function replaceReducer(newReducer) {
    currentReducer = newReducer;
    dispatcher$.onNext(initAction);
  }

  function reduce(action) {
    if (!isPlainObject(action)) {
      throw new Error('Actions must be plain objects.');
    }

    state = currentReducer(state, action);

    return state;
  }

  const state$ = dispatcher$.map(reduce).publish().refCount().startWith(state);

  // must call state$.subscribe() to start life cycle
  state$.subscribe(
    callListeners,
    err => { throw err; }
  );

  return {
    state$,
    dispatcher$,
    getState: () => state,
    dispatch,
    subscribe,
    getReducer: () => currentReducer,
    replaceReducer
  };
}


<|start_filename|>examples/counter-rx/rx-middleware/delayMiddleware.js<|end_filename|>
import { INCREMENT_DELAY } from '../constants/ActionTypes';
import Rx from 'rx';

export default function thunkMiddleware(getState) {
  return action => {
    if(action.type === INCREMENT_DELAY) {
      return Rx.Observable.just(action.payload.action).delay(action.payload.time);
    }

    return Rx.Observable.just(action);
  };
}


<|start_filename|>examples/counter-rx/rx-middleware/promiseMiddleware.js<|end_filename|>
import Rx from 'rx';

function isPromise(val) {
  return val && typeof val.then === 'function';
}

export default function promiseMiddleware(getState) {
  return action => {
    if(isPromise(action)) {
      return Rx.Observable.fromPromise(action);
    }

    return Rx.Observable.just(action);
  };
}

<|start_filename|>src/utils/connectAction.js<|end_filename|>
export default function connectAction(action$, store) {
  action$.subscribe(
    (action) => {
      store.dispatch(action);
    },
    (err) => {
      store.dispatcher$.onError(err);
    },
    () => {
      store.dispatcher$.onCompleted();
    }
  );
}


<|start_filename|>examples/counter-react-redux/package.json<|end_filename|>
{
  "name": "counter-react-redux",
  "version": "0.1.0",
  "description": "Counter example for rx-redux",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server --inline --colors",
    "dev": "webpack-dev-server --config webpack.config.dev.js --hot --inline --devtool=eval-source-map --colors",
    "build": "NODE_ENV=production webpack --display-modules --progress",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/jas-chen/rx-redux.git"
  },
  "keywords": [
    "rx-redux",
    "redux",
    "reactive",
    "rx",
    "rxjs"
  ],
  "author": "<NAME>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/jas-chen/rx-redux/issues"
  },
  "homepage": "https://github.com/jas-chen/rx-redux#readme",
  "devDependencies": {
    "babel-core": "^5.8.3",
    "babel-loader": "^5.3.2",
    "react-hot-loader": "^1.2.8",
    "webpack": "^1.10.3",
    "webpack-dev-server": "^1.10.1"
  },
  "dependencies": {
    "react": "^0.13.3",
    "react-redux": "^0.2.2",
    "redux-thunk": "^0.1.0",
    "rx-lite": "^2.5.2",
    "rx-redux": "^0.5.7"
  }
}


<|start_filename|>examples/counter/middleware/promiseMiddleware.js<|end_filename|>
function isPromise(val) {
  return val && typeof val.then === 'function';
}

export default function promiseMiddleware({ dispatch, getState }) {
  return next => action =>
    isPromise(action)
      ? action.then(dispatch)
      : next(action);
}

<|start_filename|>examples/counter-rx/constants/ActionTypes.js<|end_filename|>
export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
export const INCREMENT_DELAY = 'INCREMENT_DELAY';
export const DUMMY_ACTION = 'DUMMY_ACTION';

<|start_filename|>package.json<|end_filename|>
{
  "name": "rx-redux",
  "version": "0.5.7",
  "description": "Reimplement redux using rxjs",
  "main": "lib/index.js",
  "scripts": {
    "clean": "rimraf lib dist",
    "build:lib": "babel src --out-dir lib",
    "build:umd": "webpack src/index.js dist/rx-redux.js --display-modules --progress && NODE_ENV=production webpack src/index.js dist/rx-redux.min.js --display-modules --progress",
    "build": "npm run build:lib && npm run build:umd",
    "lint": "eslint src",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/jas-chen/rx-redux.git"
  },
  "keywords": [
    "rx-redux",
    "redux",
    "reactive",
    "rx",
    "rxjs",
    "flux"
  ],
  "author": "<NAME>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/jas-chen/rx-redux/issues"
  },
  "homepage": "https://github.com/jas-chen/rx-redux#readme",
  "devDependencies": {
    "babel-core": "^5.8.3",
    "babel-eslint": "^4.0.5",
    "babel-loader": "^5.3.2",
    "eslint": "^0.24.1",
    "eslint-config-airbnb": "0.0.6",
    "rimraf": "^2.4.2",
    "rx": "^2.5.3",
    "webpack": "^1.10.3"
  }
}
