[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    \"es2015\",\n    \"stage-0\"\n  ]\n}\n\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ncoverage\n"
  },
  {
    "path": ".istanbul.yml",
    "content": "instrumentation:\n    excludes: ['test', 'node_modules']\ncheck:\n    global:\n        lines: 100\n        branches: 100\n        statements: 100\n        functions: 100\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - 4\n  - 5\n"
  },
  {
    "path": "README.md",
    "content": "# pouch-redux-middleware\n\n[![By](https://img.shields.io/badge/made%20by-yld!-32bbee.svg?style=flat)](http://yld.io/contact?source=github-pouch-redux-middleware)\n[![Build Status](https://secure.travis-ci.org/pgte/pouch-redux-middleware.svg?branch=master)](http://travis-ci.org/pgte/pouch-redux-middleware?branch=master)\n\nRedux Middleware for syncing state and a PouchDB database.\n\nPropagates changes made to a state into PouchDB.\nPropagates changes made to PouchDB into the state.\n\n## Install\n\n```\n$ npm install pouch-redux-middleware --save\n```\n\n## Overview\n\npouch-redux-middleware will automatically populate a part of the store, specified by `path`, with the documents using the specified actions. This \"sub-state\" will be a list of the documents straight out of the database. When the database is modified by a 3rd party (e.g. by replication) a Redux action will be dispatched to update the \"sub-state\". Conversely, if you alter a document within the \"sub-state\", then the document will be updated in the database.\n\n* If a new document is created in the database (e.g. by replication or directly using `db.post`), then the corresponding  `insert` action will be dispatched. If a document is updated in the database (e.g. by replication or directly), then the corresponding `update` action will be dispatched. If a document is deleted in the database (e.g. by replication or directly), then the corresponding `remove` action will be dispatched.\n* If you add a document to the \"sub-state\", then the document will be added to the database automatically (you should specify keys such as `_id`). If you alter a document in the \"sub-state\", the document will be updated in the database automatically. If you remove a document from the \"sub-state\", the document will be removed from the database automatically.\n* You may specify that that only a subset of the database's documents should populate the store by using `changeFilter` which effectively filters the documents under consideration.\n\n## Example\n\nExample of configuring a store:\n\n```js\nimport * as types from '../constants/ActionTypes'\nimport PouchMiddleware from 'pouch-redux-middleware'\nimport { createStore, applyMiddleware } from 'redux'\nimport rootReducer from '../reducers'\nimport PouchDB from 'pouchdb'\n\nexport default function configureStore() {\n  const db = new PouchDB('todos');\n\n  const pouchMiddleware = PouchMiddleware({\n    path: '/todos',\n    db,\n    actions: {\n      remove: doc => { return { type: types.DELETE_TODO, id: doc._id } },\n      insert: doc => { return { type: types.INSERT_TODO, todo: doc } },\n      batchInsert: docs => { return { type: types.BATCH_INSERT_TODOS, todos: docs } }\n      update: doc => { return { type: types.UPDATE_TODO, todo: doc } },\n    }\n  })\n\n  const store = createStore(\n    rootReducer,\n    undefined,\n    applyMiddleware(pouchMiddleware)\n  )\n\n  return store\n}\n```\n\n## API\n\n### PouchMiddleware(paths)\n\n* `paths`: path or array containing path specs\n\nA path spec is an object describing the behaviour of a sub-tree of the state it has the following attributes:\n\n* `path`: a JsonPath path where the documents will stored in the state as an array\n* `db`: a PouchDB database\n* `actions`: an object describing the actions to perform when initially inserting items and when a change occurs in the db.\nIt's an object with keys containing a function that returns an action for each\nof the events (`remove`, `insert`, `batchInsert` and `update`)\n* `changeFilter`: a filtering function that receives a changed document, and if it returns\nfalse, the document will be ignored for the path. This is useful when you have\nmultiple paths in a single database that are differentiated through an attribute\n(like `type`).\n* `handleResponse` a function that is invoked with the direct response of the database,\nwhich is useful when metadata is needed or errors need custom handling.\nArguments are `error, data, callback`. `callback` must be invoked with a potential error\nafter custom handling is done.\n* `initialBatchDispatched` a function that is invoked once the initial set of\ndata has been read from pouchdb and dispatched to the redux store.\nThis comes handy if you want skip the initial updates to a store\nsubscriber by delaying the subscription to the redux store\nuntil the initial state is present. For example, when your application is first\nloaded you may wish to delay rendering until the store is updated.\n\nExample of a path spec:\n\n```js\n{\n  path: '/todos',\n  db,\n  actions: {\n    remove: doc => { return { type: types.DELETE_TODO, id: doc._id } },\n    insert: doc => { return { type: types.INSERT_TODO, todo: doc } },\n    batchInsert: docs => { return { type: types.BATCH_INSERT_TODOS, todos: docs } }\n    update: doc => { return { type: types.UPDATE_TODO, todo: doc } },\n  }\n}\n```\n\n## License\n\nISC\n"
  },
  {
    "path": "index.js",
    "content": "module.exports = require('./lib');\n"
  },
  {
    "path": "lib/index.js",
    "content": "'use strict';\n\nvar jPath = require('json-path');\nvar Queue = require('async-function-queue');\nvar extend = require('xtend');\nvar equal = require('deep-equal');\n\nmodule.exports = createPouchMiddleware;\n\nfunction createPouchMiddleware(_paths) {\n  var paths = _paths || [];\n  if (!Array.isArray(paths)) {\n    paths = [paths];\n  }\n\n  if (!paths.length) {\n    throw new Error('PouchMiddleware: no paths');\n  }\n\n  var defaultSpec = {\n    path: '.',\n    remove: scheduleRemove,\n    insert: scheduleInsert,\n    propagateDelete: propagateDelete,\n    propagateUpdate: propagateUpdate,\n    propagateInsert: propagateInsert,\n    propagateBatchInsert: propagateBatchInsert,\n    handleResponse: function handleResponse(err, data, cb) {\n      cb(err);\n    },\n    queue: Queue(1),\n    docs: {},\n    actions: {\n      remove: defaultAction('remove'),\n      update: defaultAction('update'),\n      insert: defaultAction('insert'),\n      batchInsert: defaultAction('batchInsert')\n    }\n  };\n\n  paths = paths.map(function (path) {\n    var spec = extend({}, defaultSpec, path);\n    spec.actions = extend({}, defaultSpec.actions, spec.actions);\n    spec.docs = {};\n\n    if (!spec.db) {\n      throw new Error('path ' + path.path + ' needs a db');\n    }\n    return spec;\n  });\n\n  function listen(path, dispatch, initialBatchDispatched) {\n    path.db.allDocs({ include_docs: true }).then(function (rawAllDocs) {\n      var allDocs = rawAllDocs.rows.map(function (doc) {\n        return doc.doc;\n      });\n      var filteredAllDocs = allDocs;\n      if (path.changeFilter) {\n        filteredAllDocs = allDocs.filter(path.changeFilter);\n      }\n      allDocs.forEach(function (doc) {\n        path.docs[doc._id] = doc;\n      });\n      path.propagateBatchInsert(filteredAllDocs, dispatch);\n      initialBatchDispatched();\n      var changes = path.db.changes({\n        live: true,\n        include_docs: true,\n        since: 'now'\n      });\n      changes.on('change', function (change) {\n        onDbChange(path, change, dispatch);\n      });\n    });\n  }\n\n  function processNewStateForPath(path, state) {\n    var docs = jPath.resolve(state, path.path);\n\n    /* istanbul ignore else */\n    if (docs && docs.length) {\n      docs.forEach(function (docs) {\n        var diffs = differences(path.docs, docs);\n        diffs.new.concat(diffs.updated).forEach(function (doc) {\n          return path.insert(doc);\n        });\n        diffs.deleted.forEach(function (doc) {\n          return path.remove(doc);\n        });\n      });\n    }\n  }\n\n  function write(data, responseHandler) {\n    return function (done) {\n      data.db[data.type](data.doc, function (err, resp) {\n        responseHandler(err, {\n          response: resp,\n          doc: data.doc,\n          type: data.type\n        }, function (err2) {\n          done(err2, resp);\n        });\n      });\n    };\n  }\n\n  function scheduleInsert(doc) {\n    this.docs[doc._id] = doc;\n    this.queue.push(write({\n      type: 'put',\n      doc: doc,\n      db: this.db\n    }, this.handleResponse));\n  }\n\n  function scheduleRemove(doc) {\n    delete this.docs[doc._id];\n    this.queue.push(write({\n      type: 'remove',\n      doc: doc,\n      db: this.db\n    }, this.handleResponse));\n  }\n\n  function propagateDelete(doc, dispatch) {\n    dispatch(this.actions.remove(doc));\n  }\n\n  function propagateInsert(doc, dispatch) {\n    dispatch(this.actions.insert(doc));\n  }\n\n  function propagateUpdate(doc, dispatch) {\n    dispatch(this.actions.update(doc));\n  }\n\n  function propagateBatchInsert(docs, dispatch) {\n    dispatch(this.actions.batchInsert(docs));\n  }\n\n  return function (options) {\n    paths.forEach(function (path) {\n      listen(path, options.dispatch, function (err) {\n        if (path.initialBatchDispatched) {\n          path.initialBatchDispatched(err);\n        }\n      });\n    });\n\n    return function (next) {\n      return function (action) {\n        var returnValue = next(action);\n        var newState = options.getState();\n\n        paths.forEach(function (path) {\n          return processNewStateForPath(path, newState);\n        });\n\n        return returnValue;\n      };\n    };\n  };\n}\n\nfunction differences(oldDocs, newDocs) {\n  var result = {\n    new: [],\n    updated: [],\n    deleted: Object.keys(oldDocs).map(function (oldDocId) {\n      return oldDocs[oldDocId];\n    })\n  };\n\n  var checkDoc = function checkDoc(newDoc) {\n    var id = newDoc._id;\n\n    /* istanbul ignore next */\n    if (!id) {\n      warn('doc with no id');\n    }\n    result.deleted = result.deleted.filter(function (doc) {\n      return doc._id !== id;\n    });\n    var oldDoc = oldDocs[id];\n    if (!oldDoc) {\n      result.new.push(newDoc);\n    } else if (!equal(oldDoc, newDoc)) {\n      result.updated.push(newDoc);\n    }\n  };\n\n  if (Array.isArray(newDocs)) {\n    newDocs.forEach(function (doc) {\n      checkDoc(doc);\n    });\n  } else {\n    var keys = Object.keys(newDocs);\n    for (var key in newDocs) {\n      checkDoc(newDocs[key]);\n    }\n  }\n\n  return result;\n}\n\nfunction onDbChange(path, change, dispatch) {\n  var changeDoc = change.doc;\n\n  if (path.changeFilter && !path.changeFilter(changeDoc)) {\n    return;\n  }\n\n  if (changeDoc._deleted) {\n    if (path.docs[changeDoc._id]) {\n      delete path.docs[changeDoc._id];\n      path.propagateDelete(changeDoc, dispatch);\n    }\n  } else {\n    var oldDoc = path.docs[changeDoc._id];\n    path.docs[changeDoc._id] = changeDoc;\n    if (oldDoc) {\n      path.propagateUpdate(changeDoc, dispatch);\n    } else {\n      path.propagateInsert(changeDoc, dispatch);\n    }\n  }\n}\n\n/* istanbul ignore next */\nfunction warn(what) {\n  var fn = console.warn || console.log;\n  if (fn) {\n    fn.call(console, what);\n  }\n}\n\n/* istanbul ignore next */\nfunction defaultAction(action) {\n  return function () {\n    throw new Error('no action provided for ' + action);\n  };\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"pouch-redux-middleware\",\n  \"version\": \"1.1.0\",\n  \"description\": \"PouchDB Redux Middleware\",\n  \"main\": \"lib/index.js\",\n  \"scripts\": {\n    \"test\": \"node --harmony node_modules/istanbul/lib/cli.js cover -- lab -vl && istanbul check-coverage\",\n    \"prepublish\": \"npm run build\",\n    \"build\": \"babel ./src -d lib\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/pgte/pouch-redux-middleware.git\"\n  },\n  \"keywords\": [\n    \"pouchdb\",\n    \"redux\",\n    \"react\",\n    \"middleware\"\n  ],\n  \"author\": \"pgte\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/pgte/pouch-redux-middleware/issues\"\n  },\n  \"homepage\": \"https://github.com/pgte/pouch-redux-middleware#readme\",\n  \"dependencies\": {\n    \"async-function-queue\": \"^1.0.0\",\n    \"deep-equal\": \"^1.0.1\",\n    \"json-path\": \"^0.1.3\",\n    \"xtend\": \"^4.0.1\"\n  },\n  \"devDependencies\": {\n    \"async\": \"^2.1.4\",\n    \"code\": \"^4.0.0\",\n    \"istanbul\": \"^0.4.5\",\n    \"lab\": \"^12.1.0\",\n    \"memdown\": \"^1.2.4\",\n    \"pouchdb\": \"^6.1.2\",\n    \"pre-commit\": \"^1.2.2\",\n    \"redux\": \"^3.6.0\",\n    \"babel-cli\": \"^6.22.2\",\n    \"babel-core\": \"^6.22.1\",\n    \"babel-preset-es2015\": \"^6.22.0\",\n    \"babel-preset-stage-0\": \"^6.22.0\"\n  },\n  \"pre-commit\": [\n    \"test\"\n  ]\n}\n"
  },
  {
    "path": "src/index.js",
    "content": "var jPath = require('json-path');\nvar Queue = require('async-function-queue');\nvar extend = require('xtend');\nvar equal = require('deep-equal');\n\nmodule.exports = createPouchMiddleware;\n\nfunction createPouchMiddleware(_paths) {\n  var paths = _paths || [];\n  if (!Array.isArray(paths)) {\n    paths = [paths];\n  }\n\n  if (!paths.length) {\n    throw new Error('PouchMiddleware: no paths');\n  }\n\n  var defaultSpec = {\n    path: '.',\n    remove: scheduleRemove,\n    insert: scheduleInsert,\n    propagateDelete,\n    propagateUpdate,\n    propagateInsert,\n    propagateBatchInsert,\n    handleResponse: function(err, data, cb) { cb(err); },\n    queue: Queue(1),\n    docs: {},\n    actions: {\n      remove: defaultAction('remove'),\n      update: defaultAction('update'),\n      insert: defaultAction('insert'),\n      batchInsert: defaultAction('batchInsert'),\n    }\n  }\n\n  paths = paths.map(function(path) {\n    var spec = extend({}, defaultSpec, path);\n    spec.actions = extend({}, defaultSpec.actions, spec.actions);\n    spec.docs = {};\n\n    if (! spec.db) {\n      throw new Error('path ' + path.path + ' needs a db');\n    }\n    return spec;\n  });\n\n  function listen(path, dispatch, initialBatchDispatched) {\n    path.db.allDocs({ include_docs: true }).then((rawAllDocs) => {\n      var allDocs = rawAllDocs.rows.map((doc) => doc.doc);\n      var filteredAllDocs = allDocs;\n      if (path.changeFilter) {\n        filteredAllDocs = allDocs.filter(path.changeFilter);\n      }\n      allDocs.forEach((doc) => {\n        path.docs[doc._id] = doc;\n      });\n      path.propagateBatchInsert(filteredAllDocs, dispatch);\n      initialBatchDispatched();\n      var changes = path.db.changes({\n        live: true,\n        include_docs: true,\n        since: 'now',\n      });\n      changes.on('change', change => {\n        onDbChange(path, change, dispatch);\n      });\n    });\n  }\n\n  function processNewStateForPath(path, state) {\n    var docs = jPath.resolve(state, path.path);\n\n    /* istanbul ignore else */\n    if (docs && docs.length) {\n      docs.forEach(function(docs) {\n        var diffs = differences(path.docs, docs);\n        diffs.new.concat(diffs.updated).forEach(doc => path.insert(doc))\n        diffs.deleted.forEach(doc => path.remove(doc));\n      });\n    }\n  }\n\n  function write(data, responseHandler) {\n    return function(done) {\n      data.db[data.type](data.doc, function(err, resp) {\n        responseHandler(\n          err,\n          {\n            response: resp,\n            doc: data.doc,\n            type: data.type\n          },\n          function(err2) {\n            done(err2, resp);\n          }\n        );\n      });\n    };\n  }\n\n  function scheduleInsert(doc) {\n    this.docs[doc._id] = doc;\n    this.queue.push(write(\n      {\n        type: 'put',\n        doc: doc,\n        db: this.db\n      },\n      this.handleResponse\n    ));\n  }\n\n  function scheduleRemove(doc) {\n    delete this.docs[doc._id];\n    this.queue.push(write(\n      {\n        type: 'remove',\n        doc: doc,\n        db: this.db\n      },\n      this.handleResponse\n    ));\n  }\n\n  function propagateDelete(doc, dispatch) {\n    dispatch(this.actions.remove(doc));\n  }\n\n  function propagateInsert(doc, dispatch) {\n    dispatch(this.actions.insert(doc));\n  }\n\n  function propagateUpdate(doc, dispatch) {\n    dispatch(this.actions.update(doc));\n  }\n\n  function propagateBatchInsert(docs, dispatch) {\n    dispatch(this.actions.batchInsert(docs));\n  }\n\n  return function(options) {\n    paths.forEach((path) => {\n      listen(path, options.dispatch, (err) => {\n        if (path.initialBatchDispatched) {\n          path.initialBatchDispatched(err);\n        }\n      });\n    });\n\n    return function(next) {\n      return function(action) {\n        var returnValue = next(action);\n        var newState = options.getState();\n\n        paths.forEach(path => processNewStateForPath(path, newState));\n\n        return returnValue;\n      }\n    }\n  }\n}\n\nfunction differences(oldDocs, newDocs) {\n  var result = {\n    new: [],\n    updated: [],\n    deleted: Object.keys(oldDocs).map(oldDocId => oldDocs[oldDocId]),\n  };\n\n  var checkDoc = function(newDoc) {\n    var id = newDoc._id;\n\n    /* istanbul ignore next */\n    if (! id) {\n      warn('doc with no id');\n    }\n    result.deleted = result.deleted.filter(doc => doc._id !== id);\n    var oldDoc = oldDocs[id];\n    if (! oldDoc) {\n      result.new.push(newDoc);\n    } else if (!equal(oldDoc, newDoc)) {\n      result.updated.push(newDoc);\n    }\n  };\n\n  if (Array.isArray(newDocs)){\n    newDocs.forEach(function (doc) {\n      checkDoc(doc)\n    });\n  } else{\n    var keys = Object.keys(newDocs);\n    for (var key in newDocs){\n      checkDoc(newDocs[key])\n    }\n  }\n\n\n  return result;\n}\n\nfunction onDbChange(path, change, dispatch) {\n  var changeDoc = change.doc;\n\n  if(path.changeFilter && (! path.changeFilter(changeDoc))) {\n    return;\n  }\n\n  if (changeDoc._deleted) {\n    if (path.docs[changeDoc._id]) {\n      delete path.docs[changeDoc._id];\n      path.propagateDelete(changeDoc, dispatch);\n    }\n  } else {\n    var oldDoc = path.docs[changeDoc._id];\n    path.docs[changeDoc._id] = changeDoc;\n    if (oldDoc) {\n      path.propagateUpdate(changeDoc, dispatch);\n    } else {\n      path.propagateInsert(changeDoc, dispatch);\n    }\n  }\n}\n\n/* istanbul ignore next */\nfunction warn(what) {\n  var fn = console.warn || console.log;\n  if (fn) {\n    fn.call(console, what);\n  }\n}\n\n/* istanbul ignore next */\nfunction defaultAction(action) {\n  return function() {\n    throw new Error('no action provided for ' + action);\n  };\n}\n"
  },
  {
    "path": "test/_action_types.js",
    "content": "[\n  'ERROR',\n  'ADD_TODO',\n  'INSERT_TODO',\n  'BATCH_INSERT_TODOS',\n  'DELETE_TODO',\n  'EDIT_TODO',\n  'UPDATE_TODO',\n  'COMPLETE_TODO',\n  'COMPLETE_ALL',\n  'CLEAR_COMPLETED'\n].forEach(function(type) {\n  exports[type]  = type;\n});\n"
  },
  {
    "path": "test/reducers/index.js",
    "content": "var redux = require('redux');\nvar todos = require('./todos');\nvar todosobject = require('./todosobject');\n\nmodule.exports = redux.combineReducers({\n  todos: todos,\n  todosobject: todosobject \n});\n"
  },
  {
    "path": "test/reducers/todos.js",
    "content": "var actionTypes = require('../_action_types');\n\nconst initialState = []\n\nmodule.exports = function todos(state, action) {\n  if (! state) {\n    state = [];\n  }\n\n  switch (action.type) {\n    case actionTypes.ADD_TODO:\n      return [\n        {\n          _id: action.id || id(),\n          completed: false,\n          text: action.text\n        },\n        ...state\n      ]\n\n    case actionTypes.INSERT_TODO:\n      return [\n        action.todo,\n        ...state\n      ]\n    case actionTypes.BATCH_INSERT_TODOS:\n      return [...state, ...action.todos]\n    case actionTypes.DELETE_TODO:\n      return state.filter(todo =>\n        todo._id !== action.id\n      )\n\n    case actionTypes.EDIT_TODO:\n      return state.map(todo =>\n        todo._id === action.id ?\n          Object.assign({}, todo, { text: action.text }) :\n          todo\n      )\n\n    case actionTypes.UPDATE_TODO:\n      return state.map(todo =>\n        todo._id === action.todo._id ?\n          action.todo :\n          todo\n      )\n\n    case actionTypes.COMPLETE_TODO:\n      return state.map(todo =>\n        todo._id === action.id ?\n          Object.assign({}, todo, { completed: !todo.completed }) :\n          todo\n      )\n\n    case actionTypes.COMPLETE_ALL:\n      const areAllMarked = state.every(todo => todo.completed)\n      return state.map(todo => Object.assign({}, todo, {\n        completed: !areAllMarked\n      }))\n\n    case actionTypes.CLEAR_COMPLETED:\n      return state.filter(todo => todo.completed === false)\n\n    default:\n      return state\n  }\n}\n\nfunction id() {\n  return Math.random().toString(36).substring(7);\n}\n"
  },
  {
    "path": "test/reducers/todosobject.js",
    "content": "var actionTypes = require('../_action_types');\n\nconst initialState = []\n\nmodule.exports = function todosobject(state, action) {\n  if (! state) {\n    state = {};\n  }\n\n  switch (action.type) {\n    case actionTypes.ADD_TODO:\n    {\n      var todo = {\n        _id: action.id || id(),\n        completed: false,\n        text: action.text\n      };\n      return Object.assign(state, {[todo._id]: todo});\n    }\n    case actionTypes.INSERT_TODO:\n      return Object.assign(state, { [action.todo._id]: action.todo  });\n    case actionTypes.DELETE_TODO:\n      var newState = Object.assign({}, state);\n      delete newState[action.id];\n      return newState;\n\n    case actionTypes.UPDATE_TODO:\n      return Object.assign(state, { [action.todo._id]: action.todo  });\n\n    default:\n      return state\n  }\n}\n\nfunction id() {\n  return Math.random().toString(36).substring(7);\n}\n"
  },
  {
    "path": "test/standalone.js",
    "content": "var Lab = require('lab');\nvar lab = exports.lab = Lab.script();\nvar describe = lab.experiment;\nvar before = lab.before;\nvar after = lab.after;\nvar it = lab.it;\nvar Code = require('code');\nvar expect = Code.expect;\n\nvar PouchMiddleware = require('../src/');\n\nvar PouchDB = require('pouchdb');\nvar db = new PouchDB('todos', {\n  db: require('memdown'),\n});\n\ndescribe('Pouch Redux Middleware', function() {\n  var pouchMiddleware;\n  var store;\n\n  it('cannot be created with no paths', function(done) {\n    expect(function() {\n      PouchMiddleware();\n    }).to.throw('PouchMiddleware: no paths');\n    done();\n  });\n\n  it('requires db in path', function(done) {\n    expect(function() {\n      PouchMiddleware([{}]);\n    }).to.throw('path undefined needs a db');\n    done();\n  });\n});\n"
  },
  {
    "path": "test/with-redux-object.js",
    "content": "var Lab = require('lab');\nvar lab = exports.lab = Lab.script();\nvar describe = lab.experiment;\nvar before = lab.before;\nvar after = lab.after;\nvar it = lab.it;\nvar Code = require('code');\nvar expect = Code.expect;\n\nvar actionTypes = require('./_action_types');\nvar rootReducer = require('./reducers');\n\nvar timers = require('timers');\nvar async = require('async');\nvar PouchDB = require('pouchdb');\nvar db = new PouchDB('todosobject', {\n  db: require('memdown'),\n});\n\nvar redux = require('redux');\n\nvar PouchMiddleware = require('../src/');\n\ndescribe('Pouch Redux Middleware with Objects', function() {\n  var pouchMiddleware;\n  var store;\n\n  it('todosmaps can be created', function(done) {\n    \n    pouchMiddleware = PouchMiddleware({\n      path: '/todosobject',\n      db: db,\n      actions: {\n        remove: (doc) => { return {type: actionTypes.DELETE_TODO, id: doc._id} },\n        insert: (doc) => { return {type: actionTypes.INSERT_TODO, todo: doc} },\n        batchInsert: (docs) => { return {type: actionTypes.BATCH_INSERT_TODOS, todos: docs} },\n        update: (doc) => { return {type: actionTypes.UPDATE_TODO, todo: doc} }\n      },\n      changeFilter: doc => !doc.filter\n    });\n    done();\n  });\n\n  it('can be used to create a store', function(done) {\n    var createStoreWithMiddleware = redux.applyMiddleware(pouchMiddleware)(redux.createStore);\n    store = createStoreWithMiddleware(rootReducer);\n    done();\n  });\n\n  it('accepts a few inserts', function(done) {\n    store.dispatch({type: actionTypes.ADD_TODO, text: 'do laundry', id: 'a'});\n    store.dispatch({type: actionTypes.ADD_TODO, text: 'wash dishes', id: 'b'});\n    timers.setTimeout(done, 100);\n  });\n\n  it('saves changes in pouchdb', function(done) {\n    async.map(['a', 'b'], db.get.bind(db), function(err, results) {\n      if (err) return done(err);\n      expect(results.length).to.equal(2);\n      expect(results[0].text).to.equal('do laundry');\n      expect(results[1].text).to.equal('wash dishes');\n      done();\n    });\n  });\n\n  it('accepts an removal', function(done) {\n    store.dispatch({type: actionTypes.DELETE_TODO, id: 'a'});\n    timers.setTimeout(done, 100);\n  });\n\n  it('saves changes in pouchdb', function(done) {\n    db.get('a', function(err) {\n      expect(err).to.be.an.object();\n      expect(err.message).to.equal('missing');\n      done();\n    });\n  });\n\n});\n"
  },
  {
    "path": "test/with-redux.js",
    "content": "var Lab = require('lab');\nvar lab = exports.lab = Lab.script();\nvar describe = lab.experiment;\nvar before = lab.before;\nvar after = lab.after;\nvar it = lab.it;\nvar Code = require('code');\nvar expect = Code.expect;\n\nvar actionTypes = require('./_action_types');\nvar rootReducer = require('./reducers');\n\nvar timers = require('timers');\nvar async = require('async');\nvar PouchDB = require('pouchdb');\nvar db = new PouchDB('todos', {\n  db: require('memdown'),\n});\n\nvar redux = require('redux');\n\nvar PouchMiddleware = require('../src/');\n\ndescribe('Pouch Redux Middleware', function() {\n  var pouchMiddleware;\n  var store;\n\n  it('can be created', function(done) {\n    \n    pouchMiddleware = PouchMiddleware({\n      path: '/todos',\n      db: db,\n      actions: {\n        remove: (doc) => { return {type: actionTypes.DELETE_TODO, id: doc._id} },\n        insert: (doc) => { return {type: actionTypes.INSERT_TODO, todo: doc} },\n        batchInsert: (docs) => { return {type: actionTypes.BATCH_INSERT_TODOS, todos: docs} },\n        update: (doc) => { return {type: actionTypes.UPDATE_TODO, todo: doc} }\n      },\n      changeFilter: doc => !doc.filter\n    });\n    done();\n  });\n\n  it('can be used to create a store', function(done) {\n    var createStoreWithMiddleware = redux.applyMiddleware(pouchMiddleware)(redux.createStore);\n    store = createStoreWithMiddleware(rootReducer);\n    done();\n  });\n\n  it('accepts a few inserts', function(done) {\n    store.dispatch({type: actionTypes.ADD_TODO, text: 'do laundry', id: 'a'});\n    store.dispatch({type: actionTypes.ADD_TODO, text: 'wash dishes', id: 'b'});\n    timers.setTimeout(done, 100);\n  });\n\n  it('saves changes in pouchdb', function(done) {\n    async.map(['a', 'b'], db.get.bind(db), function(err, results) {\n      if (err) return done(err);\n      expect(results.length).to.equal(2);\n      expect(results[0].text).to.equal('do laundry');\n      expect(results[1].text).to.equal('wash dishes');\n      done();\n    });\n  });\n\n  it('accepts an edit', function(done) {\n    store.dispatch({type: actionTypes.EDIT_TODO, text: 'wash all the dishes', id: 'b'});\n    timers.setTimeout(done, 100);\n  });\n\n  it('saves changes in pouchdb', function(done) {\n    async.map(['a', 'b'], db.get.bind(db), function(err, results) {\n      if (err) return done(err);\n      expect(results.length).to.equal(2);\n      expect(results[0].text).to.equal('do laundry');\n      expect(results[1].text).to.equal('wash all the dishes');\n      done();\n    });\n  });\n\n  it('accepts an removal', function(done) {\n    store.dispatch({type: actionTypes.DELETE_TODO, id: 'a'});\n    timers.setTimeout(done, 100);\n  });\n\n  it('saves changes in pouchdb', function(done) {\n    db.get('a', function(err) {\n      expect(err).to.be.an.object();\n      expect(err.message).to.equal('missing');\n      done();\n    });\n  });\n\n  it('making changes in pouchdb...', function(done) {\n    db.get('b', function(err, doc) {\n      expect(err).to.equal(null);\n      doc.text = 'wash some of the dishes';\n      db.put(doc, done);\n    });\n  });\n\n  it('waiting a bit', function(done) {\n    timers.setTimeout(done, 100);\n  });\n\n  it('...propagates update from pouchdb', function(done) {\n    expect(store.getState().todos.filter(function(doc) {\n      return doc._id == 'b';\n    })[0].text).to.equal('wash some of the dishes');\n    done();\n  });\n\n  it('making removal in pouchdb...', function(done) {\n    db.get('b', function(err, doc) {\n      expect(err).to.equal(null);\n      db.remove(doc, done);\n    });\n  });\n\n  it('waiting a bit', function(done) {\n    timers.setTimeout(done, 100);\n  });\n\n  it('...propagates update from pouchdb', function(done) {\n    expect(store.getState().todos.filter(function(doc) {\n      return doc._id == 'b';\n    }).length).to.equal(0);\n    done();\n  });\n\n  it('making insert in pouchdb...', function(done) {\n    db.post({\n      _id: 'c',\n      text: 'pay bills',\n    }, done);\n  });\n\n  it('waiting a bit', function(done) {\n    timers.setTimeout(done, 100);\n  });\n\n  it('...propagates update from pouchdb', function(done) {\n    expect(store.getState().todos.filter(function(doc) {\n      return doc._id == 'c';\n    })[0].text).to.equal('pay bills');\n    done();\n  });\n\n  it('...inserts filtered document', function(done) {\n    db.post({\n      _id: 'd',\n      filter: true,\n    }).then(() => done()).catch(done);\n  });\n\n  it('waiting a bit', function(done) {\n    timers.setTimeout(done, 100);\n  });\n\n  it('...filters documents', function(done) {\n    expect(store.getState().todos.filter(function(doc) {\n      return doc._id == 'd';\n    }).length).to.equal(0);\n    done();\n  });\n\n  it('calles initialBatchDispatched', (done) => {\n    const anotherMiddleware = PouchMiddleware({\n      path: '/todos',\n      db: db,\n      actions: {\n        remove: (doc) => { return {type: actionTypes.DELETE_TODO, id: doc._id} },\n        insert: (doc) => { return {type: actionTypes.INSERT_TODO, todo: doc} },\n        batchInsert: (docs) => { return {type: actionTypes.BATCH_INSERT_TODOS, todos: docs} },\n        update: (doc) => { return {type: actionTypes.UPDATE_TODO, todo: doc} }\n      },\n      initialBatchDispatched(err) {\n        if (err) {\n          return done(err);\n        }\n\n        var called = false;\n        store.subscribe(() => {\n          if (called) {\n            done(new Error('expect subscribe to only be called once'));\n          }\n          called = true;\n          expect(store.getState().todos.length).to.equal(1);\n          timers.setTimeout(done, 100);\n        });\n\n        expect(store.getState().todos.length).to.equal(2);\n        store.dispatch({type: actionTypes.DELETE_TODO, id: 'c'});\n      }\n    });\n    const store = redux.applyMiddleware(anotherMiddleware)(redux.createStore)(rootReducer);\n    expect(store.getState().todos.length).to.equal(0);\n  });\n});\n"
  }
]