Full Code of pgte/pouch-redux-middleware for AI

master d22968c7c77f cached
16 files
28.7 KB
7.8k tokens
13 symbols
1 requests
Download .txt
Repository: pgte/pouch-redux-middleware
Branch: master
Commit: d22968c7c77f
Files: 16
Total size: 28.7 KB

Directory structure:
gitextract_9uhaz__j/

├── .babelrc
├── .gitignore
├── .istanbul.yml
├── .travis.yml
├── README.md
├── index.js
├── lib/
│   └── index.js
├── package.json
├── src/
│   └── index.js
└── test/
    ├── _action_types.js
    ├── reducers/
    │   ├── index.js
    │   ├── todos.js
    │   └── todosobject.js
    ├── standalone.js
    ├── with-redux-object.js
    └── with-redux.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": [
    "es2015",
    "stage-0"
  ]
}



================================================
FILE: .gitignore
================================================
node_modules
coverage


================================================
FILE: .istanbul.yml
================================================
instrumentation:
    excludes: ['test', 'node_modules']
check:
    global:
        lines: 100
        branches: 100
        statements: 100
        functions: 100


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
  - 4
  - 5


================================================
FILE: README.md
================================================
# pouch-redux-middleware

[![By](https://img.shields.io/badge/made%20by-yld!-32bbee.svg?style=flat)](http://yld.io/contact?source=github-pouch-redux-middleware)
[![Build Status](https://secure.travis-ci.org/pgte/pouch-redux-middleware.svg?branch=master)](http://travis-ci.org/pgte/pouch-redux-middleware?branch=master)

Redux Middleware for syncing state and a PouchDB database.

Propagates changes made to a state into PouchDB.
Propagates changes made to PouchDB into the state.

## Install

```
$ npm install pouch-redux-middleware --save
```

## Overview

pouch-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.

* 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.
* 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.
* 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.

## Example

Example of configuring a store:

```js
import * as types from '../constants/ActionTypes'
import PouchMiddleware from 'pouch-redux-middleware'
import { createStore, applyMiddleware } from 'redux'
import rootReducer from '../reducers'
import PouchDB from 'pouchdb'

export default function configureStore() {
  const db = new PouchDB('todos');

  const pouchMiddleware = PouchMiddleware({
    path: '/todos',
    db,
    actions: {
      remove: doc => { return { type: types.DELETE_TODO, id: doc._id } },
      insert: doc => { return { type: types.INSERT_TODO, todo: doc } },
      batchInsert: docs => { return { type: types.BATCH_INSERT_TODOS, todos: docs } }
      update: doc => { return { type: types.UPDATE_TODO, todo: doc } },
    }
  })

  const store = createStore(
    rootReducer,
    undefined,
    applyMiddleware(pouchMiddleware)
  )

  return store
}
```

## API

### PouchMiddleware(paths)

* `paths`: path or array containing path specs

A path spec is an object describing the behaviour of a sub-tree of the state it has the following attributes:

* `path`: a JsonPath path where the documents will stored in the state as an array
* `db`: a PouchDB database
* `actions`: an object describing the actions to perform when initially inserting items and when a change occurs in the db.
It's an object with keys containing a function that returns an action for each
of the events (`remove`, `insert`, `batchInsert` and `update`)
* `changeFilter`: a filtering function that receives a changed document, and if it returns
false, the document will be ignored for the path. This is useful when you have
multiple paths in a single database that are differentiated through an attribute
(like `type`).
* `handleResponse` a function that is invoked with the direct response of the database,
which is useful when metadata is needed or errors need custom handling.
Arguments are `error, data, callback`. `callback` must be invoked with a potential error
after custom handling is done.
* `initialBatchDispatched` a function that is invoked once the initial set of
data has been read from pouchdb and dispatched to the redux store.
This comes handy if you want skip the initial updates to a store
subscriber by delaying the subscription to the redux store
until the initial state is present. For example, when your application is first
loaded you may wish to delay rendering until the store is updated.

Example of a path spec:

```js
{
  path: '/todos',
  db,
  actions: {
    remove: doc => { return { type: types.DELETE_TODO, id: doc._id } },
    insert: doc => { return { type: types.INSERT_TODO, todo: doc } },
    batchInsert: docs => { return { type: types.BATCH_INSERT_TODOS, todos: docs } }
    update: doc => { return { type: types.UPDATE_TODO, todo: doc } },
  }
}
```

## License

ISC


================================================
FILE: index.js
================================================
module.exports = require('./lib');


================================================
FILE: lib/index.js
================================================
'use strict';

var jPath = require('json-path');
var Queue = require('async-function-queue');
var extend = require('xtend');
var equal = require('deep-equal');

module.exports = createPouchMiddleware;

function createPouchMiddleware(_paths) {
  var paths = _paths || [];
  if (!Array.isArray(paths)) {
    paths = [paths];
  }

  if (!paths.length) {
    throw new Error('PouchMiddleware: no paths');
  }

  var defaultSpec = {
    path: '.',
    remove: scheduleRemove,
    insert: scheduleInsert,
    propagateDelete: propagateDelete,
    propagateUpdate: propagateUpdate,
    propagateInsert: propagateInsert,
    propagateBatchInsert: propagateBatchInsert,
    handleResponse: function handleResponse(err, data, cb) {
      cb(err);
    },
    queue: Queue(1),
    docs: {},
    actions: {
      remove: defaultAction('remove'),
      update: defaultAction('update'),
      insert: defaultAction('insert'),
      batchInsert: defaultAction('batchInsert')
    }
  };

  paths = paths.map(function (path) {
    var spec = extend({}, defaultSpec, path);
    spec.actions = extend({}, defaultSpec.actions, spec.actions);
    spec.docs = {};

    if (!spec.db) {
      throw new Error('path ' + path.path + ' needs a db');
    }
    return spec;
  });

  function listen(path, dispatch, initialBatchDispatched) {
    path.db.allDocs({ include_docs: true }).then(function (rawAllDocs) {
      var allDocs = rawAllDocs.rows.map(function (doc) {
        return doc.doc;
      });
      var filteredAllDocs = allDocs;
      if (path.changeFilter) {
        filteredAllDocs = allDocs.filter(path.changeFilter);
      }
      allDocs.forEach(function (doc) {
        path.docs[doc._id] = doc;
      });
      path.propagateBatchInsert(filteredAllDocs, dispatch);
      initialBatchDispatched();
      var changes = path.db.changes({
        live: true,
        include_docs: true,
        since: 'now'
      });
      changes.on('change', function (change) {
        onDbChange(path, change, dispatch);
      });
    });
  }

  function processNewStateForPath(path, state) {
    var docs = jPath.resolve(state, path.path);

    /* istanbul ignore else */
    if (docs && docs.length) {
      docs.forEach(function (docs) {
        var diffs = differences(path.docs, docs);
        diffs.new.concat(diffs.updated).forEach(function (doc) {
          return path.insert(doc);
        });
        diffs.deleted.forEach(function (doc) {
          return path.remove(doc);
        });
      });
    }
  }

  function write(data, responseHandler) {
    return function (done) {
      data.db[data.type](data.doc, function (err, resp) {
        responseHandler(err, {
          response: resp,
          doc: data.doc,
          type: data.type
        }, function (err2) {
          done(err2, resp);
        });
      });
    };
  }

  function scheduleInsert(doc) {
    this.docs[doc._id] = doc;
    this.queue.push(write({
      type: 'put',
      doc: doc,
      db: this.db
    }, this.handleResponse));
  }

  function scheduleRemove(doc) {
    delete this.docs[doc._id];
    this.queue.push(write({
      type: 'remove',
      doc: doc,
      db: this.db
    }, this.handleResponse));
  }

  function propagateDelete(doc, dispatch) {
    dispatch(this.actions.remove(doc));
  }

  function propagateInsert(doc, dispatch) {
    dispatch(this.actions.insert(doc));
  }

  function propagateUpdate(doc, dispatch) {
    dispatch(this.actions.update(doc));
  }

  function propagateBatchInsert(docs, dispatch) {
    dispatch(this.actions.batchInsert(docs));
  }

  return function (options) {
    paths.forEach(function (path) {
      listen(path, options.dispatch, function (err) {
        if (path.initialBatchDispatched) {
          path.initialBatchDispatched(err);
        }
      });
    });

    return function (next) {
      return function (action) {
        var returnValue = next(action);
        var newState = options.getState();

        paths.forEach(function (path) {
          return processNewStateForPath(path, newState);
        });

        return returnValue;
      };
    };
  };
}

function differences(oldDocs, newDocs) {
  var result = {
    new: [],
    updated: [],
    deleted: Object.keys(oldDocs).map(function (oldDocId) {
      return oldDocs[oldDocId];
    })
  };

  var checkDoc = function checkDoc(newDoc) {
    var id = newDoc._id;

    /* istanbul ignore next */
    if (!id) {
      warn('doc with no id');
    }
    result.deleted = result.deleted.filter(function (doc) {
      return doc._id !== id;
    });
    var oldDoc = oldDocs[id];
    if (!oldDoc) {
      result.new.push(newDoc);
    } else if (!equal(oldDoc, newDoc)) {
      result.updated.push(newDoc);
    }
  };

  if (Array.isArray(newDocs)) {
    newDocs.forEach(function (doc) {
      checkDoc(doc);
    });
  } else {
    var keys = Object.keys(newDocs);
    for (var key in newDocs) {
      checkDoc(newDocs[key]);
    }
  }

  return result;
}

function onDbChange(path, change, dispatch) {
  var changeDoc = change.doc;

  if (path.changeFilter && !path.changeFilter(changeDoc)) {
    return;
  }

  if (changeDoc._deleted) {
    if (path.docs[changeDoc._id]) {
      delete path.docs[changeDoc._id];
      path.propagateDelete(changeDoc, dispatch);
    }
  } else {
    var oldDoc = path.docs[changeDoc._id];
    path.docs[changeDoc._id] = changeDoc;
    if (oldDoc) {
      path.propagateUpdate(changeDoc, dispatch);
    } else {
      path.propagateInsert(changeDoc, dispatch);
    }
  }
}

/* istanbul ignore next */
function warn(what) {
  var fn = console.warn || console.log;
  if (fn) {
    fn.call(console, what);
  }
}

/* istanbul ignore next */
function defaultAction(action) {
  return function () {
    throw new Error('no action provided for ' + action);
  };
}

================================================
FILE: package.json
================================================
{
  "name": "pouch-redux-middleware",
  "version": "1.1.0",
  "description": "PouchDB Redux Middleware",
  "main": "lib/index.js",
  "scripts": {
    "test": "node --harmony node_modules/istanbul/lib/cli.js cover -- lab -vl && istanbul check-coverage",
    "prepublish": "npm run build",
    "build": "babel ./src -d lib"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/pgte/pouch-redux-middleware.git"
  },
  "keywords": [
    "pouchdb",
    "redux",
    "react",
    "middleware"
  ],
  "author": "pgte",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/pgte/pouch-redux-middleware/issues"
  },
  "homepage": "https://github.com/pgte/pouch-redux-middleware#readme",
  "dependencies": {
    "async-function-queue": "^1.0.0",
    "deep-equal": "^1.0.1",
    "json-path": "^0.1.3",
    "xtend": "^4.0.1"
  },
  "devDependencies": {
    "async": "^2.1.4",
    "code": "^4.0.0",
    "istanbul": "^0.4.5",
    "lab": "^12.1.0",
    "memdown": "^1.2.4",
    "pouchdb": "^6.1.2",
    "pre-commit": "^1.2.2",
    "redux": "^3.6.0",
    "babel-cli": "^6.22.2",
    "babel-core": "^6.22.1",
    "babel-preset-es2015": "^6.22.0",
    "babel-preset-stage-0": "^6.22.0"
  },
  "pre-commit": [
    "test"
  ]
}


================================================
FILE: src/index.js
================================================
var jPath = require('json-path');
var Queue = require('async-function-queue');
var extend = require('xtend');
var equal = require('deep-equal');

module.exports = createPouchMiddleware;

function createPouchMiddleware(_paths) {
  var paths = _paths || [];
  if (!Array.isArray(paths)) {
    paths = [paths];
  }

  if (!paths.length) {
    throw new Error('PouchMiddleware: no paths');
  }

  var defaultSpec = {
    path: '.',
    remove: scheduleRemove,
    insert: scheduleInsert,
    propagateDelete,
    propagateUpdate,
    propagateInsert,
    propagateBatchInsert,
    handleResponse: function(err, data, cb) { cb(err); },
    queue: Queue(1),
    docs: {},
    actions: {
      remove: defaultAction('remove'),
      update: defaultAction('update'),
      insert: defaultAction('insert'),
      batchInsert: defaultAction('batchInsert'),
    }
  }

  paths = paths.map(function(path) {
    var spec = extend({}, defaultSpec, path);
    spec.actions = extend({}, defaultSpec.actions, spec.actions);
    spec.docs = {};

    if (! spec.db) {
      throw new Error('path ' + path.path + ' needs a db');
    }
    return spec;
  });

  function listen(path, dispatch, initialBatchDispatched) {
    path.db.allDocs({ include_docs: true }).then((rawAllDocs) => {
      var allDocs = rawAllDocs.rows.map((doc) => doc.doc);
      var filteredAllDocs = allDocs;
      if (path.changeFilter) {
        filteredAllDocs = allDocs.filter(path.changeFilter);
      }
      allDocs.forEach((doc) => {
        path.docs[doc._id] = doc;
      });
      path.propagateBatchInsert(filteredAllDocs, dispatch);
      initialBatchDispatched();
      var changes = path.db.changes({
        live: true,
        include_docs: true,
        since: 'now',
      });
      changes.on('change', change => {
        onDbChange(path, change, dispatch);
      });
    });
  }

  function processNewStateForPath(path, state) {
    var docs = jPath.resolve(state, path.path);

    /* istanbul ignore else */
    if (docs && docs.length) {
      docs.forEach(function(docs) {
        var diffs = differences(path.docs, docs);
        diffs.new.concat(diffs.updated).forEach(doc => path.insert(doc))
        diffs.deleted.forEach(doc => path.remove(doc));
      });
    }
  }

  function write(data, responseHandler) {
    return function(done) {
      data.db[data.type](data.doc, function(err, resp) {
        responseHandler(
          err,
          {
            response: resp,
            doc: data.doc,
            type: data.type
          },
          function(err2) {
            done(err2, resp);
          }
        );
      });
    };
  }

  function scheduleInsert(doc) {
    this.docs[doc._id] = doc;
    this.queue.push(write(
      {
        type: 'put',
        doc: doc,
        db: this.db
      },
      this.handleResponse
    ));
  }

  function scheduleRemove(doc) {
    delete this.docs[doc._id];
    this.queue.push(write(
      {
        type: 'remove',
        doc: doc,
        db: this.db
      },
      this.handleResponse
    ));
  }

  function propagateDelete(doc, dispatch) {
    dispatch(this.actions.remove(doc));
  }

  function propagateInsert(doc, dispatch) {
    dispatch(this.actions.insert(doc));
  }

  function propagateUpdate(doc, dispatch) {
    dispatch(this.actions.update(doc));
  }

  function propagateBatchInsert(docs, dispatch) {
    dispatch(this.actions.batchInsert(docs));
  }

  return function(options) {
    paths.forEach((path) => {
      listen(path, options.dispatch, (err) => {
        if (path.initialBatchDispatched) {
          path.initialBatchDispatched(err);
        }
      });
    });

    return function(next) {
      return function(action) {
        var returnValue = next(action);
        var newState = options.getState();

        paths.forEach(path => processNewStateForPath(path, newState));

        return returnValue;
      }
    }
  }
}

function differences(oldDocs, newDocs) {
  var result = {
    new: [],
    updated: [],
    deleted: Object.keys(oldDocs).map(oldDocId => oldDocs[oldDocId]),
  };

  var checkDoc = function(newDoc) {
    var id = newDoc._id;

    /* istanbul ignore next */
    if (! id) {
      warn('doc with no id');
    }
    result.deleted = result.deleted.filter(doc => doc._id !== id);
    var oldDoc = oldDocs[id];
    if (! oldDoc) {
      result.new.push(newDoc);
    } else if (!equal(oldDoc, newDoc)) {
      result.updated.push(newDoc);
    }
  };

  if (Array.isArray(newDocs)){
    newDocs.forEach(function (doc) {
      checkDoc(doc)
    });
  } else{
    var keys = Object.keys(newDocs);
    for (var key in newDocs){
      checkDoc(newDocs[key])
    }
  }


  return result;
}

function onDbChange(path, change, dispatch) {
  var changeDoc = change.doc;

  if(path.changeFilter && (! path.changeFilter(changeDoc))) {
    return;
  }

  if (changeDoc._deleted) {
    if (path.docs[changeDoc._id]) {
      delete path.docs[changeDoc._id];
      path.propagateDelete(changeDoc, dispatch);
    }
  } else {
    var oldDoc = path.docs[changeDoc._id];
    path.docs[changeDoc._id] = changeDoc;
    if (oldDoc) {
      path.propagateUpdate(changeDoc, dispatch);
    } else {
      path.propagateInsert(changeDoc, dispatch);
    }
  }
}

/* istanbul ignore next */
function warn(what) {
  var fn = console.warn || console.log;
  if (fn) {
    fn.call(console, what);
  }
}

/* istanbul ignore next */
function defaultAction(action) {
  return function() {
    throw new Error('no action provided for ' + action);
  };
}


================================================
FILE: test/_action_types.js
================================================
[
  'ERROR',
  'ADD_TODO',
  'INSERT_TODO',
  'BATCH_INSERT_TODOS',
  'DELETE_TODO',
  'EDIT_TODO',
  'UPDATE_TODO',
  'COMPLETE_TODO',
  'COMPLETE_ALL',
  'CLEAR_COMPLETED'
].forEach(function(type) {
  exports[type]  = type;
});


================================================
FILE: test/reducers/index.js
================================================
var redux = require('redux');
var todos = require('./todos');
var todosobject = require('./todosobject');

module.exports = redux.combineReducers({
  todos: todos,
  todosobject: todosobject 
});


================================================
FILE: test/reducers/todos.js
================================================
var actionTypes = require('../_action_types');

const initialState = []

module.exports = function todos(state, action) {
  if (! state) {
    state = [];
  }

  switch (action.type) {
    case actionTypes.ADD_TODO:
      return [
        {
          _id: action.id || id(),
          completed: false,
          text: action.text
        },
        ...state
      ]

    case actionTypes.INSERT_TODO:
      return [
        action.todo,
        ...state
      ]
    case actionTypes.BATCH_INSERT_TODOS:
      return [...state, ...action.todos]
    case actionTypes.DELETE_TODO:
      return state.filter(todo =>
        todo._id !== action.id
      )

    case actionTypes.EDIT_TODO:
      return state.map(todo =>
        todo._id === action.id ?
          Object.assign({}, todo, { text: action.text }) :
          todo
      )

    case actionTypes.UPDATE_TODO:
      return state.map(todo =>
        todo._id === action.todo._id ?
          action.todo :
          todo
      )

    case actionTypes.COMPLETE_TODO:
      return state.map(todo =>
        todo._id === action.id ?
          Object.assign({}, todo, { completed: !todo.completed }) :
          todo
      )

    case actionTypes.COMPLETE_ALL:
      const areAllMarked = state.every(todo => todo.completed)
      return state.map(todo => Object.assign({}, todo, {
        completed: !areAllMarked
      }))

    case actionTypes.CLEAR_COMPLETED:
      return state.filter(todo => todo.completed === false)

    default:
      return state
  }
}

function id() {
  return Math.random().toString(36).substring(7);
}


================================================
FILE: test/reducers/todosobject.js
================================================
var actionTypes = require('../_action_types');

const initialState = []

module.exports = function todosobject(state, action) {
  if (! state) {
    state = {};
  }

  switch (action.type) {
    case actionTypes.ADD_TODO:
    {
      var todo = {
        _id: action.id || id(),
        completed: false,
        text: action.text
      };
      return Object.assign(state, {[todo._id]: todo});
    }
    case actionTypes.INSERT_TODO:
      return Object.assign(state, { [action.todo._id]: action.todo  });
    case actionTypes.DELETE_TODO:
      var newState = Object.assign({}, state);
      delete newState[action.id];
      return newState;

    case actionTypes.UPDATE_TODO:
      return Object.assign(state, { [action.todo._id]: action.todo  });

    default:
      return state
  }
}

function id() {
  return Math.random().toString(36).substring(7);
}


================================================
FILE: test/standalone.js
================================================
var Lab = require('lab');
var lab = exports.lab = Lab.script();
var describe = lab.experiment;
var before = lab.before;
var after = lab.after;
var it = lab.it;
var Code = require('code');
var expect = Code.expect;

var PouchMiddleware = require('../src/');

var PouchDB = require('pouchdb');
var db = new PouchDB('todos', {
  db: require('memdown'),
});

describe('Pouch Redux Middleware', function() {
  var pouchMiddleware;
  var store;

  it('cannot be created with no paths', function(done) {
    expect(function() {
      PouchMiddleware();
    }).to.throw('PouchMiddleware: no paths');
    done();
  });

  it('requires db in path', function(done) {
    expect(function() {
      PouchMiddleware([{}]);
    }).to.throw('path undefined needs a db');
    done();
  });
});


================================================
FILE: test/with-redux-object.js
================================================
var Lab = require('lab');
var lab = exports.lab = Lab.script();
var describe = lab.experiment;
var before = lab.before;
var after = lab.after;
var it = lab.it;
var Code = require('code');
var expect = Code.expect;

var actionTypes = require('./_action_types');
var rootReducer = require('./reducers');

var timers = require('timers');
var async = require('async');
var PouchDB = require('pouchdb');
var db = new PouchDB('todosobject', {
  db: require('memdown'),
});

var redux = require('redux');

var PouchMiddleware = require('../src/');

describe('Pouch Redux Middleware with Objects', function() {
  var pouchMiddleware;
  var store;

  it('todosmaps can be created', function(done) {
    
    pouchMiddleware = PouchMiddleware({
      path: '/todosobject',
      db: db,
      actions: {
        remove: (doc) => { return {type: actionTypes.DELETE_TODO, id: doc._id} },
        insert: (doc) => { return {type: actionTypes.INSERT_TODO, todo: doc} },
        batchInsert: (docs) => { return {type: actionTypes.BATCH_INSERT_TODOS, todos: docs} },
        update: (doc) => { return {type: actionTypes.UPDATE_TODO, todo: doc} }
      },
      changeFilter: doc => !doc.filter
    });
    done();
  });

  it('can be used to create a store', function(done) {
    var createStoreWithMiddleware = redux.applyMiddleware(pouchMiddleware)(redux.createStore);
    store = createStoreWithMiddleware(rootReducer);
    done();
  });

  it('accepts a few inserts', function(done) {
    store.dispatch({type: actionTypes.ADD_TODO, text: 'do laundry', id: 'a'});
    store.dispatch({type: actionTypes.ADD_TODO, text: 'wash dishes', id: 'b'});
    timers.setTimeout(done, 100);
  });

  it('saves changes in pouchdb', function(done) {
    async.map(['a', 'b'], db.get.bind(db), function(err, results) {
      if (err) return done(err);
      expect(results.length).to.equal(2);
      expect(results[0].text).to.equal('do laundry');
      expect(results[1].text).to.equal('wash dishes');
      done();
    });
  });

  it('accepts an removal', function(done) {
    store.dispatch({type: actionTypes.DELETE_TODO, id: 'a'});
    timers.setTimeout(done, 100);
  });

  it('saves changes in pouchdb', function(done) {
    db.get('a', function(err) {
      expect(err).to.be.an.object();
      expect(err.message).to.equal('missing');
      done();
    });
  });

});


================================================
FILE: test/with-redux.js
================================================
var Lab = require('lab');
var lab = exports.lab = Lab.script();
var describe = lab.experiment;
var before = lab.before;
var after = lab.after;
var it = lab.it;
var Code = require('code');
var expect = Code.expect;

var actionTypes = require('./_action_types');
var rootReducer = require('./reducers');

var timers = require('timers');
var async = require('async');
var PouchDB = require('pouchdb');
var db = new PouchDB('todos', {
  db: require('memdown'),
});

var redux = require('redux');

var PouchMiddleware = require('../src/');

describe('Pouch Redux Middleware', function() {
  var pouchMiddleware;
  var store;

  it('can be created', function(done) {
    
    pouchMiddleware = PouchMiddleware({
      path: '/todos',
      db: db,
      actions: {
        remove: (doc) => { return {type: actionTypes.DELETE_TODO, id: doc._id} },
        insert: (doc) => { return {type: actionTypes.INSERT_TODO, todo: doc} },
        batchInsert: (docs) => { return {type: actionTypes.BATCH_INSERT_TODOS, todos: docs} },
        update: (doc) => { return {type: actionTypes.UPDATE_TODO, todo: doc} }
      },
      changeFilter: doc => !doc.filter
    });
    done();
  });

  it('can be used to create a store', function(done) {
    var createStoreWithMiddleware = redux.applyMiddleware(pouchMiddleware)(redux.createStore);
    store = createStoreWithMiddleware(rootReducer);
    done();
  });

  it('accepts a few inserts', function(done) {
    store.dispatch({type: actionTypes.ADD_TODO, text: 'do laundry', id: 'a'});
    store.dispatch({type: actionTypes.ADD_TODO, text: 'wash dishes', id: 'b'});
    timers.setTimeout(done, 100);
  });

  it('saves changes in pouchdb', function(done) {
    async.map(['a', 'b'], db.get.bind(db), function(err, results) {
      if (err) return done(err);
      expect(results.length).to.equal(2);
      expect(results[0].text).to.equal('do laundry');
      expect(results[1].text).to.equal('wash dishes');
      done();
    });
  });

  it('accepts an edit', function(done) {
    store.dispatch({type: actionTypes.EDIT_TODO, text: 'wash all the dishes', id: 'b'});
    timers.setTimeout(done, 100);
  });

  it('saves changes in pouchdb', function(done) {
    async.map(['a', 'b'], db.get.bind(db), function(err, results) {
      if (err) return done(err);
      expect(results.length).to.equal(2);
      expect(results[0].text).to.equal('do laundry');
      expect(results[1].text).to.equal('wash all the dishes');
      done();
    });
  });

  it('accepts an removal', function(done) {
    store.dispatch({type: actionTypes.DELETE_TODO, id: 'a'});
    timers.setTimeout(done, 100);
  });

  it('saves changes in pouchdb', function(done) {
    db.get('a', function(err) {
      expect(err).to.be.an.object();
      expect(err.message).to.equal('missing');
      done();
    });
  });

  it('making changes in pouchdb...', function(done) {
    db.get('b', function(err, doc) {
      expect(err).to.equal(null);
      doc.text = 'wash some of the dishes';
      db.put(doc, done);
    });
  });

  it('waiting a bit', function(done) {
    timers.setTimeout(done, 100);
  });

  it('...propagates update from pouchdb', function(done) {
    expect(store.getState().todos.filter(function(doc) {
      return doc._id == 'b';
    })[0].text).to.equal('wash some of the dishes');
    done();
  });

  it('making removal in pouchdb...', function(done) {
    db.get('b', function(err, doc) {
      expect(err).to.equal(null);
      db.remove(doc, done);
    });
  });

  it('waiting a bit', function(done) {
    timers.setTimeout(done, 100);
  });

  it('...propagates update from pouchdb', function(done) {
    expect(store.getState().todos.filter(function(doc) {
      return doc._id == 'b';
    }).length).to.equal(0);
    done();
  });

  it('making insert in pouchdb...', function(done) {
    db.post({
      _id: 'c',
      text: 'pay bills',
    }, done);
  });

  it('waiting a bit', function(done) {
    timers.setTimeout(done, 100);
  });

  it('...propagates update from pouchdb', function(done) {
    expect(store.getState().todos.filter(function(doc) {
      return doc._id == 'c';
    })[0].text).to.equal('pay bills');
    done();
  });

  it('...inserts filtered document', function(done) {
    db.post({
      _id: 'd',
      filter: true,
    }).then(() => done()).catch(done);
  });

  it('waiting a bit', function(done) {
    timers.setTimeout(done, 100);
  });

  it('...filters documents', function(done) {
    expect(store.getState().todos.filter(function(doc) {
      return doc._id == 'd';
    }).length).to.equal(0);
    done();
  });

  it('calles initialBatchDispatched', (done) => {
    const anotherMiddleware = PouchMiddleware({
      path: '/todos',
      db: db,
      actions: {
        remove: (doc) => { return {type: actionTypes.DELETE_TODO, id: doc._id} },
        insert: (doc) => { return {type: actionTypes.INSERT_TODO, todo: doc} },
        batchInsert: (docs) => { return {type: actionTypes.BATCH_INSERT_TODOS, todos: docs} },
        update: (doc) => { return {type: actionTypes.UPDATE_TODO, todo: doc} }
      },
      initialBatchDispatched(err) {
        if (err) {
          return done(err);
        }

        var called = false;
        store.subscribe(() => {
          if (called) {
            done(new Error('expect subscribe to only be called once'));
          }
          called = true;
          expect(store.getState().todos.length).to.equal(1);
          timers.setTimeout(done, 100);
        });

        expect(store.getState().todos.length).to.equal(2);
        store.dispatch({type: actionTypes.DELETE_TODO, id: 'c'});
      }
    });
    const store = redux.applyMiddleware(anotherMiddleware)(redux.createStore)(rootReducer);
    expect(store.getState().todos.length).to.equal(0);
  });
});
Download .txt
gitextract_9uhaz__j/

├── .babelrc
├── .gitignore
├── .istanbul.yml
├── .travis.yml
├── README.md
├── index.js
├── lib/
│   └── index.js
├── package.json
├── src/
│   └── index.js
└── test/
    ├── _action_types.js
    ├── reducers/
    │   ├── index.js
    │   ├── todos.js
    │   └── todosobject.js
    ├── standalone.js
    ├── with-redux-object.js
    └── with-redux.js
Download .txt
SYMBOL INDEX (13 symbols across 5 files)

FILE: lib/index.js
  function createPouchMiddleware (line 10) | function createPouchMiddleware(_paths) {
  function differences (line 166) | function differences(oldDocs, newDocs) {
  function onDbChange (line 207) | function onDbChange(path, change, dispatch) {
  function warn (line 231) | function warn(what) {
  function defaultAction (line 239) | function defaultAction(action) {

FILE: src/index.js
  function createPouchMiddleware (line 8) | function createPouchMiddleware(_paths) {
  function differences (line 164) | function differences(oldDocs, newDocs) {
  function onDbChange (line 202) | function onDbChange(path, change, dispatch) {
  function warn (line 226) | function warn(what) {
  function defaultAction (line 234) | function defaultAction(action) {

FILE: test/reducers/todos.js
  function id (line 68) | function id() {

FILE: test/reducers/todosobject.js
  function id (line 35) | function id() {

FILE: test/with-redux.js
  method initialBatchDispatched (line 177) | initialBatchDispatched(err) {
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
  {
    "path": ".babelrc",
    "chars": 52,
    "preview": "{\n  \"presets\": [\n    \"es2015\",\n    \"stage-0\"\n  ]\n}\n\n"
  },
  {
    "path": ".gitignore",
    "chars": 22,
    "preview": "node_modules\ncoverage\n"
  },
  {
    "path": ".istanbul.yml",
    "chars": 163,
    "preview": "instrumentation:\n    excludes: ['test', 'node_modules']\ncheck:\n    global:\n        lines: 100\n        branches: 100\n    "
  },
  {
    "path": ".travis.yml",
    "chars": 39,
    "preview": "language: node_js\nnode_js:\n  - 4\n  - 5\n"
  },
  {
    "path": "README.md",
    "chars": 4799,
    "preview": "# pouch-redux-middleware\n\n[![By](https://img.shields.io/badge/made%20by-yld!-32bbee.svg?style=flat)](http://yld.io/conta"
  },
  {
    "path": "index.js",
    "chars": 35,
    "preview": "module.exports = require('./lib');\n"
  },
  {
    "path": "lib/index.js",
    "chars": 5776,
    "preview": "'use strict';\n\nvar jPath = require('json-path');\nvar Queue = require('async-function-queue');\nvar extend = require('xten"
  },
  {
    "path": "package.json",
    "chars": 1241,
    "preview": "{\n  \"name\": \"pouch-redux-middleware\",\n  \"version\": \"1.1.0\",\n  \"description\": \"PouchDB Redux Middleware\",\n  \"main\": \"lib/"
  },
  {
    "path": "src/index.js",
    "chars": 5511,
    "preview": "var jPath = require('json-path');\nvar Queue = require('async-function-queue');\nvar extend = require('xtend');\nvar equal "
  },
  {
    "path": "test/_action_types.js",
    "chars": 230,
    "preview": "[\n  'ERROR',\n  'ADD_TODO',\n  'INSERT_TODO',\n  'BATCH_INSERT_TODOS',\n  'DELETE_TODO',\n  'EDIT_TODO',\n  'UPDATE_TODO',\n  '"
  },
  {
    "path": "test/reducers/index.js",
    "chars": 196,
    "preview": "var redux = require('redux');\nvar todos = require('./todos');\nvar todosobject = require('./todosobject');\n\nmodule.export"
  },
  {
    "path": "test/reducers/todos.js",
    "chars": 1581,
    "preview": "var actionTypes = require('../_action_types');\n\nconst initialState = []\n\nmodule.exports = function todos(state, action) "
  },
  {
    "path": "test/reducers/todosobject.js",
    "chars": 860,
    "preview": "var actionTypes = require('../_action_types');\n\nconst initialState = []\n\nmodule.exports = function todosobject(state, ac"
  },
  {
    "path": "test/standalone.js",
    "chars": 777,
    "preview": "var Lab = require('lab');\nvar lab = exports.lab = Lab.script();\nvar describe = lab.experiment;\nvar before = lab.before;\n"
  },
  {
    "path": "test/with-redux-object.js",
    "chars": 2350,
    "preview": "var Lab = require('lab');\nvar lab = exports.lab = Lab.script();\nvar describe = lab.experiment;\nvar before = lab.before;\n"
  },
  {
    "path": "test/with-redux.js",
    "chars": 5783,
    "preview": "var Lab = require('lab');\nvar lab = exports.lab = Lab.script();\nvar describe = lab.experiment;\nvar before = lab.before;\n"
  }
]

About this extraction

This page contains the full source code of the pgte/pouch-redux-middleware GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (28.7 KB), approximately 7.8k tokens, and a symbol index with 13 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!