function liftReducerWith(reducer, initialCommittedState, monitorReducer, options) {
	  var initialLiftedState = {
	    monitorState: monitorReducer(undefined, {}),
	    nextActionId: 1,
	    actionsById: { 0: liftAction(INIT_ACTION) },
	    stagedActionIds: [0],
	    skippedActionIds: [],
	    committedState: initialCommittedState,
	    currentStateIndex: 0,
	    computedStates: [],
	    isLocked: options.shouldStartLocked === true,
	    isPaused: options.shouldRecordChanges === false
	  };

	  /**
	   * Manages how the history actions modify the history state.
	   */
	  return function (liftedState, liftedAction) {
	    var _ref = liftedState || initialLiftedState;

	    var monitorState = _ref.monitorState;
	    var actionsById = _ref.actionsById;
	    var nextActionId = _ref.nextActionId;
	    var stagedActionIds = _ref.stagedActionIds;
	    var skippedActionIds = _ref.skippedActionIds;
	    var committedState = _ref.committedState;
	    var currentStateIndex = _ref.currentStateIndex;
	    var computedStates = _ref.computedStates;
	    var isLocked = _ref.isLocked;
	    var isPaused = _ref.isPaused;


	    if (!liftedState) {
	      // Prevent mutating initialLiftedState
	      actionsById = _extends({}, actionsById);
	    }

	    function commitExcessActions(n) {
	      // Auto-commits n-number of excess actions.
	      var excess = n;
	      var idsToDelete = stagedActionIds.slice(1, excess + 1);

	      for (var i = 0; i < idsToDelete.length; i++) {
	        if (computedStates[i + 1].error) {
	          // Stop if error is found. Commit actions up to error.
	          excess = i;
	          idsToDelete = stagedActionIds.slice(1, excess + 1);
	          break;
	        } else {
	          delete actionsById[idsToDelete[i]];
	        }
	      }

	      skippedActionIds = skippedActionIds.filter(function (id) {
	        return idsToDelete.indexOf(id) === -1;
	      });
	      stagedActionIds = [0].concat(stagedActionIds.slice(excess + 1));
	      committedState = computedStates[excess].state;
	      computedStates = computedStates.slice(excess);
	      currentStateIndex = currentStateIndex > excess ? currentStateIndex - excess : 0;
	    }

	    function computePausedAction(shouldInit) {
	      var _extends2;

	      var computedState = void 0;
	      if (shouldInit) {
	        computedState = computedStates[currentStateIndex];
	        monitorState = monitorReducer(monitorState, liftedAction);
	      } else {
	        computedState = computeNextEntry(reducer, liftedAction.action, computedStates[currentStateIndex].state, false);
	      }
	      if (!options.pauseActionType || nextActionId === 1) {
	        return {
	          monitorState: monitorState,
	          actionsById: { 0: liftAction(INIT_ACTION) },
	          nextActionId: 1,
	          stagedActionIds: [0],
	          skippedActionIds: [],
	          committedState: computedState.state,
	          currentStateIndex: 0,
	          computedStates: [computedState],
	          isLocked: isLocked,
	          isPaused: true
	        };
	      }
	      if (shouldInit) {
	        stagedActionIds = [].concat(stagedActionIds, [nextActionId]);
	        nextActionId++;
	        currentStateIndex++;
	      }
	      return {
	        monitorState: monitorState,
	        actionsById: _extends({}, actionsById, (_extends2 = {}, _extends2[nextActionId - 1] = liftAction({ type: options.pauseActionType }), _extends2)),
	        nextActionId: nextActionId,
	        stagedActionIds: stagedActionIds,
	        skippedActionIds: skippedActionIds,
	        committedState: committedState,
	        currentStateIndex: currentStateIndex,
	        computedStates: [].concat(computedStates.slice(0, currentStateIndex), [computedState]),
	        isLocked: isLocked,
	        isPaused: true
	      };
	    }

	    // By default, agressively recompute every state whatever happens.
	    // This has O(n) performance, so we'll override this to a sensible
	    // value whenever we feel like we don't have to recompute the states.
	    var minInvalidatedStateIndex = 0;

	    switch (liftedAction.type) {
	      case ActionTypes.PERFORM_ACTION:
	        {
	          if (isLocked) return liftedState || initialLiftedState;
	          if (isPaused) return computePausedAction();

	          // Auto-commit as new actions come in.
	          if (options.maxAge && stagedActionIds.length === options.maxAge) {
	            commitExcessActions(1);
	          }

	          if (currentStateIndex === stagedActionIds.length - 1) {
	            currentStateIndex++;
	          }
	          var actionId = nextActionId++;
	          // Mutation! This is the hottest path, and we optimize on purpose.
	          // It is safe because we set a new key in a cache dictionary.
	          actionsById[actionId] = liftedAction;
	          stagedActionIds = [].concat(stagedActionIds, [actionId]);
	          // Optimization: we know that only the new action needs computing.
	          minInvalidatedStateIndex = stagedActionIds.length - 1;
	          break;
	        }
	      case ActionTypes.RESET:
	        {
	          // Get back to the state the store was created with.
	          actionsById = { 0: liftAction(INIT_ACTION) };
	          nextActionId = 1;
	          stagedActionIds = [0];
	          skippedActionIds = [];
	          committedState = initialCommittedState;
	          currentStateIndex = 0;
	          computedStates = [];
	          break;
	        }
	      case ActionTypes.COMMIT:
	        {
	          // Consider the last committed state the new starting point.
	          // Squash any staged actions into a single committed state.
	          actionsById = { 0: liftAction(INIT_ACTION) };
	          nextActionId = 1;
	          stagedActionIds = [0];
	          skippedActionIds = [];
	          committedState = computedStates[currentStateIndex].state;
	          currentStateIndex = 0;
	          computedStates = [];
	          break;
	        }
	      case ActionTypes.ROLLBACK:
	        {
	          // Forget about any staged actions.
	          // Start again from the last committed state.
	          actionsById = { 0: liftAction(INIT_ACTION) };
	          nextActionId = 1;
	          stagedActionIds = [0];
	          skippedActionIds = [];
	          currentStateIndex = 0;
	          computedStates = [];
	          break;
	        }
	      case ActionTypes.TOGGLE_ACTION:
	        {
	          var _ret = function () {
	            // Toggle whether an action with given ID is skipped.
	            // Being skipped means it is a no-op during the computation.
	            var actionId = liftedAction.id;

	            var index = skippedActionIds.indexOf(actionId);
	            if (index === -1) {
	              skippedActionIds = [actionId].concat(skippedActionIds);
	            } else {
	              skippedActionIds = skippedActionIds.filter(function (id) {
	                return id !== actionId;
	              });
	            }
	            // Optimization: we know history before this action hasn't changed
	            minInvalidatedStateIndex = stagedActionIds.indexOf(actionId);
	            return 'break';
	          }();

	          if (_ret === 'break') break;
	        }
	      case ActionTypes.SET_ACTIONS_ACTIVE:
	        {
	          // Toggle whether an action with given ID is skipped.
	          // Being skipped means it is a no-op during the computation.
	          var start = liftedAction.start;
	          var end = liftedAction.end;
	          var active = liftedAction.active;

	          var actionIds = [];
	          for (var i = start; i < end; i++) {
	            actionIds.push(i);
	          }if (active) {
	            skippedActionIds = (0, _difference2.default)(skippedActionIds, actionIds);
	          } else {
	            skippedActionIds = (0, _union2.default)(skippedActionIds, actionIds);
	          }

	          // Optimization: we know history before this action hasn't changed
	          minInvalidatedStateIndex = stagedActionIds.indexOf(start);
	          break;
	        }
	      case ActionTypes.JUMP_TO_STATE:
	        {
	          // Without recomputing anything, move the pointer that tell us
	          // which state is considered the current one. Useful for sliders.
	          currentStateIndex = liftedAction.index;
	          // Optimization: we know the history has not changed.
	          minInvalidatedStateIndex = Infinity;
	          break;
	        }
	      case ActionTypes.SWEEP:
	        {
	          // Forget any actions that are currently being skipped.
	          stagedActionIds = (0, _difference2.default)(stagedActionIds, skippedActionIds);
	          skippedActionIds = [];
	          currentStateIndex = Math.min(currentStateIndex, stagedActionIds.length - 1);
	          break;
	        }
	      case ActionTypes.IMPORT_STATE:
	        {
	          if (Array.isArray(liftedAction.nextLiftedState)) {
	            // recompute array of actions
	            actionsById = { 0: liftAction(INIT_ACTION) };
	            nextActionId = 1;
	            stagedActionIds = [0];
	            skippedActionIds = [];
	            currentStateIndex = liftedAction.nextLiftedState.length;
	            computedStates = [];
	            committedState = liftedAction.preloadedState;
	            minInvalidatedStateIndex = 0;
	            // iterate through actions
	            liftedAction.nextLiftedState.forEach(function (action) {
	              actionsById[nextActionId] = liftAction(action);
	              stagedActionIds.push(nextActionId);
	              nextActionId++;
	            });
	          } else {
	            var _liftedAction$nextLif = liftedAction.nextLiftedState;
	            // Completely replace everything.

	            monitorState = _liftedAction$nextLif.monitorState;
	            actionsById = _liftedAction$nextLif.actionsById;
	            nextActionId = _liftedAction$nextLif.nextActionId;
	            stagedActionIds = _liftedAction$nextLif.stagedActionIds;
	            skippedActionIds = _liftedAction$nextLif.skippedActionIds;
	            committedState = _liftedAction$nextLif.committedState;
	            currentStateIndex = _liftedAction$nextLif.currentStateIndex;
	            computedStates = _liftedAction$nextLif.computedStates;


	            if (liftedAction.noRecompute) {
	              minInvalidatedStateIndex = Infinity;
	            }
	          }

	          break;
	        }
	      case ActionTypes.LOCK_CHANGES:
	        {
	          isLocked = liftedAction.status;
	          minInvalidatedStateIndex = Infinity;
	          break;
	        }
	      case ActionTypes.PAUSE_RECORDING:
	        {
	          isPaused = liftedAction.status;
	          if (isPaused) return computePausedAction(true);
	          minInvalidatedStateIndex = Infinity;
	          break;
	        }
	      case '@@redux/INIT':
	        {
	          if (options.shouldHotReload === false && liftedState) {
	            return liftedState;
	          }

	          // Recompute states on hot reload and init.
	          minInvalidatedStateIndex = 0;

	          if (options.maxAge && stagedActionIds.length > options.maxAge) {
	            // States must be recomputed before committing excess.
	            computedStates = recomputeStates(computedStates, minInvalidatedStateIndex, reducer, committedState, actionsById, stagedActionIds, skippedActionIds, options.shouldCatchErrors);

	            commitExcessActions(stagedActionIds.length - options.maxAge);

	            // Avoid double computation.
	            minInvalidatedStateIndex = Infinity;
	          }

	          break;
	        }
	      default:
	        {
	          // If the action is not recognized, it's a monitor action.
	          // Optimization: a monitor action can't change history.
	          minInvalidatedStateIndex = Infinity;
	          break;
	        }
	    }

	    computedStates = recomputeStates(computedStates, minInvalidatedStateIndex, reducer, committedState, actionsById, stagedActionIds, skippedActionIds, options.shouldCatchErrors);
	    monitorState = monitorReducer(monitorState, liftedAction);
	    return {
	      monitorState: monitorState,
	      actionsById: actionsById,
	      nextActionId: nextActionId,
	      stagedActionIds: stagedActionIds,
	      skippedActionIds: skippedActionIds,
	      committedState: committedState,
	      currentStateIndex: currentStateIndex,
	      computedStates: computedStates,
	      isLocked: isLocked,
	      isPaused: isPaused
	    };
	  };
	}
Creates a history state reducer from an app's reducer.