<commit_before>import equal from 'deep-equal';
import Immutable from 'immutable';
import save from './save.js';

const REINIT = '@@redux-pouchdb-plus/REINIT';
const INIT = '@@redux-pouchdb-plus/INIT';
const SET_REDUCER = '@@redux-pouchdb-plus/SET_REDUCER';

const initializedReducers = {};

export function reinit(reducerName) {
  const reducerNames = Object.keys(initializedReducers);

  if (!reducerName) { // reinit all reducers
    for (let n of reducerNames) {
      initializedReducers[n] = false;
    }
  }
  else { // reinit a specific reducer
    if (reducerNames.indexOf(reducerName) === -1)
      throw 'Invalid persistent reducer to reinit: ' + reducerName;

    initializedReducers[reducerName] = false;
  }

  return {type: REINIT, reducerName}
}

export const persistentStore = (storeOptions={}) => createStore => (reducer, initialState) => {
  const store = createStore(reducer, initialState);

  store.dispatch({
    type: INIT,
    store,
    storeOptions
  });

  return store;
}

export const persistentReducer = (reducer, reducerOptions={}) => {
  let initialState;
  let immutable;
  let store;
  let storeOptions;
  let changes;
  let saveReducer;
  let currentState;

  initializedReducers[reducer.name] = false;

  // call the provide (store only) callback as soon
  // as all persistent reducers are initialized
  function onReady() {
    if (storeOptions.onReady instanceof Function)
      storeOptions.onReady.call(null, store);
  }

  // call the provided callbacks as soon as this reducer
  // was initialized (loaded from or saved to the db)
  function onInit(state) {
    if (reducerOptions.onInit instanceof Function)
      reducerOptions.onInit.call(null, reducer.name, state, store);
    if (storeOptions.onInit instanceof Function)
      storeOptions.onInit.call(null, reducer.name, state, store);
  }

  // call the provided callbacks when this reducer
  // was updated with data from the db
  function onUpdate(state) {
    if (reducerOptions.onUpdate instanceof Function)
      reducerOptions.onUpdate.call(null, reducer.name, state, store);
    if (storeOptions.onUpdate instanceof Function)
      storeOptions.onUpdate.call(null, reducer.name, state, store);
  }

  // call the provided callbacks when the state
  // of this reducer was saved to the db
  function onSave(state) {
    if (reducerOptions.onSave instanceof Function)
      reducerOptions.onSave.call(null, reducer.name, state, store);
    if (storeOptions.onSave instanceof Function)
      storeOptions.onSave.call(null, reducer.name, state, store);
  }

  // get the current db connector an initialize the state of this
  // reducer by loading it from the db or by saving it
  // to the db (if it is not already persisted there)
  function reinitReducer(state) {
    if (changes) changes.cancel();

    let db = reducerOptions.db || storeOptions.db;
    if (!db) throw 'No db connector provided. ' +
      'You must at least provide one to the store or the reducer.';

    if (db instanceof Function)
      db = db(reducer.name, store);

    saveReducer = save(db);

    db.get(reducer.name).then(doc => {
      // set reducer state if there was an entry found in the db
      setReducer(doc);
    }).catch(err => {
      // save the reducer state if there was no entry in the db
      if (err.status === 404)
        return saveReducer(reducer.name, toPouch(state)).then(() => {
          onSave(state);
        });
      else
        throw err;
    }).then(() => {
      // from here on the reducer was loaded from db or saved to db
      initializedReducers[reducer.name] = true;
      onInit(currentState);

      let ready = true;
      for (let reducerName of Object.keys(initializedReducers)) {
        if (!initializedReducers[reducerName]) {
          ready = false;
          break;
        }
      }
      if (ready) onReady();

      // listen to changes in the db (e.g. when a replication occurs)
      // and update the reducer state when it happens
      return changes = db.changes({
        include_docs: true,
        live: true,
        since: 'now',
        doc_ids: [reducer.name]
      }).on('change', change => {
        if (!change.doc.state)
          saveReducer(change.doc._id, toPouch(currentState)).then(() => {
            onSave(currentState);
          });
        else if (!isEqual(change.doc.state, currentState))
          setReducer(change.doc);
      });
    });
  }

  // an action to update the current reducer state (used when
  // the state was fetched from the db)
  function setReducer(doc) {
    const { _id, _rev, state } = doc;
    const _state = fromPouch(state);

    store.dispatch({
      type: SET_REDUCER,
      reducer: _id,
      state: _state,<commit_msg>Fixes an endless loop when using Immutable js<commit_after>import equal from 'deep-equal';
import Immutable from 'immutable';
import save from './save.js';

const REINIT = '@@redux-pouchdb-plus/REINIT';
const INIT = '@@redux-pouchdb-plus/INIT';
const SET_REDUCER = '@@redux-pouchdb-plus/SET_REDUCER';

const initializedReducers = {};

export function reinit(reducerName) {
  const reducerNames = Object.keys(initializedReducers);

  if (!reducerName) { // reinit all reducers
    for (let n of reducerNames) {
      initializedReducers[n] = false;
    }
  }
  else { // reinit a specific reducer
    if (reducerNames.indexOf(reducerName) === -1)
      throw 'Invalid persistent reducer to reinit: ' + reducerName;

    initializedReducers[reducerName] = false;
  }

  return {type: REINIT, reducerName}
}

export const persistentStore = (storeOptions={}) => createStore => (reducer, initialState) => {
  const store = createStore(reducer, initialState);

  store.dispatch({
    type: INIT,
    store,
    storeOptions
  });

  return store;
}

export const persistentReducer = (reducer, reducerOptions={}) => {
  let initialState;
  let immutable;
  let store;
  let storeOptions;
  let changes;
  let saveReducer;
  let currentState;

  initializedReducers[reducer.name] = false;

  // call the provide (store only) callback as soon
  // as all persistent reducers are initialized
  function onReady() {
    if (storeOptions.onReady instanceof Function)
      storeOptions.onReady.call(null, store);
  }

  // call the provided callbacks as soon as this reducer
  // was initialized (loaded from or saved to the db)
  function onInit(state) {
    if (reducerOptions.onInit instanceof Function)
      reducerOptions.onInit.call(null, reducer.name, state, store);
    if (storeOptions.onInit instanceof Function)
      storeOptions.onInit.call(null, reducer.name, state, store);
  }

  // call the provided callbacks when this reducer
  // was updated with data from the db
  function onUpdate(state) {
    if (reducerOptions.onUpdate instanceof Function)
      reducerOptions.onUpdate.call(null, reducer.name, state, store);
    if (storeOptions.onUpdate instanceof Function)
      storeOptions.onUpdate.call(null, reducer.name, state, store);
  }

  // call the provided callbacks when the state
  // of this reducer was saved to the db
  function onSave(state) {
    if (reducerOptions.onSave instanceof Function)
      reducerOptions.onSave.call(null, reducer.name, state, store);
    if (storeOptions.onSave instanceof Function)
      storeOptions.onSave.call(null, reducer.name, state, store);
  }

  // get the current db connector an initialize the state of this
  // reducer by loading it from the db or by saving it
  // to the db (if it is not already persisted there)
  function reinitReducer(state) {
    if (changes) changes.cancel();

    let db = reducerOptions.db || storeOptions.db;
    if (!db) throw 'No db connector provided. ' +
      'You must at least provide one to the store or the reducer.';

    if (db instanceof Function)
      db = db(reducer.name, store);

    saveReducer = save(db);

    db.get(reducer.name).then(doc => {
      // set reducer state if there was an entry found in the db
      setReducer(doc);
    }).catch(err => {
      // save the reducer state if there was no entry in the db
      if (err.status === 404)
        return saveReducer(reducer.name, toPouch(state)).then(() => {
          onSave(state);
        });
      else
        throw err;
    }).then(() => {
      // from here on the reducer was loaded from db or saved to db
      initializedReducers[reducer.name] = true;
      onInit(currentState);

      let ready = true;
      for (let reducerName of Object.keys(initializedReducers)) {
        if (!initializedReducers[reducerName]) {
          ready = false;
          break;
        }
      }
      if (ready) onReady();

      // listen to changes in the db (e.g. when a replication occurs)
      // and update the reducer state when it happens
      return changes = db.changes({
        include_docs: true,
        live: true,
        since: 'now',
        doc_ids: [reducer.name]
      }).on('change', change => {
        if (!change.doc.state)
          saveReducer(change.doc._id, toPouch(currentState)).then(() => {
            onSave(currentState);
          });
        else if (!isEqual(fromPouch(change.doc.state), currentState))
          setReducer(change.doc);
      });
    });
  }

  // an action to update the current reducer state (used when
  // the state was fetched from the db)
  function setReducer(doc) {
    const { _id, _rev, state } = doc;
    const _state = fromPouch(state);

    store.dispatch({
      type: SET_REDUCER,
      reducer: _id,
      state: _state,
{'fraction_non_alphanumeric': 0.1082849604221636, 'fraction_numerical': 0.0008443271767810027, 'mean_word_length': 2.9092409240924093, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2300458', 'n_tokens_mistral': 2898, 'n_tokens_neox': 2837, 'n_words': 884}