diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index d48edb27..00000000
--- a/.eslintignore
+++ /dev/null
@@ -1,5 +0,0 @@
-tests/integration/deps/*.js
-tests/integration/cordova/**/*.js
-tests/performance-bundle.js
-tests/fuzzy/seedrandom.min.js
-tests/integration/utils-bundle.js
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
index c03794ec..17bf1ddb 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,18 +1,14 @@
{
-
"extends": "eslint:recommended",
-
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
-
"env": {
"browser": true,
"node": true,
"mocha": true
},
-
"globals": {
"Map": true,
"Set": true,
@@ -29,17 +25,47 @@
"testUtils": true,
"importScripts": true
},
-
"rules": {
"no-empty": 0,
"no-console": 0,
- "semi": ["error", "always"],
- "curly": ["error", "all"],
- "space-unary-ops": ["error", {"words": true}],
- "space-before-function-paren": ["error", {"anonymous": "always", "named": "never"}],
- "max-len": ["error", 100, {"ignoreComments": true, "ignoreStrings": true, "ignoreRegExpLiterals": true}],
- "curly": ["error"],
- "keyword-spacing": ["error"],
- "space-before-blocks": ["error"]
+ "semi": [
+ "error",
+ "always"
+ ],
+ "curly": [
+ "error",
+ "all"
+ ],
+ "space-unary-ops": [
+ "error",
+ {
+ "words": true
+ }
+ ],
+ "space-before-function-paren": [
+ "error",
+ {
+ "anonymous": "always",
+ "named": "never"
+ }
+ ],
+ "max-len": [
+ "error",
+ 100,
+ {
+ "ignoreComments": true,
+ "ignoreStrings": true,
+ "ignoreRegExpLiterals": true
+ }
+ ],
+ "curly": [
+ "error"
+ ],
+ "keyword-spacing": [
+ "error"
+ ],
+ "space-before-blocks": [
+ "error"
+ ]
}
}
diff --git a/.gitignore b/.gitignore
index 5948bc06..0d258827 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-node_modules/
+/node_modules
testdb_*
test_*
_pouch_*
@@ -12,3 +12,4 @@ config.json
log.txt
/pouchdb
/tmp
+pouchdb-tests
diff --git a/.travis.yml b/.travis.yml
index 71dc711e..dcecf787 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -31,6 +31,8 @@ env:
- secure: "MiufQQKR/EBoS7kcau/I7oYenVilysEqwx37zdgLEKlEUe3SxVOe31uLZv/bhfLNZiRuLAfmIOZmhLGnhMf0LaBzR2yC5qhBxrVHcAiTuS3q6zxpzEf02jnu+hACvj1kJJEPjpOLpEVx7ghWL4McEO0qLbdtSbQlm2IkOX1ONg0="
matrix:
+ - COMMAND=unit-tests
+ - COMMAND=test-express-minimum
- CLIENT=node COMMAND=test-pouchdb
- CLIENT=node SERVER_ARGS=--in-memory COMMAND=test-pouchdb
- CLIENT=node SERVER_ARGS=--sqlite COMMAND=test-pouchdb
@@ -40,6 +42,10 @@ env:
- CLIENT=selenium:firefox COMMAND=test-pouchdb
- CLIENT=selenium:phantomjs COMMAND=test-pouchdb
+ # Run the mapreduce tests
+ - COMMAND=test-pouchdb TYPE=mapreduce CLIENT=node
+ - COMMAND=test-pouchdb TYPE=mapreduce CLIENT=selenium:firefox
+
# Test against the couchdb harness
- COMMAND=test-couchdb
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d1795749..07bb584b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,11 +1,71 @@
+Contributing
+===
+
+Want to contribute to PouchDB Server? Great, you've come to the right place!
+This document contains everything you need to know to get started building,
+testing, and publishing the code.
+
+Monorepo
+---
+
+PouchDB Server is a monorepo, containing the packages `pouchdb-server` and `express-pouchdb`. It follows the same format as the [PouchDB monorepo](https://github.com/pouchdb/pouchdb), using the [alle](https://github.com/boennemann/alle) monorepo setup. You can read more about monorepos [here](https://github.com/babel/babel/blob/master/doc/design/monorepo.md) and [here](https://gist.github.com/nolanlawson/457cdb309c9ec5b39f0d420266a9faa4).
+
+Conceptually `pouchdb-server` is a standalone CouchDB-esque server, whereas
+`express-pouchdb` is designed to be injectable into an existing Express app.
+`pouchdb-server` uses `express-pouchdb` under the hood.
+
+Testing
+---
+
+One of the primary benefits of **pouchdb-server** is the ability to run PouchDB's Node test suite against itself. To do that, you can simply,
+
+```bash
+$ npm run test-pouchdb
+```
+
+Whatever args you provide as `SERVER_ARGS` will be passed to `pouchdb-server` itself:
+
+```bash
+$ SERVER_ARGS='--in-memory' npm run test-pouchdb
+```
+
+Or to test in Firefox (IndexedDB):
+
+```bash
+$ CLIENT=selenium:firefox npm run test-pouchdb
+```
+
+Or to test in PhantomJS (WebSQL):
+
+```bash
+$ CLIENT=selenium:phantomjs ES5_SHIM=true npm run test-pouchdb
+```
+
+Additionally, we've started porting CouchDB's JavaScript test harness to
+[a simple Node module](https://github.com/nick-thompson/couchdb-harness), which can be run against PouchDB via **pouchdb-server**.
+
+```bash
+$ npm run test-couchdb
+```
+
+## Submitting a pull request
+
Want to help me make this thing awesome? Great! Here's how you should get started.
-1. First, make sure that the bugfix or feature you're looking to implement isn't better fit for [express-pouchdb](https://github.com/nick-thompson/express-pouchdb).
-2. PouchDB is still developing rapidly. If you need bleeding egde versions, you should first read how to [set up express-pouchdb for local development](https://github.com/nick-thompson/express-pouchdb#contributing). (Make sure that, afterwards, you `npm link` express-pouchdb).
-3. Go ahead and fork **pouchdb-server**, clone it to your machine.
-4. Now you'll want to, from the root of **pouchdb-server**, `npm link express-pouchdb`.
-5. `npm install` the rest of the dependencies.
+1. First, check whether your bugfix must be in `express-pouchdb` or `pouchdb-server`.
+2. Make your changes on a separate branch whose name reflects your changes,
+3. Run all tests to make sure your changes do not break anything
+4. To create a PR, push your changes to your fork, and open a pull request!
+5. For a PR, follow the commit message style guidelines in[PouchDB CONTRIBUTING.md](https://github.com/pouchdb/pouchdb/blob/master/CONTRIBUTING.md).
+
+## Release process
+
+`pouchdb-server` is a monorepo, meaning that when you publish, you need to publish all packages simultaneously. Versions are kept in sync across packages, for simplicity's sake.
+
+Release process:
-Please make your changes on a separate branch whose name reflects your changes, push them to your fork, and open a pull request!
+1. `npm version patch | minor | major` to change the version in the top-level `package.json`, which will apply to all packages in the release script
+2. `git push origin master --tags`
+3. `npm run release`
-For commit message style guidelines, please refer to [PouchDB CONTRIBUTING.md](https://github.com/pouchdb/pouchdb/blob/master/CONTRIBUTING.md).
+This will search through all the sub-packages and automatically figure out the correct `dependencies` and `optionalDependencies`. To add a new dependency, just add it to the top-level `package.json` and it will be identified at release time.
\ No newline at end of file
diff --git a/README.md b/README.md
index 1223f16d..434c7ebb 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,54 @@
-# pouchdb-server
-[![Build Status](https://travis-ci.org/pouchdb/pouchdb-server.svg)](https://travis-ci.org/pouchdb/pouchdb-server)
+PouchDB Server [![Build Status](https://travis-ci.org/pouchdb/pouchdb-server.svg)](https://travis-ci.org/pouchdb/pouchdb-server)
+=====
-> A standalone REST interface server for PouchDB.
+PouchDB Server is a drop-in replacement for CouchDB, using PouchDB and
+Node.js. It is modeled after the single-node design of CouchDB 1.x,
+although it contains some CouchDB 2.x features such as
+[Mango queries](http://github.com/nolanlawson/pouchdb-find).
-## Introduction
+PouchDB Server is not designed for production environments, as it is much less
+battle-tested than CouchDB. However, it can be useful for development,
+testing, and prototypes. It passes the full [PouchDB test suite](https://github.com/pouchdb/pouchdb/tree/master/tests).
-**pouchdb-server** is a simple Node.js server that presents a simple REST API, which mimics that of [CouchDB](http://couchdb.apache.org),
-on top of [PouchDB](http://pouchdb.com). Among other things, this allows users to replicate IndexedDB stores to LevelDB, or to
-spin up a quick and dirty drop-in replacement for CouchDB to get things moving quickly.
+_For the `express-pouchdb` sub-package, skip to [express-pouchdb](#express-pouchdb)._
-## Installation
+Usage
+---
-```bash
-$ npm install -g pouchdb-server
-```
+Install:
-## Usage
+ npm install -g pouchdb-server
-```
+Then run:
+
+ pouchdb-server
+
+Now you can view the [Fauxton](https://github.com/apache/couchdb-fauxton) web
+interface by opening `http://localhost:5984/_utils` in a browser.
+
+### Basic options
+
+PouchDB Server's default port is 5984. To change it:
+
+ pouchdb-server --port 3000
+
+By default, all files are written in the current directory. To use another one:
+ pouchdb-server --dir path/to/my/directory
+
+PouchDB Server can run fully in-memory (no changes are saved):
+
+ pouchdb-server --in-memory
+
+Or it can run using SQLite rather than LevelDB:
+
+ pouchdb-server --sqlite
+
+### Full options
+
+Most PouchDB Server options are available via the command line:
+
+```
Usage: pouchdb-server [options]
Options:
@@ -73,59 +103,43 @@ Examples:
Requires: npm install sqldown sqlite3
```
-A simple example might be,
+### Configuration
-```bash
-$ pouchdb-server -p 15984
-pouchdb-server listening on port 15984.
-```
-
-Alternatively, **pouchdb-server**'s functionality can be mounted into other Express web apps. For more information
-on that, check out [express-pouchdb](https://github.com/nick-thompson/express-pouchdb).
-
-## Fauxton
-
-**pouchdb-server** currently supports an experimental version of CouchDB's [Fauxton](http://docs.couchdb.org/en/latest/fauxton/index.html). Fauxton, the successor to CouchDB's original Futon, is a simple web UI for interacting with your databases. With your server running, navigate to `/_utils` to check it out!
-
-## Configuration
-
-By default, you can configure pouchdb-server using a `config.json` file, which is
-typically expected at the root of wherever you run pouchdb-server, but may be specified with the `--config` option.
+By default, you can configure PouchDB Server using a `config.json` file, which is
+typically expected at the root of wherever you run it, but may be specified with the `--config` option.
Below are some examples of `config.json` options:
-### log.level
+#### log.level
To change the log output level, you can create a `config.json` file containing e.g.:
-```js
+```json
{
"log": {
"level": "none"
}
}
-
```
The available values are `debug`, `info`, `warning`, `error`, and `none`. The default
is `info`.
-### log.file
+#### log.file
To choose the file where logs are written, you can create a `config.json` file containing e.g.:
-```js
+```json
{
"log": {
"file": "/path/to/log.txt"
}
}
-
```
By default, logs are written to `./log.txt`.
-## Automatic port configuration
+### Automatic port configuration
Due to conventions set by Heroku and others, if you have a `PORT` environment variable,
`pouchdb-server` will pick up on that and use it instead of `5984` as the default.
@@ -135,61 +149,230 @@ export PORT=3000
pouchdb-server # will run on port 3000
```
-## Testing
+express-pouchdb
+-----
-One of the primary benefits of **pouchdb-server** is the ability to run PouchDB's Node test suite against itself. To do that, you can simply,
+The `express-pouchdb` module is a fully qualified [Express](http://expressjs.com/) application with routing defined to
+mimic most of the [CouchDB](http://couchdb.apache.org/) REST API, and whose behavior is handled by
+[PouchDB](http://pouchdb.com/).
-```bash
-$ npm run test-pouchdb
+The intention is for `express-pouchdb` to be mounted into Express apps for
+extended usability. A simple example of this is
+[pouchdb-server](https://github.com/pouchdb/pouchdb-server) itself.
+
+### Usage
+
+Install the necessary packages:
+
+ npm install express-pouchdb pouchdb express
+
+Now here's a sample Express app, which we'll name `app.js`.
+
+```js
+var PouchDB = require('pouchdb');
+var express = require('express');
+var app = express();
+
+app.use('/db', require('express-pouchdb')(PouchDB));
+
+app.listen(3000);
```
-Whatever args you provide as `SERVER_ARGS` will be passed to `pouchdb-server` itself:
+Now we can run this script and find each of `express-pouchdb`'s routes
+at the `/db` path:
-```bash
-$ SERVER_ARGS='--in-memory' npm run test-pouchdb
+ node app.js &
+ curl localhost:3000/db
+
+You should see:
+
+```json
+{
+ "express-pouchdb": "Welcome!",
+ "uuid": "c0da32be-957f-4934-861f-d1e3ed10e544",
+ "vendor": {
+ "name": "PouchDB authors",
+ "version": "2.2.6"
+ },
+ "version": "2.2.6"
+}
```
-Or to test in Firefox (IndexedDB):
+*Note:* `express-pouchdb` conflicts with some middleware. You can work
+around this by only enabling affected middleware for routes not handled
+by `express-pouchdb`. [body-parser](https://www.npmjs.com/package/body-parser)
+is the most important middleware known to be problematic.
+
+#### API
+
+`express-pouchdb` exports a single function that builds an express [application object](http://expressjs.com/4x/api.html#application). Its function signature is:
+
+``require('express-pouchdb')([PouchDB[, options]])``
+- ``PouchDB``: the PouchDB object used to access databases. Optional.
+- ``options``: Optional. These options are supported:
+ - ``configPath``: a path to the configuration file to use. Defaults to './config.json'.
+ - ``logPath``: a path to the log file to use. Defaults to './log.txt'.
+ - ``inMemoryConfig``: `true` if all configuration should be in-memory. Defaults to `false`.
+ - ``mode``: determines which parts of the HTTP API express-pouchdb offers are enabled. There are three values:
+ - ``'fullCouchDB'``: enables every part of the HTTP API, which makes express-pouchdb very close to a full CouchDB replacement. This is the default.
+ - ``'minimumForPouchDB'``: just exposes parts of the HTTP API that map 1-1 to the PouchDB api. This is the minimum required to make the PouchDB test suite run, and a nice start when you just need an HTTP API to replicate with.
+ - ``'custom'``: no parts of the HTTP API are enabled. You can add parts yourself using the ``opts.overrideMode`` discussed below.
+ - ``overrideMode``: Sometimes the preprogrammed modes are insufficient for your needs, or you chose the ``'custom'`` mode. In that case, you can set this to an object. This object can have the following properties:
+ - ``'include'``: a javascript array that specifies parts to include on top of the ones specified by ``opts.mode``. Optional.
+ - ``'exclude'``: a javascript array that specifies parts to exclude from the ones specified by ``opts.mode``. Optional.
+
+The application object returned contains some extra properties that
+offer additional functionality compared to an ordinary express
+application:
+
+- ``setPouchDB``: a function that allows changing the ``PouchDB`` object `express-pouchdb` uses on the fly. Takes one argument: the new ``PouchDB`` object to use.
+- ``couchConfig``: an object that provides programmatic access to the configuration file and HTTP API express-pouchdb offers. For an overview of available configuration options, take a look at Fauxton's configuration page. (``/_utils#_config``)
+- ``couchLogger``: an object that provides programmatic access to the log file and HTTP API `express-pouchdb` offers.
+
+#### Examples
+
+##### Example 1
+
+Builds an HTTP API that exposes a minimal HTTP interface, but adds
+Fauxton as a debugging tool.
+
+```javascript
+var app = require('express-pouchdb')({
+ mode: 'minimumForPouchDB',
+ overrideMode: {
+ include: ['routes/fauxton']
+ }
+});
+// when not specifying PouchDB as an argument to the main function, you
+// need to specify it like this before requests are routed to ``app``
+app.setPouchDB(require('pouchdb'));
+```
-```bash
-$ CLIENT=selenium:firefox npm run test-pouchdb
+##### Example 2
+
+builds a full HTTP API but excludes express-pouchdb's authentication
+logic (say, because it interferes with custom authentication logic used
+in our own express app):
+
+```javascript
+var app2 = require('express-pouchdb')(require('pouchdb'), {
+ mode: 'fullCouchDB', // specified for clarity. It's the default so not necessary.
+ overrideMode: {
+ exclude: [
+ 'routes/authentication',
+ // disabling the above, gives error messages which require you to disable the
+ // following parts too. Which makes sense since they depend on it.
+ 'routes/authorization',
+ 'routes/session'
+ ]
+ }
+});
```
-Or to test in PhantomJS (WebSQL):
+#### Using your own PouchDB
-```bash
-$ CLIENT=selenium:phantomjs ES5_SHIM=true npm run test-pouchdb
+Since you pass in the `PouchDB` that you would like to use with
+`express-pouchb`, you can drop `express-pouchdb` into an existing Node-based
+PouchDB application and get all the benefits of the HTTP interface
+without having to change your code.
+
+```js
+var express = require('express')
+ , app = express()
+ , PouchDB = require('pouchdb');
+
+app.use('/db', require('express-pouchdb')(PouchDB));
+
+var myPouch = new PouchDB('foo');
+
+// myPouch is now modifiable in your own code, and it's also
+// available via HTTP at /db/foo
```
-Additionally, we've started porting CouchDB's JavaScript test harness to
-[a simple Node module](https://github.com/nick-thompson/couchdb-harness), which can be run against PouchDB via **pouchdb-server**.
+#### PouchDB defaults
-```bash
-$ npm run test-couchdb
+When you use your own PouchDB code in tandem with `express-pouchdb`, the `PouchDB.defaults()` API can be very convenient for specifying some default settings for how PouchDB databases are created.
+
+For instance, if you want to use an in-memory [MemDOWN](https://github.com/rvagg/memdown)-backed pouch, you can simply do:
+
+```js
+var InMemPouchDB = PouchDB.defaults({db: require('memdown')});
+
+app.use('/db', require('express-pouchdb')(InMemPouchDB));
+
+var myPouch = new InMemPouchDB('foo');
```
-## Contributing
+Similarly, if you want to place all database files in a folder other than the `pwd`, you can do:
-Want to help me make this thing awesome? Great! Here's how you should get started.
+```js
+var TempPouchDB = PouchDB.defaults({prefix: '/tmp/my-temp-pouch/'});
+
+app.use('/db', require('express-pouchdb')(TempPouchDB));
+
+var myPouch = new TempPouchDB('foo');
+```
-1. First, make sure that the bugfix or feature you're looking to implement isn't better fit for [express-pouchdb](https://github.com/nick-thompson/express-pouchdb).
-2. PouchDB is still developing rapidly. If you need bleeding egde versions, you should first read how to [set up express-pouchdb for local development](https://github.com/nick-thompson/express-pouchdb#contributing). (Make sure that, afterwards, you `npm link` express-pouchdb).
-3. Go ahead and fork **pouchdb-server**, clone it to your machine.
-4. Now you'll want to, from the root of **pouchdb-server**, `npm link express-pouchdb`.
-5. `npm install` the rest of the dependencies.
+If you want express-pouchdb to proxy requests to another CouchDB-style
+HTTP API, you can use [http-pouchdb](https://www.npmjs.com/package/http-pouchdb):
-Please make your changes on a separate branch whose name reflects your changes, push them to your fork, and open a pull request!
+```javascript
+var TempPouchDB = require('http-pouchdb')(PouchDB, 'http://localhost:5984');
+app.use('/db', require('express-pouchdb')(TempPouchDB));
+```
+
+#### Functionality
+
+On top of the exposing everything PouchDB offers through a CouchDB-like
+interface, `express-pouchdb` also offers the following extra
+functionality found in CouchDB but not in PouchDB by default (depending
+on the mode used, of course):
+
+- [Fauxton][], a web interface for the HTTP API.
+- [Authentication][] and [authorisation][] support. HTTP basic
+ authentication and cookie authentication are available. Authorisation
+ is handled by [validation functions][] and [security documents][].
+- [Configuration][] support. You can modify configuration values
+ manually in the `config.json` file, or use the HTTP or Fauxton
+ interface.
+- [Replicator database][] support. This allows your replications to
+ persist past a restart of your application.
+- Support for [show][], [list][] and [update][] functions. These allow
+ you to serve non-json content straight from your database.
+- [Rewrite][] and [Virtual Host][] support, for nicer urls.
+
+[fauxton]: https://www.npmjs.com/package/fauxton
+[authentication]: http://docs.couchdb.org/en/latest/intro/security.html
+[authorisation]: http://docs.couchdb.org/en/latest/intro/overview.html#security-and-validation
+[validation functions]: http://docs.couchdb.org/en/latest/couchapp/ddocs.html#vdufun
+[security documents]: http://docs.couchdb.org/en/latest/api/database/security.html
+[configuration]: http://docs.couchdb.org/en/latest/config/intro.html#setting-parameters-via-the-http-api
+[replicator database]: http://docs.couchdb.org/en/latest/replication/replicator.html
+[show]: http://guide.couchdb.org/editions/1/en/show.html
+[list]: http://guide.couchdb.org/editions/1/en/transforming.html
+[update]: http://docs.couchdb.org/en/latest/couchapp/ddocs.html#update-functions
+[rewrite]: http://docs.couchdb.org/en/latest/api/ddoc/rewrites.html
+[virtual host]: http://docs.couchdb.org/en/latest/config/http.html#vhosts
+
+Getting Help
+------------
+
+The PouchDB community is active [on Slack](http://slack.pouchdb.com/), [on Freenode IRC](https://www.irccloud.com/invite?channel=pouchdb&hostname=irc.freenode.net&port=6697&ssl=1), [Slack](http://slack.pouchdb.com),in [the Google Groups mailing list](https://groups.google.com/forum/#!forum/pouchdb), and [on StackOverflow](http://stackoverflow.com/questions/tagged/pouchdb). Or you can [tweet @pouchdb](http://twitter.com/pouchdb)!
+
+If you think you've found a bug in PouchDB, please write a reproducible test case and file [a Github issue](https://github.com/pouchdb/pouchdb/issues). We recommend [bl.ocks.org](http://bl.ocks.org/) for code snippets, because some iframe-based services like JSFiddle and JSBin do not support IndexedDB in all browsers. You can start with [this template](https://gist.github.com/nolanlawson/816f138a51b86785d3e6).
+
+## Contributing
-For commit message style guidelines, please refer to [PouchDB CONTRIBUTING.md](https://github.com/pouchdb/pouchdb/blob/master/CONTRIBUTING.md).
+See the [CONTRIBUTING.md](https://github.com/pouchdb/pouchdb-server/blob/master/CONTRIBUTING.md) file for how to get involved.
## Contributors
-[These people](https://github.com/pouchdb/express-pouchdb/graphs/contributors) made **pouchdb-server** into what it is today!
+[These people](https://github.com/pouchdb/pouchdb-server/graphs/contributors) made PouchDB Server into what it is today!
## Changelog
-`pouchdb-server` follows [semantic versioning](http://semver.org/). To see a changelog with all releases since v2.0.0, check out the [Github releases page](https://github.com/pouchdb/pouchdb-server/releases).
+`pouchdb-server` and `express-pouchdb` follow [semantic versioning](http://semver.org/). To see a changelog with all releases since v2.0.0, check out the [Github releases page](https://github.com/pouchdb/pouchdb-server/releases).
## License
-Apache License Version 2.0. See [the LICENSE file](https://github.com/pouchdb/pouchdb-server/blob/master/LICENSE) for more information.
+The Apache 2 License. See [the LICENSE file](https://github.com/pouchdb/pouchdb-server/blob/master/LICENSE) for more information.
diff --git a/bin/express-pouchdb-minimum-for-pouchdb.js b/bin/express-pouchdb-minimum-for-pouchdb.js
new file mode 100755
index 00000000..808a111e
--- /dev/null
+++ b/bin/express-pouchdb-minimum-for-pouchdb.js
@@ -0,0 +1,8 @@
+'use strict';
+
+var PouchDB = require('pouchdb');
+
+var app = require('../packages/node_modules/express-pouchdb')(PouchDB, {
+ mode: 'minimumForPouchDB'
+});
+app.listen(6984);
diff --git a/bin/pouchdb-server b/bin/pouchdb-server
deleted file mode 100755
index 18f15738..00000000
--- a/bin/pouchdb-server
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env node
-
-require('../lib/index.js');
\ No newline at end of file
diff --git a/bin/prerelease.js b/bin/prerelease.js
new file mode 100644
index 00000000..070e49cf
--- /dev/null
+++ b/bin/prerelease.js
@@ -0,0 +1,72 @@
+'use strict';
+
+// Update all the dependencies inside packages/node_modules/*/package.json
+// to reflect the true dependencies (automatically determined by require())
+// and update the version numbers to reflect the version from the top-level
+// package.json.
+
+var fs = require('fs');
+var path = require('path');
+var glob = require('glob');
+var findRequires = require('find-requires');
+var builtinModules = require('builtin-modules');
+var uniq = require('lodash.uniq');
+var flatten = require('lodash.flatten');
+
+var topPkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
+var modules = fs.readdirSync('./packages/node_modules');
+var version = topPkg.version;
+
+modules.forEach(function (mod) {
+ var pkgDir = path.join('./packages/node_modules', mod);
+ var pkgPath = path.join(pkgDir, 'package.json');
+ var pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
+
+ // all packages get the same version as the top package.json
+ pkg.version = version;
+
+ // for the dependencies, find all require() calls
+ var srcFiles = glob.sync(path.join(pkgDir, 'lib/**/*.js'));
+ var uniqDeps = uniq(flatten(srcFiles.map(function (srcFile) {
+ var code = fs.readFileSync(srcFile, 'utf8');
+ try {
+ return findRequires(code);
+ } catch (e) {
+ return []; // happens if this is an es6 module, parsing fails
+ }
+ }))).filter(function (dep) {
+ // some modules require() themselves, e.g. for plugins
+ return dep !== pkg.name &&
+ // exclude built-ins like 'inherits', 'fs', etc.
+ builtinModules.indexOf(dep) === -1;
+ }).sort();
+
+ //find dependencies and igonore if we referencing a local file
+ const dependencies = uniqDeps.reduce(function (deps, dep) {
+ if (/^\.\//.test(dep) || /^\.\.\//.test(dep)) {
+ return deps; // do nothing its a local file
+ }
+
+ dep = dep.split('/')[0]; // split colors/safe to be colors
+
+ if (topPkg.dependencies[dep]) {
+ deps.dependencies[dep] = topPkg.dependencies[dep];
+ } else if (topPkg.optionalDependencies[dep]) {
+ deps.optionalDependencies[dep] = topPkg.optionalDependencies[dep];
+ } else if (modules.indexOf(dep) !== -1) { // core pouchdb-* module
+ deps.dependencies[dep] = topPkg.version;
+ } else {
+ throw new Error('Unknown dependency ' + dep);
+ }
+
+ return deps;
+ }, {
+ dependencies: {},
+ optionalDependencies: {}
+ });
+
+ Object.assign(pkg, dependencies);
+
+ var jsonString = JSON.stringify(pkg, null, ' ') + '\n';
+ fs.writeFileSync(pkgPath, jsonString, 'utf8');
+});
diff --git a/bin/release.sh b/bin/release.sh
new file mode 100755
index 00000000..b591ba26
--- /dev/null
+++ b/bin/release.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+for pkg in $(ls packages/node_modules); do
+ if [ ! -d "packages/node_modules/$pkg" ]; then
+ continue
+ elif [ "true" = $(node --eval "console.log(require('./packages/node_modules/$pkg/package.json').private);") ]; then
+ continue
+ fi
+ cd packages/node_modules/$pkg
+ echo "Publishing $pkg..."
+ npm publish
+ cd -
+done
+
+git checkout -- packages/node_modules/*/package.json
\ No newline at end of file
diff --git a/bin/set-version.js b/bin/set-version.js
new file mode 100755
index 00000000..4fd1bfad
--- /dev/null
+++ b/bin/set-version.js
@@ -0,0 +1,25 @@
+#!/usr/bin/env node
+
+'use strict';
+
+// Set the version of all modules, since they're versioned together.
+// Usage: npm run set-version -- [version]
+// e.g. npm run set-version -- 1.2.3
+
+var version = process.argv[process.argv.length - 1];
+var fs = require('fs');
+var path = require('path');
+
+var packages = fs.readdirSync('packages/node_modules');
+
+packages.map(function (pkg) {
+ return path.resolve(__dirname, '../packages/node_modules', pkg, 'package.json');
+}).concat([
+ path.resolve(__dirname, '../package.json')
+]).forEach(function (jsonFile) {
+ var json = JSON.parse(fs.readFileSync(jsonFile), 'utf-8');
+ json.version = version;
+ fs.writeFileSync(jsonFile, JSON.stringify(json, null, ' ') + '\n', 'utf-8');
+});
+
+console.log('done');
diff --git a/bin/test-couchdb.sh b/bin/test-couchdb.sh
index 9e14c697..7c3babff 100755
--- a/bin/test-couchdb.sh
+++ b/bin/test-couchdb.sh
@@ -4,7 +4,7 @@
# couchdb-harness depends on the fact that /_config/httpd/port equals
# the port on which pouchdb-server is running.
echo '{"httpd": {"port": 6984}}' > config.json
-node bin/pouchdb-server &
+./packages/node_modules/pouchdb-server/bin/pouchdb-server &
export POUCHDB_SERVER_PID=$!
./node_modules/couchdb-harness/bin/couchdb-harness -p 6984
diff --git a/bin/test-express-minimum.sh b/bin/test-express-minimum.sh
new file mode 100755
index 00000000..39341d69
--- /dev/null
+++ b/bin/test-express-minimum.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+node ./bin/express-pouchdb-minimum-for-pouchdb.js &
+POUCHDB_SERVER_PID=$!
+
+cd ./pouchdb-tests
+
+COUCH_HOST=http://127.0.0.1:6984 TIMEOUT=120000 npm run test-node
+
+EXIT_STATUS=$?
+if [[ ! -z $POUCHDB_SERVER_PID ]]; then
+ kill $POUCHDB_SERVER_PID
+fi
+exit $EXIT_STATUS
diff --git a/bin/test-pouchdb.sh b/bin/test-pouchdb.sh
index eb724802..9b4fdc5e 100755
--- a/bin/test-pouchdb.sh
+++ b/bin/test-pouchdb.sh
@@ -1,18 +1,14 @@
#!/bin/bash
-rm -fr tmp
-mkdir -p tmp
-cd tmp
-
-../bin/pouchdb-server -n -p 6984 $SERVER_ARGS &
+./packages/node_modules/pouchdb-server/bin/pouchdb-server -n -p 6984 $SERVER_ARGS &
POUCHDB_SERVER_PID=$!
-cd ../pouchdb
+cd ./pouchdb-tests
-COUCH_HOST=http://127.0.0.1:6984 TIMEOUT=120000 npm run test-node
+COUCH_HOST=http://localhost:6984 TIMEOUT=120000 npm run test-node
EXIT_STATUS=$?
if [[ ! -z $POUCHDB_SERVER_PID ]]; then
kill $POUCHDB_SERVER_PID
fi
-exit $EXIT_STATUS
+exit $EXIT_STATUS
\ No newline at end of file
diff --git a/bin/test-setup.sh b/bin/test-setup.sh
index f576c581..b6f70272 100755
--- a/bin/test-setup.sh
+++ b/bin/test-setup.sh
@@ -1,18 +1,14 @@
#!/bin/bash
-# install pouchdb from git rather than npm,
-# so we can run its own tests
-# also this branch may change as we update pouchdb
-# versions because we can't necessarily test against
-# the master tests since we use the version of pouchdb
-# from npm
-rm -rf pouchdb
-git clone --single-branch \
- --branch master \
- --depth 300 \
- https://github.com/pouchdb/pouchdb.git pouchdb
-cd pouchdb
-# using a specific commit for now rather than master
-git reset --hard 587ba8f4df4b6fcf32a8fd015f8c7ebadd8f280d
+DIRECTORY='pouchdb-tests'
+
+if [ ! -d "$DIRECTORY" ]; then
+ # Control will enter here if $DIRECTORY exists.
+ git clone --single-branch --branch master \
+ https://github.com/pouchdb/pouchdb.git ${DIRECTORY}
+fi
+
+cd pouchdb-tests
npm install
-cd -
+
+cd ..
diff --git a/examples/todos/app.js b/examples/todos/app.js
new file mode 100644
index 00000000..60fb7b05
--- /dev/null
+++ b/examples/todos/app.js
@@ -0,0 +1,19 @@
+
+/**
+ * Module dependencies.
+ */
+
+var express = require('express')
+ , path = require('path')
+ , morgan = require('morgan');
+
+var app = express();
+
+
+app.use(morgan('dev'));
+app.use(express.static(path.join(__dirname, 'public')));
+app.use('/db', require('../../'));
+
+
+app.listen(3000);
+console.log("Express server listening on port " + 3000);
diff --git a/examples/todos/public/images/bg.png b/examples/todos/public/images/bg.png
new file mode 100644
index 00000000..b2a76008
Binary files /dev/null and b/examples/todos/public/images/bg.png differ
diff --git a/examples/todos/public/index.html b/examples/todos/public/index.html
new file mode 100644
index 00000000..17115ee5
--- /dev/null
+++ b/examples/todos/public/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+ VanillaJS • TodoMVC
+
+
+
+
+
+
+
+
+
diff --git a/examples/todos/public/scripts/app.js b/examples/todos/public/scripts/app.js
new file mode 100644
index 00000000..ba202611
--- /dev/null
+++ b/examples/todos/public/scripts/app.js
@@ -0,0 +1,168 @@
+(function() {
+
+ 'use strict';
+
+ var ENTER_KEY = 13;
+ var newTodoDom = document.getElementById('new-todo');
+ var syncDom = document.getElementById('sync-wrapper');
+
+ // EDITING STARTS HERE (you dont need to edit anything above this line)
+
+ var db = new PouchDB('todos');
+ var remoteCouch = window.location.href + 'db/todos';
+
+ db.info(function(err, info) {
+ db.changes({
+ since: info.update_seq,
+ continuous: true,
+ onChange: showTodos
+ });
+ });
+
+ // We have to create a new todo document and enter it in the database
+ function addTodo(text) {
+ var todo = {
+ _id: new Date().toISOString(),
+ title: text,
+ completed: false
+ };
+ db.put(todo, function callback (err, result) {
+ if (!err) {
+ console.log('Successfully posted a todo!');
+ }
+ });
+ }
+
+ // Show the current list of todos by reading them from the database
+ function showTodos() {
+ db.allDocs({
+ include_docs: true,
+ descending: true
+ }, function(err, doc) {
+ redrawTodosUI(doc.rows);
+ });
+ }
+
+ function checkboxChanged(todo, event) {
+ todo.completed = event.target.checked;
+ db.put(todo);
+ }
+
+ // User pressed the delete button for a todo, delete it
+ function deleteButtonPressed(todo) {
+ db.remove(todo);
+ }
+
+ // The input box when editing a todo has blurred, we should save
+ // the new title or delete the todo if the title is empty
+ function todoBlurred(todo, event) {
+ var trimmedText = event.target.value.trim();
+ if (!trimmedText) {
+ db.remove(todo);
+ } else {
+ todo.title = trimmedText;
+ db.put(todo);
+ }
+ }
+
+ // Initialise a sync with the remote server
+ function sync() {
+ syncDom.setAttribute('data-sync-state', 'syncing');
+ var opts = {continuous: true, complete: syncError};
+ db.replicate.to(remoteCouch, opts);
+ db.replicate.from(remoteCouch, opts);
+ }
+
+ // EDITING STARTS HERE (you dont need to edit anything below this line)
+
+ // There was some form or error syncing
+ function syncError() {
+ syncDom.setAttribute('data-sync-state', 'error');
+ }
+
+ // User has double clicked a todo, display an input so they can edit the title
+ function todoDblClicked(todo) {
+ var div = document.getElementById('li_' + todo._id);
+ var inputEditTodo = document.getElementById('input_' + todo._id);
+ div.className = 'editing';
+ inputEditTodo.focus();
+ }
+
+ // If they press enter while editing an entry, blur it to trigger save
+ // (or delete)
+ function todoKeyPressed(todo, event) {
+ if (event.keyCode === ENTER_KEY) {
+ var inputEditTodo = document.getElementById('input_' + todo._id);
+ inputEditTodo.blur();
+ }
+ }
+
+ // Given an object representing a todo, this will create a list item
+ // to display it.
+ function createTodoListItem(todo) {
+ var checkbox = document.createElement('input');
+ checkbox.className = 'toggle';
+ checkbox.type = 'checkbox';
+ checkbox.addEventListener('change', checkboxChanged.bind(this, todo));
+
+ var label = document.createElement('label');
+ label.appendChild( document.createTextNode(todo.title));
+ label.addEventListener('dblclick', todoDblClicked.bind(this, todo));
+
+ var deleteLink = document.createElement('button');
+ deleteLink.className = 'destroy';
+ deleteLink.addEventListener( 'click', deleteButtonPressed.bind(this, todo));
+
+ var divDisplay = document.createElement('div');
+ divDisplay.className = 'view';
+ divDisplay.appendChild(checkbox);
+ divDisplay.appendChild(label);
+ divDisplay.appendChild(deleteLink);
+
+ var inputEditTodo = document.createElement('input');
+ inputEditTodo.id = 'input_' + todo._id;
+ inputEditTodo.className = 'edit';
+ inputEditTodo.value = todo.title;
+ inputEditTodo.addEventListener('keypress', todoKeyPressed.bind(this, todo));
+ inputEditTodo.addEventListener('blur', todoBlurred.bind(this, todo));
+
+ var li = document.createElement('li');
+ li.id = 'li_' + todo._id;
+ li.appendChild(divDisplay);
+ li.appendChild(inputEditTodo);
+
+ if (todo.completed) {
+ li.className += 'complete';
+ checkbox.checked = true;
+ }
+
+ return li;
+ }
+
+ function redrawTodosUI(todos) {
+ var ul = document.getElementById('todo-list');
+ ul.innerHTML = '';
+ todos.forEach(function(todo) {
+ ul.appendChild(createTodoListItem(todo.doc));
+ });
+ }
+
+ function newTodoKeyPressHandler( event ) {
+ if (event.keyCode === ENTER_KEY) {
+ addTodo(newTodoDom.value);
+ newTodoDom.value = '';
+ }
+ }
+
+ function addEventListeners() {
+ newTodoDom.addEventListener('keypress', newTodoKeyPressHandler, false);
+ }
+
+ addEventListeners();
+ showTodos();
+
+ if (remoteCouch) {
+ sync();
+ }
+
+})();
diff --git a/examples/todos/public/scripts/pouchdb-nightly.min.js b/examples/todos/public/scripts/pouchdb-nightly.min.js
new file mode 100644
index 00000000..ef9a7e95
--- /dev/null
+++ b/examples/todos/public/scripts/pouchdb-nightly.min.js
@@ -0,0 +1,3 @@
+!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.PouchDB=e():"undefined"!=typeof global?global.PouchDB=e():"undefined"!=typeof self&&(self.PouchDB=e())}(function(){var define,module,exports;return function e(t,n,r){function o(a,s){if(!n[a]){if(!t[a]){var u="function"==typeof require&&require;if(!s&&u)return u(a,!0);if(i)return i(a,!0);throw new Error("Cannot find module '"+a+"'")}var c=n[a]={exports:{}};t[a][0].call(c.exports,function(e){var n=t[a][1][e];return o(n?n:e)},c,c.exports,e,t,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;at&&a.push(e)}),merge.traverseRevTree(o,function(e,t,n,r,o){var i=t+"-"+n;"available"===o.status&&-1!==a.indexOf(i)&&(o.status="missing",s.push(i))}),customApi._doCompaction(e,o,s,n)})}var api={},customApi=Pouch.adapters[opts.adapter](opts,function(e,t){if(e)return callback&&callback(e),void 0;for(var n in api)t.hasOwnProperty(n)||(t[n]=api[n]);opts.name===Pouch.prefix+Pouch.ALL_DBS?callback(e,t):Pouch.open(opts,function(e){callback(e,t)})}),auto_compaction=opts.auto_compaction===!0;api.post=function(e,t,n){return"function"==typeof t&&(n=t,t={}),"object"!=typeof e||Array.isArray(e)?call(n,errors.NOT_AN_OBJECT):customApi.bulkDocs({docs:[e]},t,autoCompact(yankError(n)))},api.put=function(e,t,n){return"function"==typeof t&&(n=t,t={}),"object"!=typeof e?call(n,errors.NOT_AN_OBJECT):"_id"in e?customApi.bulkDocs({docs:[e]},t,autoCompact(yankError(n))):call(n,errors.MISSING_ID)},api.putAttachment=function(e,t,n,r,o,i){function a(e){e._attachments=e._attachments||{},e._attachments[t]={content_type:o,data:r},api.put(e,i)}return api.taskqueue.ready()?("function"==typeof o&&(i=o,o=r,r=n,n=null),"undefined"==typeof o&&(o=r,r=n,n=null),api.get(e,function(t,r){return t&&t.error===errors.MISSING_DOC.error?(a({_id:e}),void 0):t?(call(i,t),void 0):r._rev!==n?(call(i,errors.REV_CONFLICT),void 0):(a(r),void 0)}),void 0):(api.taskqueue.addTask("putAttachment",arguments),void 0)},api.removeAttachment=function(e,t,n,r){api.get(e,function(e,o){return e?(call(r,e),void 0):o._rev!==n?(call(r,errors.REV_CONFLICT),void 0):o._attachments?(delete o._attachments[t],0===Object.keys(o._attachments).length&&delete o._attachments,api.put(o,r),void 0):call(r,null)})},api.remove=function(e,t,n){"function"==typeof t&&(n=t,t={}),void 0===t&&(t={}),t.was_delete=!0;var r={_id:e._id,_rev:e._rev};return r._deleted=!0,customApi.bulkDocs({docs:[r]},t,yankError(n))},api.revsDiff=function(e,t,n){function r(e,t){s[e]||(s[e]={missing:[]}),s[e].missing.push(t)}function o(t,n){var o=e[t].slice(0);merge.traverseRevTree(n,function(e,n,i,a,s){var u=n+"-"+i,c=o.indexOf(u);-1!==c&&(o.splice(c,1),"available"!==s.status&&r(t,u))}),o.forEach(function(e){r(t,e)})}"function"==typeof t&&(n=t,t={});var i=Object.keys(e),a=0,s={};i.map(function(t){customApi._getRevisionTree(t,function(r,u){if(r&&"not_found"===r.error&&"missing"===r.reason)s[t]={missing:e[t]};else{if(r)return call(n,r);o(t,u)}return++a===i.length?call(n,null,s):void 0})})},api.compact=function(e,t){"function"==typeof e&&(t=e,e={}),api.changes({complete:function(e,n){if(e)return call(t),void 0;var r=n.results.length;return r?(n.results.forEach(function(e){compactDocument(e.id,0,function(){r--,r||call(t)})}),void 0):(call(t),void 0)}})},api.get=function(e,t,n){function r(){var r=[],i=o.length;return i?(o.forEach(function(o){api.get(e,{rev:o,revs:t.revs},function(e,t){e?r.push({missing:o}):r.push({ok:t}),i--,i||call(n,null,r)})}),void 0):call(n,null,r)}if(!api.taskqueue.ready())return api.taskqueue.addTask("get",arguments),void 0;"function"==typeof t&&(n=t,t={});var o=[];{if(!t.open_revs)return customApi._get(e,t,function(e,r){if(e)return call(n,e);var o=r.doc,i=r.metadata,a=r.ctx;if(t.conflicts){var s=merge.collectConflicts(i);s.length&&(o._conflicts=s)}if(t.revs||t.revs_info){var u=merge.rootToLeaf(i.rev_tree),c=arrayFirst(u,function(e){return-1!==e.ids.map(function(e){return e.id}).indexOf(o._rev.split("-")[1])});if(c.ids.splice(c.ids.map(function(e){return e.id}).indexOf(o._rev.split("-")[1])+1),c.ids.reverse(),t.revs&&(o._revisions={start:c.pos+c.ids.length-1,ids:c.ids.map(function(e){return e.id})}),t.revs_info){var d=c.pos+c.ids.length;o._revs_info=c.ids.map(function(e){return d--,{rev:d+"-"+e.id,status:e.opts.status}})}}if(t.local_seq&&(o._local_seq=r.metadata.seq),t.attachments&&o._attachments){var l=o._attachments,f=Object.keys(l).length;if(0===f)return call(n,null,o);Object.keys(l).forEach(function(e){customApi._getAttachment(l[e],{encode:!0,ctx:a},function(t,r){o._attachments[e].data=r,--f||call(n,null,o)})})}else{if(o._attachments)for(var p in o._attachments)o._attachments[p].stub=!0;call(n,null,o)}});if("all"===t.open_revs)customApi._getRevisionTree(e,function(e,t){e&&(t=[]),o=merge.collectLeaves(t).map(function(e){return e.rev}),r()});else{if(!Array.isArray(t.open_revs))return call(n,utils.error(errors.UNKNOWN_ERROR,"function_clause"));o=t.open_revs;for(var i=0;i1&&"_design"!==d[0]&&"_local"!==d[0]||d.length>2&&"_design"===d[0]&&"_local"!==d[0])&&(c.binary=!0),n(c,function(e,t,n){return e?u.call(o,e):(u.call(o,null,t,n),void 0)})},l.remove=function(e,t,o){return l.taskqueue.ready()?("function"==typeof t&&(o=t,t={}),n({headers:s.headers,method:"DELETE",url:i(s,r(e._id))+"?rev="+e._rev},o),void 0):(l.taskqueue.addTask("remove",arguments),void 0)},l.getAttachment=function(e,t,n,o){"function"==typeof n&&(o=n,n={}),void 0===n.auto_encode&&(n.auto_encode=!0),n.auto_encode&&(e=r(e)),n.auto_encode=!1,l.get(e+"/"+t,n,o)},l.removeAttachment=function(e,t,o,a){return l.taskqueue.ready()?(n({headers:s.headers,method:"DELETE",url:i(s,r(e)+"/"+t)+"?rev="+o},a),void 0):(l.taskqueue.addTask("removeAttachment",arguments),void 0)},l.putAttachment=function(e,t,o,a,u,c){if(!l.taskqueue.ready())return l.taskqueue.addTask("putAttachment",arguments),void 0;"function"==typeof u&&(c=u,u=a,a=o,o=null),"undefined"==typeof u&&(u=a,a=o,o=null);var d=r(e)+"/"+t,f=i(s,d);o&&(f+="?rev="+o);var p={headers:s.headers,method:"PUT",url:f,processData:!1,body:a,timeout:6e4};p.headers["Content-Type"]=u,n(p,c)},l.put=function(e,t,o){if(!l.taskqueue.ready())return l.taskqueue.addTask("put",arguments),void 0;if("function"==typeof t&&(o=t,t={}),"object"!=typeof e)return u.call(o,c.NOT_AN_OBJECT);if(!("_id"in e))return u.call(o,c.MISSING_ID);var a=[];t&&"undefined"!=typeof t.new_edits&&a.push("new_edits="+t.new_edits),a=a.join("&"),""!==a&&(a="?"+a),n({headers:s.headers,method:"PUT",url:i(s,r(e._id))+a,body:e},o)},l.post=function(e,t,n){return l.taskqueue.ready()?("function"==typeof t&&(n=t,t={}),"object"!=typeof e?u.call(n,c.NOT_AN_OBJECT):("_id"in e?l.put(e,t,n):p.list.length>0?(e._id=p.list.pop(),l.put(e,t,n)):p.get(function(r){return r?u.call(n,c.UNKNOWN_ERROR):(e._id=p.list.pop(),l.put(e,t,n),void 0)}),void 0)):(l.taskqueue.addTask("post",arguments),void 0)},l.bulkDocs=function(e,t,r){return l.taskqueue.ready()?("function"==typeof t&&(r=t,t={}),t||(t={}),"undefined"!=typeof t.new_edits&&(e.new_edits=t.new_edits),n({headers:s.headers,method:"POST",url:i(s,"_bulk_docs"),body:e},r),void 0):(l.taskqueue.addTask("bulkDocs",arguments),void 0)},l.allDocs=function(e,t){if(!l.taskqueue.ready())return l.taskqueue.addTask("allDocs",arguments),void 0;"function"==typeof e&&(t=e,e={});var r,o=[],a="GET";e.conflicts&&o.push("conflicts=true"),e.descending&&o.push("descending=true"),e.include_docs&&o.push("include_docs=true"),e.startkey&&o.push("startkey="+encodeURIComponent(JSON.stringify(e.startkey))),e.endkey&&o.push("endkey="+encodeURIComponent(JSON.stringify(e.endkey))),e.limit&&o.push("limit="+e.limit),"undefined"!=typeof e.skip&&o.push("skip="+e.skip),o=o.join("&"),""!==o&&(o="?"+o),"undefined"!=typeof e.keys&&(a="POST",r=JSON.stringify({keys:e.keys})),n({headers:s.headers,method:a,url:i(s,"_all_docs"+o),body:r},t)},l.changes=function(e){var t=25;if(!l.taskqueue.ready()){var r=l.taskqueue.addTask("changes",arguments);return{cancel:function(){return r.task?r.task.cancel():(r.parameters[0].aborted=!0,void 0)}}}if("latest"===e.since){var o;return l.info(function(t,n){e.aborted||(e.since=n.update_seq,o=l.changes(e))}),{cancel:function(){return o?o.cancel():(e.aborted=!0,void 0)}}}var a={},d="undefined"!=typeof e.limit?e.limit:!1;0===d&&(d=1);var f=d;if(e.style&&(a.style=e.style),(e.include_docs||e.filter&&"function"==typeof e.filter)&&(a.include_docs=!0),e.continuous&&(a.feed="longpoll"),e.conflicts&&(a.conflicts=!0),e.descending&&(a.descending=!0),e.filter&&"string"==typeof e.filter&&(a.filter=e.filter,"_view"===e.filter&&e.view&&"string"==typeof e.view&&(a.view=e.view)),e.query_params&&"object"==typeof e.query_params)for(var p in e.query_params)e.query_params.hasOwnProperty(p)&&(a[p]=e.query_params[p]);var v,h,m,_,g=function(r,o){a.since=r,e.continuous||_||(_=m),a.limit=!d||f>t?t:f;var u="?"+Object.keys(a).map(function(e){return e+"="+a[e]}).join("&"),c={headers:s.headers,method:"GET",url:i(s,"_changes"+u),timeout:null};h=r,e.aborted||(v=n(c,o))},y=10,b=0,k={results:[]},w=function(n,r){if(r&&r.results){k.last_seq=r.last_seq;var o={};o.query=e.query_params,r.results=r.results.filter(function(t){f--;var n=u.filterChange(e)(t);return n&&(k.results.push(t),u.call(e.onChange,t)),n})}else n&&(e.aborted=!0,u.call(e.complete,n,null));r&&r.last_seq&&(h=r.last_seq);var i=r&&r.results.length||0;_-=t;var a=d&&0>=f||r&&!i&&0>=_||i&&r.last_seq===m||e.descending&&0!==h;if(e.continuous||!a){n?b+=1:b=0;var s=1<p&&u.call(e.complete,n||c.UNKNOWN_ERROR,null),setTimeout(function(){g(h,w)},l)}else u.call(e.complete,null,k)};return e.continuous?g(e.since||0,w):l.info(function(t,n){return t?u.call(e.complete,t):(m=n.update_seq,g(e.since||0,w),void 0)}),{cancel:function(){e.aborted=!0,v.abort()}}},l.revsDiff=function(e,t,r){return l.taskqueue.ready()?("function"==typeof t&&(r=t,t={}),n({headers:s.headers,method:"POST",url:i(s,"_revs_diff"),body:e},function(e,t){u.call(r,e,t)}),void 0):(l.taskqueue.addTask("revsDiff",arguments),void 0)},l.close=function(e){return l.taskqueue.ready()?(u.call(e,null),void 0):(l.taskqueue.addTask("close",arguments),void 0)},l.replicateOnServer=function(e,r,i){if(!l.taskqueue.ready())return l.taskqueue.addTask("replicateOnServer",arguments),i;var a=o(e.id()),c={source:s.db,target:a.protocol===s.protocol&&a.authority===s.authority?a.db:a.source};r.continuous&&(c.continuous=!0),r.create_target&&(c.create_target=!0),r.doc_ids&&(c.doc_ids=r.doc_ids),r.filter&&"string"==typeof r.filter&&(c.filter=r.filter),r.query_params&&(c.query_params=r.query_params);var d,f={},p={headers:s.headers,method:"POST",url:s.protocol+"://"+s.host+(80===s.port?"":":"+s.port)+"/_replicate",body:c};i.cancel=function(){this.cancelled=!0,d&&!f.ok&&d.abort(),f._local_id&&(p.body={replication_id:f._local_id}),p.body.cancel=!0,n(p,function(e,n,o){return e?u.call(t,e):(u.call(r.complete,null,f,o),void 0)})},i.cancelled||(d=n(p,function(e,n,o){return e?u.call(t,e):(f.ok=!0,n._local_id&&(f._local_id=n._local_id),u.call(r.complete,null,n,o),void 0)}))},l}var u=e("../utils"),c=e("../deps/errors");n.options={strictMode:!1,key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}},s.destroy=function(e,t,n){var r=o(e,t);t=t||{},"function"==typeof t&&(n=t,t={}),t.headers=r.headers,t.method="DELETE",t.url=i(r,""),u.ajax(t,n)},s.valid=function(){return!0},t.exports=s},{"../deps/errors":7,"../utils":14}],3:[function(e,t){"use strict";function n(e){return function(t){o.call(e,{status:500,error:t.type,reason:t.target})}}function r(e,t){function s(e){e.createObjectStore(l,{keyPath:"id"}).createIndex("seq","seq",{unique:!0}),e.createObjectStore(f,{autoIncrement:!0}).createIndex("_doc_id_rev","_doc_id_rev",{unique:!0}),e.createObjectStore(p,{keyPath:"digest"}),e.createObjectStore(v,{keyPath:"id",autoIncrement:!1}),e.createObjectStore(h)}function u(e){for(var t=e.length,n=new ArrayBuffer(t),r=new Uint8Array(n),o=0;t>o;o++)r[o]=e.charCodeAt(o);return n}function c(e,t){return e._bulk_seq-t._bulk_seq}var d=1,l="document-store",f="by-sequence",p="attach-store",v="meta-store",h="detect-blob-support",m=e.name,_=window.indexedDB.open(m,d);"openReqList"in r||(r.openReqList={}),r.openReqList[m]=_;var g=null,y=null,b={},k=null;return _.onupgradeneeded=function(e){for(var t=e.target.result,n=e.oldVersion;n!==e.newVersion;)0===n&&s(t),n++},_.onsuccess=function(e){k=e.target.result,k.onversionchange=function(){k.close()};var n=k.transaction([v,h],"readwrite"),r=n.objectStore(v).get(v);r.onsuccess=function(e){var r=e.target.result||{id:v};m+"_id"in r?y=r[m+"_id"]:(y=o.uuid(),r[m+"_id"]=y,n.objectStore(v).put(r));try{n.objectStore(h).put(o.createBlob(),"key"),g=!0}catch(i){g=!1}finally{o.call(t,null,b)}}},_.onerror=n(t),b.type=function(){return"idb"},b.id=function(){return y},b._bulkDocs=function(e,t,s){function d(e){var t=e.target.result;t.updateSeq=(t.updateSeq||0)+x,N.objectStore(v).put(t)}function h(){if(!C.length)return N.objectStore(v).get(v).onsuccess=d,void 0;var e=C.shift(),t=N.objectStore(l).get(e.metadata.id);t.onsuccess=function(t){var n=t.target.result;n?q(n,e):S(e)}}function _(){var e=[];A.sort(c),A.forEach(function(t){if(delete t._bulk_seq,t.error)return e.push(t),void 0;var n=t.metadata,a=i.winningRev(n);e.push({ok:!0,id:n.id,rev:a}),o.isLocalId(n.id)||(r.Changes.notify(m),r.Changes.notifyLocalWindows(m))}),o.call(s,null,e)}function y(e,t){if(e.stub)return t();if("string"==typeof e.data){var n;try{n=atob(e.data)}catch(r){var i=o.error(a.BAD_ARG,"Attachments need to be base64 encoded");return o.call(s,i)}if(e.digest="md5-"+o.Crypto.MD5(n),g){var c=e.content_type;n=u(n),e.data=o.createBlob([n],{type:c})}return t()}var d=new FileReader;d.onloadend=function(){e.digest="md5-"+o.Crypto.MD5(this.result),g||(e.data=btoa(this.result)),t()},d.readAsBinaryString(e.data)}function b(e){function t(){n++,C.length===n&&e()}if(!C.length)return e();var n=0;C.forEach(function(e){function n(){o++,o===r.length&&t()}var r=e.data&&e.data._attachments?Object.keys(e.data._attachments):[];if(!r.length)return t();var o=0;for(var i in e.data._attachments)y(e.data._attachments[i],n)})}function w(e,t){function n(e){a||(e?(a=e,o.call(t,a)):s===u.length&&i())}function r(e){s++,n(e)}function i(){e.data._doc_id_rev=e.data._id+"::"+e.data._rev;var n=N.objectStore(f).put(e.data);n.onsuccess=function(n){e.metadata.seq=n.target.result,delete e.metadata.rev;var r=N.objectStore(l).put(e.metadata);r.onsuccess=function(){A.push(e),o.call(t)}}}var a=null,s=0;e.data._id=e.metadata.id,e.data._rev=e.metadata.rev,x++,o.isDeleted(e.metadata,e.metadata.rev)&&(e.data._deleted=!0);var u=e.data._attachments?Object.keys(e.data._attachments):[];for(var c in e.data._attachments)if(e.data._attachments[c].stub)s++,n();else{var d=e.data._attachments[c].data;delete e.data._attachments[c].data;var p=e.data._attachments[c].digest;O(e,p,d,r)}u.length||i()}function q(e,t){var n=i.merge(e.rev_tree,t.metadata.rev_tree[0],1e3),r=o.isDeleted(e),s=r&&o.isDeleted(t.metadata)||!r&&R&&"new_leaf"!==n.conflicts;return s?(A.push(E(a.REV_CONFLICT,t._bulk_seq)),h()):(t.metadata.rev_tree=n.tree,w(t,h),void 0)}function S(e){return"was_delete"in t&&o.isDeleted(e.metadata)?(A.push(a.MISSING_DOC),h()):(w(e,h),void 0)}function E(e,t){return e._bulk_seq=t,e}function O(e,t,n,r){{var i=N.objectStore(p);i.get(t).onsuccess=function(a){var s=a.target.result&&a.target.result.refs||{},u=[e.metadata.id,e.metadata.rev].join("@"),c={digest:t,body:n,refs:s};c.refs[u]=!0;i.put(c).onsuccess=function(){o.call(r)}}}}var R=t.new_edits,T=e.docs,C=T.map(function(e,t){var n=o.parseDoc(e,R);return n._bulk_seq=t,n}),D=C.filter(function(e){return e.error});if(D.length)return o.call(s,D[0]);var N,A=[],x=0;b(function(){N=k.transaction([l,f,p,v],"readwrite"),N.onerror=n(s),N.ontimeout=n(s),N.oncomplete=_,h()})},b._get=function(e,t,n){function r(){o.call(n,c,{doc:s,metadata:u,ctx:d})}var s,u,c,d;d=t.ctx?t.ctx:k.transaction([l,f,p],"readonly"),d.objectStore(l).get(e).onsuccess=function(e){if(u=e.target.result,!u)return c=a.MISSING_DOC,r();if(o.isDeleted(u)&&!t.rev)return c=o.error(a.MISSING_DOC,"deleted"),r();var n=i.winningRev(u),l=u.id+"::"+(t.rev?t.rev:n),p=d.objectStore(f).index("_doc_id_rev");p.get(l).onsuccess=function(e){return s=e.target.result,s&&s._doc_id_rev&&delete s._doc_id_rev,s?(r(),void 0):(c=a.MISSING_DOC,r())}}},b._getAttachment=function(e,t,n){var r,i;i=t.ctx?t.ctx:k.transaction([l,f,p],"readonly");var a=e.digest,s=e.content_type;i.objectStore(p).get(a).onsuccess=function(e){var i=e.target.result.body;if(t.encode)if(g){var a=new FileReader;a.onloadend=function(){r=btoa(this.result),o.call(n,null,r)},a.readAsBinaryString(i)}else r=i,o.call(n,null,r);else g?r=i:(i=u(atob(i)),r=o.createBlob([i],{type:s})),o.call(n,null,r)}},b._allDocs=function(e,t){var n="startkey"in e?e.startkey:!1,r="endkey"in e?e.endkey:!1,a="descending"in e?e.descending:!1;a=a?"prev":null;var s=n&&r?window.IDBKeyRange.bound(n,r):n?window.IDBKeyRange.lowerBound(n):r?window.IDBKeyRange.upperBound(r):null,u=k.transaction([l,f],"readonly");u.oncomplete=function(){"keys"in e&&(e.keys.forEach(function(e){e in v?p.push(v[e]):p.push({key:e,error:"not_found"})}),e.descending&&p.reverse()),o.call(t,null,{total_rows:p.length,offset:e.skip,rows:"limit"in e?p.slice(e.skip,e.limit+e.skip):e.skip>0?p.slice(e.skip):p})};var c=u.objectStore(l),d=a?c.openCursor(s,a):c.openCursor(s),p=[],v={};d.onsuccess=function(t){function n(t,n){if(o.isLocalId(t.id))return r["continue"]();var a={id:t.id,key:t.id,value:{rev:i.winningRev(t)}};if(e.include_docs){a.doc=n,a.doc._rev=i.winningRev(t),a.doc._doc_id_rev&&delete a.doc._doc_id_rev,e.conflicts&&(a.doc._conflicts=i.collectConflicts(t));for(var s in a.doc._attachments)a.doc._attachments[s].stub=!0}"keys"in e?e.keys.indexOf(t.id)>-1&&(o.isDeleted(t)&&(a.value.deleted=!0,a.doc=null),v[a.id]=a):o.isDeleted(t)||p.push(a),r["continue"]()}if(t.target.result){var r=t.target.result,a=r.value;if(e.include_docs){var s=u.objectStore(f).index("_doc_id_rev"),c=i.winningRev(a),d=a.id+"::"+c;s.get(d).onsuccess=function(e){n(r.value,e.target.result)}}else n(a)}}},b._info=function(e){function t(e){o=e.target.result&&e.target.result.updateSeq||0}function n(e){var n=e.target.result;return n?(n.value.deleted!==!0&&r++,n["continue"](),void 0):(i.objectStore(v).get(v).onsuccess=t,void 0)}var r=0,o=0,i=k.transaction([l,v],"readonly");i.oncomplete=function(){e(null,{db_name:m,doc_count:r,update_seq:o})},i.objectStore(l).openCursor().onsuccess=n},b._changes=function(e){function t(){p=k.transaction([l,f]),p.oncomplete=a;var t;t=c?p.objectStore(f).openCursor(window.IDBKeyRange.lowerBound(e.since,!0),c):p.objectStore(f).openCursor(window.IDBKeyRange.lowerBound(e.since,!0)),t.onsuccess=n,t.onerror=s}function n(t){if(!t.target.result){for(var n=0,r=v.length;r>n;n++){var a=v[n];a&&_.push(a)}return!1}var s=t.target.result,u=s.value._id,c=h[u];if(void 0!==c)return v[c].seq=s.key,v.push(v[c]),v[c]=null,h[u]=v.length-1,s["continue"]();var m=p.objectStore(l);m.get(s.value._id).onsuccess=function(t){var n=t.target.result;if(o.isLocalId(n.id))return s["continue"]();d= "'+u+'"'),c&&(p+=(u?" AND ":" WHERE ")+d+'.id <= "'+c+'"'),p+=" ORDER BY "+d+".id "+(f?"DESC":"ASC")),b.transaction(function(t){t.executeSql(p,[],function(t,n){for(var r=0,u=n.rows.length;u>r;r++){var c=n.rows.item(r),d=JSON.parse(c.metadata),l=JSON.parse(c.data);if(!i.isLocalId(d.id)){if(c={id:d.id,key:d.id,value:{rev:a.winningRev(d)}},e.include_docs){c.doc=l,c.doc._rev=a.winningRev(d),e.conflicts&&(c.doc._conflicts=a.collectConflicts(d));for(var f in c.doc._attachments)c.doc._attachments[f].stub=!0}"keys"in e?e.keys.indexOf(d.id)>-1&&(i.isDeleted(d)&&(c.value.deleted=!0,c.doc=null),s[c.id]=c):i.isDeleted(d)||o.push(c)}}})},r(t),function(){"keys"in e&&(e.keys.forEach(function(e){e in s?o.push(s[e]):o.push({key:e,error:"not_found"})}),e.descending&&o.reverse()),i.call(t,null,{total_rows:o.length,offset:e.skip,rows:"limit"in e?o.slice(e.skip,e.limit+e.skip):e.skip>0?o.slice(e.skip):o})})},_._changes=function(e){function t(){var t="SELECT "+d+".id, "+l+".seq, "+l+".json AS data, "+d+".json AS metadata FROM "+l+" JOIN "+d+" ON "+l+".seq = "+d+".winningseq WHERE "+d+".seq > "+e.since+" ORDER BY "+d+".seq "+(r?"DESC":"ASC");b.transaction(function(n){n.executeSql(t,[],function(t,n){for(var r=0,o=0,u=n.rows.length;u>o;o++){var c=n.rows.item(o),d=JSON.parse(c.metadata);if(!i.isLocalId(d.id)){r=200&&p.status<300){var r;r=e.binary?i([p.response||""],{type:p.getResponseHeader("Content-Type")}):p.responseText,n(a,r,p,t)}else n(s,p,t)},e.timeout>0&&(l=setTimeout(c,e.timeout)),p.send(e.body),{abort:c}}return e.json&&(e.binary||(e.headers.Accept="application/json"),e.headers["Content-Type"]=e.headers["Content-Type"]||"application/json"),e.binary&&(e.encoding=null,e.json=!1),e.processData||(e.json=!1),r(e,function(r,o,i){if(r)return r.status=o?o.statusCode:400,n(s,r,t);var u=o.headers["content-type"],c=i||"";e.binary||!e.json&&e.processData||"object"==typeof c||!(/json/.test(u)||/^[\s]*\{/.test(c)&&/\}[\s]*$/.test(c))||(c=JSON.parse(c)),o.statusCode>=200&&o.statusCode<300?n(a,c,o,t):(e.binary&&(c=JSON.parse(c.toString())),c.status=o.statusCode,n(t,c))})}var r=e("request"),o=e("./extend.js"),i=e("./blob.js");t.exports=n},{"./blob.js":6,"./extend.js":8,request:16}],6:[function(e,t){function n(e,t){e=e||[],t=t||{};try{return new Blob(e,t)}catch(n){if("TypeError"!==n.name)throw n;for(var r=window.BlobBuilder||window.MSBlobBuilder||window.MozBlobBuilder||window.WebKitBlobBuilder,o=new r,i=0;id;d++)if(null!=(e=arguments[d]))for(t in e)n=c[t],r=e[t],c!==r&&(f&&r&&(o(r)||(s=p(r)))?(s?(s=!1,u=n&&p(n)?n:[]):u=n&&o(n)?n:{},c[t]=a(f,u,r)):void 0!==r&&(p(e)&&i(r)||(c[t]=r)));return c}for(var s={},u=["Boolean","Number","String","Function","Array","Date","RegExp","Object","Error"],c=0;c>>32-t}function n(e,t){var n,r,o,i,a;return o=2147483648&e,i=2147483648&t,n=1073741824&e,r=1073741824&t,a=(1073741823&e)+(1073741823&t),n&r?2147483648^a^o^i:n|r?1073741824&a?3221225472^a^o^i:1073741824^a^o^i:a^o^i}function i(e,t,n){return e&t|~e&n}function a(e,t,n){return e&n|t&~n}function s(e,t,n){return e^t^n}function u(e,t,n){return t^(e|~n)}function c(e,r,o,a,s,u,c){return e=n(e,n(n(i(r,o,a),s),c)),n(t(e,u),r)}function d(e,r,o,i,s,u,c){return e=n(e,n(n(a(r,o,i),s),c)),n(t(e,u),r)}function l(e,r,o,i,a,u,c){return e=n(e,n(n(s(r,o,i),a),c)),n(t(e,u),r)}function f(e,r,o,i,a,s,c){return e=n(e,n(n(u(r,o,i),a),c)),n(t(e,s),r)}function p(e){for(var t,n=e.length,r=n+8,o=(r-r%64)/64,i=16*(o+1),a=Array(i-1),s=0,u=0;n>u;)t=(u-u%4)/4,s=u%4*8,a[t]=a[t]|e.charCodeAt(u)<>>29,a}function v(e){var t,n,r="",o="";for(n=0;3>=n;n++)t=e>>>8*n&255,o="0"+t.toString(16),r+=o.substr(o.length-2,2);return r}if(!r.browser)return o.createHash("md5").update(e).digest("hex");var h,m,_,g,y,b,k,w,q,S=Array(),E=7,O=12,R=17,T=22,C=5,D=9,N=14,A=20,x=4,j=11,I=16,L=23,B=6,M=10,P=15,U=21;for(S=p(e),b=1732584193,k=4023233417,w=2562383102,q=271733878,h=0;hr;r++)i[r]=o[0|Math.random()*t];else{var a;for(i[8]=i[13]=i[18]=i[23]="-",i[14]="4",r=0;36>r;r++)i[r]||(a=0|16*Math.random(),i[r]=o[19==r?3&a|8:a])}return i.join("")}n.CHARS="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split(""),t.exports=n},{}],11:[function(e,t){function n(e,t,r){if(!(this instanceof n))return new n(e,t,r);("function"==typeof t||"undefined"==typeof t)&&(r=t,t={}),"object"==typeof e&&(t=e,e=void 0),"undefined"==typeof r&&(r=function(){});var o=n.parseAdapter(t.name||e);if(t.originalName=e,t.name=t.name||o.name,t.adapter=t.adapter||o.adapter,!n.adapters[t.adapter])throw"Adapter is missing";if(!n.adapters[t.adapter].valid())throw"Invalid Adapter";var a=new i(t,function(e,t){if(e)return r&&r(e),void 0;for(var o in n.plugins){var i=n.plugins[o](t);for(var a in i)a in t||(t[a]=i[a])}t.taskqueue.ready(!0),t.taskqueue.execute(t),r(null,t)});for(var s in a)this[s]=a[s];for(var u in n.plugins){var c=n.plugins[u](this);for(var d in c)d in this||(this[d]=c[d])}}var r=e("__browserify_process"),o=e("./utils"),i=e("./adapter")(n);n.adapters={},n.plugins={},n.prefix="_pouch_",n.parseAdapter=function(e){var t,r=e.match(/([a-z\-]*):\/\/(.*)/);if(r){if(e=/http(s?)/.test(r[1])?r[1]+"://"+r[2]:r[2],t=r[1],!n.adapters[t].valid())throw"Invalid adapter";return{name:e,adapter:r[1]}}for(var o=["idb","leveldb","websql"],i=0;i0;){var o=n.pop(),i=o.tree1,a=o.tree2;(i[1].status||a[1].status)&&(i[1].status="available"===i[1].status||"available"===a[1].status?"available":"missing");for(var s=0;s0;){var p=f.pop();0!==p.diff?p.ids&&p.ids[2].forEach(function(e,t){f.push({ids:e,diff:p.diff-1,parent:p.ids,parentIdx:t})}):p.ids[0]===c.ids[0]&&l.push(p)}var v=l[0];v?(o=r(v.ids,c.ids),v.parent[2][v.parentIdx]=o.tree,i.push({pos:u.pos,ids:u.ids}),a=a||o.conflicts,s=!0):i.push(e)}else i.push(e)}),s||i.push(t),i.sort(function(e,t){return e.pos-t.pos}),{tree:i,conflicts:a||"internal_node"}):{tree:[t],conflicts:"new_leaf"}}function i(e,t){var r=s.rootToLeaf(e).map(function(e){var r=e.ids.slice(-t);return{pos:e.pos+(e.ids.length-r.length),ids:n(r)}});return r.reduce(function(e,t){return o(e,t,!0).tree},[r.shift()])}var a=e("./deps/extend"),s={};s.merge=function(e,t,n){e=a(!0,[],e),t=a(!0,{},t);var r=o(e,t);return{tree:i(r.tree,n),conflicts:r.conflicts}},s.winningRev=function(e){var t=[];return s.traverseRevTree(e.rev_tree,function(e,n,r,o,i){e&&t.push({pos:n,id:r,deleted:!!i.deleted})}),t.sort(function(e,t){return e.deleted!==t.deleted?e.deleted>t.deleted?1:-1:e.pos!==t.pos?t.pos-e.pos:e.id0;){var r=n.pop(),o=r.pos,i=r.ids,a=t(0===i[2].length,o,i[0],r.ctx,i[1]);i[2].forEach(function(e){n.push({pos:o+1,ids:e,ctx:a})})}},s.collectLeaves=function(e){var t=[];return s.traverseRevTree(e,function(e,n,r,o,i){e&&t.unshift({rev:n+"-"+r,pos:n,opts:i})}),t.sort(function(e,t){return t.pos-e.pos}),t.map(function(e){delete e.pos}),t},s.collectConflicts=function(e){var t=s.winningRev(e),n=s.collectLeaves(e.rev_tree),r=[];return n.forEach(function(e){e.rev===t||e.opts.deleted||r.push(e.rev)}),r},s.rootToLeaf=function(e){var t=[];return s.traverseRevTree(e,function(e,n,r,o,i){if(o=o?o.slice(0):[],o.push({id:r,opts:i}),e){var a=n+1-o.length;t.unshift({pos:a,ids:o})}return o}),t},t.exports=s},{"./deps/extend":8}],13:[function(e,t,n){"use strict";function r(){var e=this;this.cancelled=!1,this.cancel=function(){e.cancelled=!0}}function o(e){var t=[],n={},r=!1;return n.enqueue=function(e,o){t.push({fun:e,args:o}),r||n.process()},n.process=function(){if(!r&&t.length&&!e.cancelled){r=!0;var n=t.shift();n.fun.apply(null,n.args)}},n.notifyRequestComplete=function(){r=!1,n.process()},n}function i(e,t,n){var r=n.filter?n.filter.toString():"";return"_local/"+d.Crypto.MD5(e.id()+t.id()+r)}function a(e,t,n,r){t.get(n,function(t,o){t&&404===t.status?r(null,0):e.get(n,function(e,t){e&&404===e.status||o.last_seq!==t.last_seq?r(null,0):r(null,t.last_seq)})})}function s(e,t,n,r,o){function i(e,t){e.get(n,function(o,i){o&&404===o.status&&(i={_id:n}),i.last_seq=r,e.put(i,t)})}i(t,function(){i(e,function(){o()})})}function u(e,t,n,r){function u(r,o,i){if(n.onChange)for(var a=0;i>a;a++)n.onChange.apply(this,[O]);w-=i,O.docs_written+=i,s(e,t,y,q,function(){_.notifyRequestComplete(),m()})}function c(){if(!g.length)return _.notifyRequestComplete();var e=g.length;t.bulkDocs({docs:g},{new_edits:!1},function(t,n){u(t,n,e)}),g=[]}function l(t,n){e.get(t,{revs:!0,rev:n,attachments:!0},function(e,t){O.docs_read++,_.notifyRequestComplete(),g.push(t),_.enqueue(c)})}function f(e){return function(t,o){if(_.notifyRequestComplete(),t)return S&&r.cancel(),d.call(n.complete,t,null),void 0;if(0===Object.keys(o).length){for(var i in e)w-=e[i];return m(),void 0}var a=function(e){_.enqueue(l,[s,e])};for(var s in o){var u=e[s]-o[s].missing.length;w-=u,o[s].missing.forEach(a)}}}function p(e,n){t.revsDiff(e,f(n))}function v(e){q=e.seq,b.push(e);var t={};t[e.id]=e.changes.map(function(e){return e.rev});var n={};n[e.id]=e.changes.length,w+=e.changes.length,_.enqueue(p,[t,n])}function h(){k=!0,m()}function m(){k&&0===w&&(O.end_time=new Date,d.call(n.complete,null,O))}var _=new o(r),g=[],y=i(e,t,n),b=[],k=!1,w=0,q=0,S=n.continuous||!1,E=n.doc_ids,O={ok:!0,start_time:new Date,docs_read:0,docs_written:0};a(e,t,y,function(t,o){if(t)return d.call(n.complete,t);if(q=o,!r.cancelled){var i={continuous:S,since:q,style:"all_docs",onChange:v,complete:h,doc_ids:E};n.filter&&(i.filter=n.filter),n.query_params&&(i.query_params=n.query_params);var a=e.changes(i);if(n.continuous){var s=r.cancel;r.cancel=function(){s(),a.cancel()}}}})}function c(e,t){return"string"==typeof e?new l(e,t):(t(null,e),void 0)}var d=e("./utils"),l=e("./index");n.replicate=function(e,t,n,o){n instanceof Function&&(o=n,n={}),void 0===n&&(n={}),n.complete||(n.complete=o);var i=new r;return c(e,function(e,r){return e?d.call(o,e):(c(t,function(e,t){if(e)return d.call(o,e);if(n.server){if("function"!=typeof r.replicateOnServer)return d.call(o,{error:"Server replication not supported for "+r.type()+" adapter"});if(r.type()!==t.type())return d.call(o,{error:"Server replication for different adapter types ("+r.type()+" and "+t.type()+") is not supported"});r.replicateOnServer(t,n,i)}else u(r,t,n,i)}),void 0)}),i}},{"./index":11,"./utils":14}],14:[function(e,t,n){function r(e){return/^_/.test(e)?/^_(design|local)/.test(e):!0}function o(){return"undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage&&"undefined"!=typeof chrome.storage.local}var i=e("./merge");n.extend=e("./deps/extend"),n.ajax=e("./deps/ajax"),n.createBlob=e("./deps/blob");var a=e("./deps/uuid");n.Crypto=e("./deps/md5.js");var s=e("./deps/buffer"),u=e("./deps/errors");n.error=function(e,t){return n.extend({},e,{reason:t})};var c=["_id","_rev","_attachments","_deleted","_revisions","_revs_info","_conflicts","_deleted_conflicts","_local_seq","_rev_tree"];n.uuids=function(e,t){"object"!=typeof t&&(t={});for(var n=t.length,r=t.radix,o=[];o.push(a(n,r))=0&&(t=t.split("-")[1]);var n=!1;return i.traverseRevTree(e.rev_tree,function(e,r,o,i,a){o===t&&(n=!!a.deleted)}),n},n.filterChange=function(e){return function(t){var n={},r=e.filter&&"function"==typeof e.filter;if(n.query=e.query_params,e.filter&&r&&!e.filter.call(this,t.doc,n))return!1;if(e.doc_ids&&-1===e.doc_ids.indexOf(t.id))return!1;if(e.include_docs)for(var o in t.doc._attachments)t.doc._attachments[o].stub=!0;else delete t.doc;return!0}},n.processChanges=function(e,t,r){t=t.filter(n.filterChange(e)),e.limit&&e.limito.since&&!o.cancelled&&(o.since=e.seq,n.call(o.onChange,e))}})})},e},n.atob="undefined"!=typeof window&&"atob"in window?function(e){return atob(e)}:function(e){var t=new s(e,"base64");if(t.toString("base64")!==e)throw"Cannot base64 encode full string";return t.toString("binary")},n.btoa="undefined"!=typeof window&&"btoa"in window?function(e){return btoa(e)}:function(e){return new s(e,"binary").toString("base64")},t.exports=n},{"./deps/ajax":5,"./deps/blob":6,"./deps/buffer":16,"./deps/errors":7,"./deps/extend":8,"./deps/md5.js":9,"./deps/uuid":10,"./merge":12}],15:[function(e,t){t.exports="1.0.0"},{}],16:[function(){},{}],17:[function(e,t){var n=t.exports={};n.nextTick=function(){var e="undefined"!=typeof window&&window.setImmediate,t="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(e)return function(e){return window.setImmediate(e)};if(t){var n=[];return window.addEventListener("message",function(e){if(e.source===window&&"process-tick"===e.data&&(e.stopPropagation(),n.length>0)){var t=n.shift();t()}},!0),function(e){n.push(e),window.postMessage("process-tick","*")}}return function(e){setTimeout(e,0)}}(),n.title="browser",n.browser=!0,n.env={},n.argv=[],n.binding=function(){throw new Error("process.binding is not supported")},n.cwd=function(){return"/"},n.chdir=function(){throw new Error("process.chdir is not supported")}},{}],18:[function(require,module,exports){"use strict";function MapReduce(db){function viewQuery(fun,options){function sum(e){return e.reduce(function(e,t){return e+t
+},0)}function emit(e,t){var n={id:current.doc._id,key:e,value:t};if(!(options.startkey&&pouchCollate(e,options.startkey)<0||options.endkey&&pouchCollate(e,options.endkey)>0||options.key&&0!==pouchCollate(e,options.key))){if(num_started++,options.include_docs){if(t&&"object"==typeof t&&t._id)return db.get(t._id,function(e,t){t&&(n.doc=t),results.push(n),checkComplete()}),void 0;n.doc=current.doc}results.push(n)}}function checkComplete(){if(completed&&results.length==num_started){if(results.sort(function(e,t){return pouchCollate(e.key,t.key)}),options.descending&&results.reverse(),options.reduce===!1)return options.complete(null,{total_rows:results.length,offset:options.skip,rows:"limit"in options?results.slice(options.skip,options.limit+options.skip):options.skip>0?results.slice(options.skip):results});var e=[];results.forEach(function(t){var n=e[e.length-1]||null;return n&&0===pouchCollate(n.key[0][0],t.key)?(n.key.push([t.key,t.id]),n.value.push(t.value),void 0):(e.push({key:[[t.key,t.id]],value:[t.value]}),void 0)}),e.forEach(function(e){e.value=fun.reduce(e.key,e.value),e.value="undefined"==typeof e.value?null:e.value,e.key=e.key[0][0]}),options.complete(null,{total_rows:e.length,offset:options.skip,rows:"limit"in options?e.slice(options.skip,options.limit+options.skip):options.skip>0?e.slice(options.skip):e})}}if(options.complete){options.skip||(options.skip=0),fun.reduce||(options.reduce=!1);var builtInReduce={_sum:function(e,t){return sum(t)},_count:function(e,t,n){return n?sum(t):t.length},_stats:function(e,t){return{sum:sum(t),min:Math.min.apply(null,t),max:Math.max.apply(null,t),count:t.length,sumsqr:function(){var e=0;for(var n in t)"number"==typeof t[n]&&(e+=t[n]*t[n]);return e}()}}},results=[],current=null,num_started=0,completed=!1;eval("fun.map = "+fun.map.toString()+";"),fun.reduce&&(builtInReduce[fun.reduce]&&(fun.reduce=builtInReduce[fun.reduce]),eval("fun.reduce = "+fun.reduce.toString()+";")),db.changes({conflicts:!0,include_docs:!0,onChange:function(e){"deleted"in e||(current={doc:e.doc},fun.map.call(this,e.doc))},complete:function(){completed=!0,checkComplete()}})}}function httpQuery(e,t,n){var r=[],o=void 0,i="GET";if("undefined"!=typeof t.reduce&&r.push("reduce="+t.reduce),"undefined"!=typeof t.include_docs&&r.push("include_docs="+t.include_docs),"undefined"!=typeof t.limit&&r.push("limit="+t.limit),"undefined"!=typeof t.descending&&r.push("descending="+t.descending),"undefined"!=typeof t.startkey&&r.push("startkey="+encodeURIComponent(JSON.stringify(t.startkey))),"undefined"!=typeof t.endkey&&r.push("endkey="+encodeURIComponent(JSON.stringify(t.endkey))),"undefined"!=typeof t.key&&r.push("key="+encodeURIComponent(JSON.stringify(t.key))),"undefined"!=typeof t.group&&r.push("group="+t.group),"undefined"!=typeof t.group_level&&r.push("group_level="+t.group_level),"undefined"!=typeof t.skip&&r.push("skip="+t.skip),"undefined"!=typeof t.keys&&(i="POST",o=JSON.stringify({keys:t.keys})),r=r.join("&"),r=""===r?"":"?"+r,"string"==typeof e){var a=e.split("/");return db.request({method:i,url:"_design/"+a[0]+"/_view/"+a[1]+r,body:o},n),void 0}var s=JSON.parse(JSON.stringify(e,function(e,t){return"function"==typeof t?t+"":t}));db.request({method:"POST",url:"_temp_view"+r,body:s},n)}return this instanceof MapReduce?(this.query=function(e,t,n){if("function"==typeof t&&(n=t,t={}),n&&(t.complete=n),"http"===db.type())return"function"==typeof e?httpQuery({map:e},t,n):httpQuery(e,t,n);if("object"==typeof e)return viewQuery(e,t);if("function"==typeof e)return viewQuery({map:e},t);var r=e.split("/");db.get("_design/"+r[0],function(e,o){return e?(n&&n(e),void 0):o.views[r[1]]?(viewQuery({map:o.views[r[1]].map,reduce:o.views[r[1]].reduce},t),void 0):(n&&n({error:"not_found",reason:"missing_named_view"}),void 0)})},void 0):new MapReduce(db)}var pouchCollate=require("pouchdb-collate");MapReduce._delete=function(){},module.exports=MapReduce},{"pouchdb-collate":19}],19:[function(e,t){"use strict";function n(e,t){for(var n=Math.min(e.length,t.length),r=0;n>r;r++){var o=a(e[r],t[r]);if(0!==o)return o}return e.length===t.length?0:e.length>t.length?1:-1}function r(e,t){return e===t?0:e>t?1:-1}function o(e,t){for(var n=Object.keys(e),r=Object.keys(t),o=Math.min(n.length,r.length),i=0;o>i;i++){var s=a(n[i],r[i]);if(0!==s)return s;if(s=a(e[n[i]],t[r[i]]),0!==s)return s}return n.length===r.length?0:n.length>r.length?1:-1}function i(e){var t=["boolean","number","string","object"];return-1!==t.indexOf(typeof e)?null===e?1:t.indexOf(typeof e)+2:Array.isArray(e)?4.5:void 0}function a(e,t){var a=i(e),s=i(t);return a-s!==0?a-s:null===e?0:"number"==typeof e?e-t:"boolean"==typeof e?t>e?-1:1:"string"==typeof e?r(e,t):Array.isArray(e)?n(e,t):"object"==typeof e?o(e,t):void 0}t.exports=a},{}]},{},[11])(11)});
\ No newline at end of file
diff --git a/examples/todos/public/styles/base.css b/examples/todos/public/styles/base.css
new file mode 100644
index 00000000..4a05fc23
--- /dev/null
+++ b/examples/todos/public/styles/base.css
@@ -0,0 +1,424 @@
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+button {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ font-size: 100%;
+ vertical-align: baseline;
+ font-family: inherit;
+ color: inherit;
+ -webkit-appearance: none;
+ /*-moz-appearance: none;*/
+ -ms-appearance: none;
+ -o-appearance: none;
+ appearance: none;
+}
+
+body {
+ font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ line-height: 1.4em;
+ background: #eaeaea url('../images/bg.png');
+ color: #4d4d4d;
+ width: 550px;
+ margin: 0 auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-font-smoothing: antialiased;
+ -ms-font-smoothing: antialiased;
+ -o-font-smoothing: antialiased;
+ font-smoothing: antialiased;
+}
+
+#todoapp {
+ background: #fff;
+ background: rgba(255, 255, 255, 0.9);
+ margin: 130px 0 40px 0;
+ border: 1px solid #ccc;
+ position: relative;
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
+ 0 25px 50px 0 rgba(0, 0, 0, 0.15);
+}
+
+#todoapp:before {
+ content: '';
+ border-left: 1px solid #f5d6d6;
+ border-right: 1px solid #f5d6d6;
+ width: 2px;
+ position: absolute;
+ top: 0;
+ left: 40px;
+ height: 100%;
+}
+
+#todoapp input::-webkit-input-placeholder {
+ font-style: italic;
+}
+
+#todoapp input:-moz-placeholder {
+ font-style: italic;
+ color: #a9a9a9;
+}
+
+#todoapp h1 {
+ position: absolute;
+ top: -120px;
+ width: 100%;
+ font-size: 70px;
+ font-weight: bold;
+ text-align: center;
+ color: #b3b3b3;
+ color: rgba(255, 255, 255, 0.3);
+ text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
+ -webkit-text-rendering: optimizeLegibility;
+ -moz-text-rendering: optimizeLegibility;
+ -ms-text-rendering: optimizeLegibility;
+ -o-text-rendering: optimizeLegibility;
+ text-rendering: optimizeLegibility;
+}
+
+#header {
+ padding-top: 15px;
+ border-radius: inherit;
+}
+
+#header:before {
+ content: '';
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ height: 15px;
+ z-index: 2;
+ border-bottom: 1px solid #6c615c;
+ background: #8d7d77;
+ background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
+ background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
+ background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
+ background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
+ background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
+ background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
+ filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
+ border-top-left-radius: 1px;
+ border-top-right-radius: 1px;
+}
+
+#new-todo,
+.edit {
+ position: relative;
+ margin: 0;
+ width: 100%;
+ font-size: 24px;
+ font-family: inherit;
+ line-height: 1.4em;
+ border: 0;
+ outline: none;
+ color: inherit;
+ padding: 6px;
+ border: 1px solid #999;
+ box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ -o-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-font-smoothing: antialiased;
+ -moz-font-smoothing: antialiased;
+ -ms-font-smoothing: antialiased;
+ -o-font-smoothing: antialiased;
+ font-smoothing: antialiased;
+}
+
+#new-todo {
+ padding: 16px 16px 16px 60px;
+ border: none;
+ background: rgba(0, 0, 0, 0.02);
+ z-index: 2;
+ box-shadow: none;
+}
+
+#main {
+ position: relative;
+ z-index: 2;
+ border-top: 1px dotted #adadad;
+}
+
+label[for='toggle-all'] {
+ display: none;
+}
+
+#toggle-all {
+ position: absolute;
+ top: -42px;
+ left: -4px;
+ width: 40px;
+ text-align: center;
+ border: none; /* Mobile Safari */
+}
+
+#toggle-all:before {
+ content: '»';
+ font-size: 28px;
+ color: #d9d9d9;
+ padding: 0 25px 7px;
+}
+
+#toggle-all:checked:before {
+ color: #737373;
+}
+
+#todo-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#todo-list li {
+ position: relative;
+ font-size: 24px;
+ border-bottom: 1px dotted #ccc;
+}
+
+#todo-list li:last-child {
+ border-bottom: none;
+}
+
+#todo-list li.editing {
+ border-bottom: none;
+ padding: 0;
+}
+
+#todo-list li.editing .edit {
+ display: block;
+ width: 506px;
+ padding: 13px 17px 12px 17px;
+ margin: 0 0 0 43px;
+}
+
+#todo-list li.editing .view {
+ display: none;
+}
+
+#todo-list li .toggle {
+ text-align: center;
+ width: 40px;
+ /* auto, since non-WebKit browsers doesn't support input styling */
+ height: auto;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ margin: auto 0;
+ border: none; /* Mobile Safari */
+ -webkit-appearance: none;
+ /*-moz-appearance: none;*/
+ -ms-appearance: none;
+ -o-appearance: none;
+ appearance: none;
+}
+
+#todo-list li .toggle:after {
+ content: '✔';
+ line-height: 43px; /* 40 + a couple of pixels visual adjustment */
+ font-size: 20px;
+ color: #d9d9d9;
+ text-shadow: 0 -1px 0 #bfbfbf;
+}
+
+#todo-list li .toggle:checked:after {
+ color: #85ada7;
+ text-shadow: 0 1px 0 #669991;
+ bottom: 1px;
+ position: relative;
+}
+
+#todo-list li label {
+ word-break: break-word;
+ padding: 15px;
+ margin-left: 45px;
+ display: block;
+ line-height: 1.2;
+ -webkit-transition: color 0.4s;
+ -moz-transition: color 0.4s;
+ -ms-transition: color 0.4s;
+ -o-transition: color 0.4s;
+ transition: color 0.4s;
+}
+
+#todo-list li.completed label {
+ color: #a9a9a9;
+ text-decoration: line-through;
+}
+
+#todo-list li .destroy {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 10px;
+ bottom: 0;
+ width: 40px;
+ height: 40px;
+ margin: auto 0;
+ font-size: 22px;
+ color: #a88a8a;
+ -webkit-transition: all 0.2s;
+ -moz-transition: all 0.2s;
+ -ms-transition: all 0.2s;
+ -o-transition: all 0.2s;
+ transition: all 0.2s;
+}
+
+#todo-list li .destroy:hover {
+ text-shadow: 0 0 1px #000, 0 0 10px rgba(199, 107, 107, 0.8);
+ -webkit-transform: scale(1.3);
+ -moz-transform: scale(1.3);
+ -ms-transform: scale(1.3);
+ -o-transform: scale(1.3);
+ transform: scale(1.3);
+}
+
+#todo-list li .destroy:after {
+ content: '✖';
+}
+
+#todo-list li:hover .destroy {
+ display: block;
+}
+
+#todo-list li .edit {
+ display: none;
+}
+
+#todo-list li.editing:last-child {
+ margin-bottom: -1px;
+}
+
+#footer {
+ color: #777;
+ padding: 0 15px;
+ position: absolute;
+ right: 0;
+ bottom: -31px;
+ left: 0;
+ height: 20px;
+ z-index: 1;
+}
+
+#footer:before {
+ content: '';
+ position: absolute;
+ right: 0;
+ bottom: 31px;
+ left: 0;
+ height: 50px;
+ z-index: -1;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
+ 0 6px 0 -3px rgba(255, 255, 255, 0.8),
+ 0 7px 1px -3px rgba(0, 0, 0, 0.3),
+ 0 43px 0 -6px rgba(255, 255, 255, 0.8),
+ 0 44px 2px -6px rgba(0, 0, 0, 0.2);
+}
+
+#todo-count {
+ float: left;
+ text-align: left;
+}
+
+#filters {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ position: absolute;
+ right: 0;
+ left: 0;
+}
+
+#filters li {
+ display: inline;
+}
+
+#filters li a {
+ color: #83756f;
+ margin: 2px;
+ text-decoration: none;
+}
+
+#filters li a.selected {
+ font-weight: bold;
+}
+
+#clear-completed {
+ float: right;
+ position: relative;
+ line-height: 20px;
+ text-decoration: none;
+ background: rgba(0, 0, 0, 0.1);
+ font-size: 11px;
+ padding: 0 10px;
+ border-radius: 3px;
+ box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
+}
+
+#clear-completed:hover {
+ background: rgba(0, 0, 0, 0.15);
+ box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
+}
+
+#info {
+ margin: 65px auto 0;
+ color: #a6a6a6;
+ font-size: 12px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
+ text-align: center;
+}
+
+#info a {
+ color: inherit;
+}
+
+/*
+ Hack to remove background from Mobile Safari.
+ Can't use it globally since it destroys checkboxes in Firefox and Opera
+*/
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+ #toggle-all,
+ #todo-list li .toggle {
+ background: none;
+ }
+
+ #todo-list li .toggle {
+ height: 40px;
+ }
+
+ #toggle-all {
+ top: -56px;
+ left: -15px;
+ width: 65px;
+ height: 41px;
+ -webkit-transform: rotate(90deg);
+ transform: rotate(90deg);
+ -webkit-appearance: none;
+ appearance: none;
+ }
+}
+
+.hidden{
+ display:none;
+}
+
+#sync-error, #sync-success {
+ display: none;
+}
+
+[data-sync-state=syncing] #sync-success {
+ display: block;
+}
+
+[data-sync-state=error] #sync-error {
+ display: block;
+}
diff --git a/package.json b/package.json
index ae277816..83f01c48 100644
--- a/package.json
+++ b/package.json
@@ -1,63 +1,84 @@
{
- "name": "pouchdb-server",
- "description": "A standalone REST interface server for PouchDB.",
- "version": "2.0.2",
- "homepage": "https://github.com/pouchdb/pouchdb-server",
- "license": "Apache-2.0",
- "author": {
- "name": "Nick Thompson",
- "email": "ncthom91@gmail.com"
- },
- "repository": {
- "type": "git",
- "url": "git://github.com/pouchdb/pouchdb-server.git"
- },
- "bin": {
- "pouchdb-server": "./bin/pouchdb-server"
- },
+ "name": "pouchdb-server-monorepo",
+ "private": true,
+ "version": "2.3.0",
"scripts": {
- "start": "node ./bin/pouchdb-server",
+ "unit-tests": "mocha tests/**/*",
"test-pouchdb": "./bin/test-setup.sh && ./bin/test-pouchdb.sh",
"test-couchdb": "./bin/test-setup.sh && ./bin/test-couchdb.sh",
- "eslint": "eslint bin/ lib/"
- },
- "engines": {
- "node": ">= 0.8.0"
+ "eslint": "eslint bin/ packages/node_modules/**/src tests/",
+ "test-express-minimum": "./bin/test-setup.sh && ./bin/test-express-minimum.sh",
+ "release": "node ./bin/prerelease.js && ./bin/release.sh"
},
"dependencies": {
+ "basic-auth": "^1.1.0",
+ "body-parser": "^1.16.1",
+ "builtin-modules": "^1.1.1",
"colors": "^1.0.3",
+ "compression": "^1.6.2",
+ "cookie-parser": "^1.4.3",
"corser": "~2.0.0",
"couchdb-harness": "*",
"couchdb-log-parse": "^0.0.4",
+ "denodeify": "^1.2.1",
"express": "^4.10.4",
- "express-pouchdb": "^2.1.3",
+ "extend": "^3.0.0",
+ "find-requires": "^0.2.2",
+ "glob": "^7.1.1",
+ "header-case-normalizer": "^1.0.3",
"http-pouchdb": "^2.1.2",
"killable": "^1.0.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.uniq": "^4.5.0",
"mkdirp": "^0.5.0",
+ "multiparty": "^4.1.3",
"nomnom": "^1.8.1",
"object-assign": "^4.1.0",
+ "on-finished": "^2.3.0",
"pouchdb-adapter-leveldb-core": "^6.1.0",
"pouchdb-adapter-memory": "^6.1.0",
+ "pouchdb-all-dbs": "^1.0.2",
+ "pouchdb-auth": "^2.1.1",
+ "pouchdb-collections": "^6.1.2",
"pouchdb-core": "^6.1.0",
+ "pouchdb-fauxton": "0.0.2",
"pouchdb-find": "^0.10.4",
+ "pouchdb-list": "^1.1.0",
"pouchdb-mapreduce": "^6.1.0",
"pouchdb-promise": "6.1.2",
"pouchdb-replication": "^6.1.0",
+ "pouchdb-replicator": "^2.1.3",
+ "pouchdb-rewrite": "^1.0.7",
+ "pouchdb-security": "^1.2.6",
+ "pouchdb-show": "^1.0.8",
+ "pouchdb-size": "^1.2.2",
+ "pouchdb-update": "^1.0.8",
+ "pouchdb-validation": "^1.2.1",
+ "pouchdb-vhost": "^1.0.2",
+ "pouchdb-wrappers": "^1.3.6",
+ "raw-body": "^2.2.0",
+ "sanitize-filename": "^1.6.1",
"serve-favicon": "~2.3.2",
"tail": "^1.2.1",
+ "uuid": "^3.0.1",
"wordwrap": "1.0.0"
},
"devDependencies": {
+ "bluebird": "^3.4.7",
+ "builtin-modules": "^1.1.1",
"eslint": "3.15.0",
- "memdown": "^1.2.4"
+ "express": "^4.14.1",
+ "find-requires": "^0.2.2",
+ "fs-extra": "^2.0.0",
+ "glob": "^7.1.1",
+ "lodash.flatten": "^4.4.0",
+ "lodash.uniq": "^4.5.0",
+ "memdown": "^1.2.4",
+ "mocha": "^3.2.0",
+ "supertest": "^3.0.0"
},
"optionalDependencies": {
"pouchdb-adapter-node-websql": "^6.1.0",
"pouchdb-adapter-leveldb": "^6.1.0"
- },
- "files": [
- "lib",
- "bin/pouchdb-server",
- "favicon.ico"
- ]
+ }
}
diff --git a/packages/node_modules/express-pouchdb/CONTRIBUTING.md b/packages/node_modules/express-pouchdb/CONTRIBUTING.md
new file mode 100644
index 00000000..ed42c984
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/CONTRIBUTING.md
@@ -0,0 +1,30 @@
+# Contributing
+
+Want to help me make this thing awesome? Great! Here's how you should get started.
+
+1. Because PouchDB is still developing so rapidly, you'll want to clone `git@github.com:pouchdb/pouchdb.git`, and run `npm link` from the root folder of your clone.
+2. Fork **express-pouchdb**, and clone it to your local machine.
+3. From the root folder of your clone run `npm link pouchdb` to install PouchDB from your local repository from Step 1.
+4. `npm install`
+
+Please make your changes on a separate branch whose name reflects your changes, push them to your fork, and open a pull request!
+
+For commit message style guidelines, please refer to [PouchDB CONTRIBUTING.md](https://github.com/pouchdb/pouchdb/blob/master/CONTRIBUTING.md).
+
+## Fauxton
+
+The custom Fauxton theme, with the PouchDB Server name and logo, are kept [in a Fauxton fork](https://github.com/nolanlawson/couchdb-fauxton) for the time being.
+
+## Testing
+
+To test for regressions, the following comes in handy:
+- the PouchDB test suite: ``npm run test-pouchdb``
+- the jshint command: ``npm run jshint``
+- the express-pouchdb test suite (for express-pouchdb specific things like its API only!): ``npm run test-express-pouchdb``
+
+``npm test`` combines these three.
+
+There is also the possibility to run express-pouchdb against a part of
+the CouchDB test suite. For that, try: ``npm run test-couchdb``. If it
+doesn't work, try using [couchdb-harness](https://github.com/nick-thompson/couchdb-harness),
+which that command is based on, directly.
diff --git a/packages/node_modules/express-pouchdb/LICENSE b/packages/node_modules/express-pouchdb/LICENSE
new file mode 100644
index 00000000..f6cd2bc8
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/node_modules/express-pouchdb/README.md b/packages/node_modules/express-pouchdb/README.md
new file mode 100644
index 00000000..3e5f2a89
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/README.md
@@ -0,0 +1,15 @@
+# express-pouchdb
+
+[![Build Status](https://travis-ci.org/pouchdb/pouchdb-server.svg)](https://travis-ci.org/pouchdb/pouchdb-server)
+
+An Express submodule with a CouchDB-style REST interface to PouchDB.
+
+### Source
+
+PouchDB Server and its sub-packages are distributed as a [monorepo](https://github.com/babel/babel/blob/master/doc/design/monorepo.md).
+
+For a full list of packages, see [the GitHub source](https://github.com/pouchdb/pouchdb-server/tree/master/packages/node_modules).
+
+## License
+
+The Apache 2 License. See [the LICENSE file](https://github.com/pouchdb/pouchdb-server/blob/master/LICENSE) for more information.
\ No newline at end of file
diff --git a/packages/node_modules/express-pouchdb/lib/clean-filename.js b/packages/node_modules/express-pouchdb/lib/clean-filename.js
new file mode 100644
index 00000000..8a216a85
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/clean-filename.js
@@ -0,0 +1,12 @@
+'use strict';
+
+var sanitize = require('sanitize-filename');
+
+function cleanFilename(name) {
+ // some windows reserved names like 'con' and 'prn'
+ // return an empty string here, so just wrap them in
+ // double underscores so it's at least something
+ return sanitize(name) || sanitize('__' + name + '__');
+}
+
+module.exports = cleanFilename;
\ No newline at end of file
diff --git a/packages/node_modules/express-pouchdb/lib/compression.js b/packages/node_modules/express-pouchdb/lib/compression.js
new file mode 100644
index 00000000..8b0cb351
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/compression.js
@@ -0,0 +1,4 @@
+"use strict";
+module.exports = function (app) {
+ app.use(require('compression')());
+};
diff --git a/packages/node_modules/express-pouchdb/lib/config-infrastructure.js b/packages/node_modules/express-pouchdb/lib/config-infrastructure.js
new file mode 100644
index 00000000..9e08dd85
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/config-infrastructure.js
@@ -0,0 +1,163 @@
+"use strict";
+
+var fs = require('fs'),
+ path = require('path'),
+ util = require('util'),
+ events = require('events'),
+ extend = require('extend'),
+ Auth = require('pouchdb-auth');
+
+function CouchConfig(file) {
+ events.EventEmitter.call(this);
+
+ this._file = file;
+ this._tempFile = this._file != null ?
+ path.dirname(this._file) + '/.' + path.basename(this._file) :
+ null;
+ this._config = readConfig(this._file);
+ this._defaults = {};
+
+ // Do not create an empty config file
+ if (this._file != null && fs.existsSync(this._file)) {
+ // Hashes admin passwords in 'file' (if necessary)
+ this._save();
+ }
+}
+
+util.inherits(CouchConfig, events.EventEmitter);
+
+function readConfig(file) {
+ if (file != null && fs.existsSync(file)) {
+ return JSON.parse(fs.readFileSync(file));
+ }
+ return {};
+}
+
+CouchConfig.prototype._save = function (callback) {
+ var self = this;
+
+ if (typeof callback !== "function") {
+ callback = function () {};
+ }
+
+ function write() {
+ if (self._file == null) {
+ callback();
+ return;
+ }
+
+ // Pretty print
+ var json = JSON.stringify(self._config, null, 2) + '\n';
+ fs.writeFile(self._tempFile, json, function () {
+ fs.rename(self._tempFile, self._file, callback);
+ });
+ }
+ if (self._config.admins) {
+ Auth.hashAdminPasswords(self._config.admins).then(function (admins) {
+ self._config.admins = admins;
+
+ write();
+ });
+ } else {
+ write();
+ }
+};
+
+CouchConfig.prototype.get = function (section, key) {
+ if (exists(this._config[section]) && exists(this._config[section][key])) {
+ return this._config[section][key];
+ } else {
+ // fall back on defaults
+ var sectionExists = exists(this._defaults[section]);
+ if (sectionExists && exists(this._defaults[section][key])) {
+ return this._defaults[section][key];
+ } else {
+ return undefined;
+ }
+ }
+};
+
+function exists(val) {
+ return typeof val !== 'undefined';
+}
+
+CouchConfig.prototype.getAll = function () {
+ return stringify(extend(true, {}, this._defaults, this._config));
+};
+
+function stringify(config) {
+ for (var sectionName in config) {
+ if (config.hasOwnProperty(sectionName)) {
+ stringifySection(config[sectionName]);
+ }
+ }
+ return config;
+}
+
+function stringifySection(section) {
+ for (var key in section) {
+ if (section.hasOwnProperty(key)) {
+ var value = section[key];
+ if (typeof value !== 'string') {
+ section[key] = JSON.stringify(value);
+ }
+ }
+ }
+ return section;
+}
+
+CouchConfig.prototype.getSection = function (section) {
+ var data = extend(true, {}, this._defaults[section], this._config[section]);
+ return stringifySection(data);
+};
+
+CouchConfig.prototype.set = function (section, key, value, callback) {
+ var previousValue;
+ if (!this._config[section]) {
+ this._config[section] = {};
+ } else {
+ previousValue = this._config[section][key];
+ }
+ this._config[section][key] = value;
+
+ this._changed(section, key, previousValue, callback);
+};
+
+CouchConfig.prototype._changed = function (section, key, prevVal, callback) {
+ var self = this;
+
+ self._save(function (err) {
+ if (err) {
+ return callback(err);
+ }
+
+ // run event handlers
+ self.emit(section + "." + key);
+ self.emit(section);
+
+ callback(null, prevVal);
+ });
+};
+
+CouchConfig.prototype.delete = function (section, key, callback) {
+ var previousValue = (this._config[section] || {})[key];
+ if (exists(previousValue)) {
+ delete this._config[section][key];
+ if (!Object.keys(this._config[section]).length) {
+ delete this._config[section];
+ }
+ }
+
+ this._changed(section, key, previousValue, callback);
+};
+
+CouchConfig.prototype.registerDefault = function (section, key, value) {
+ this._defaults[section] = this._defaults[section] || {};
+ this._defaults[section][key] = value;
+};
+
+module.exports = function (app) {
+ var path = app.opts.configPath || './config.json';
+ var inMemory = app.opts.inMemoryConfig || false;
+ app.couchConfig = new CouchConfig(inMemory ? null : path);
+};
diff --git a/packages/node_modules/express-pouchdb/lib/create-or-delete-dbs.js b/packages/node_modules/express-pouchdb/lib/create-or-delete-dbs.js
new file mode 100644
index 00000000..3b034e8f
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/create-or-delete-dbs.js
@@ -0,0 +1,52 @@
+'use strict';
+
+// unified access to creating/deleting PouchDB databases,
+// to re-use PouchDB objects and avoid race conditions
+
+var Promise = require('pouchdb-promise');
+var PouchMap = require('pouchdb-collections').Map;
+
+var promiseChain = Promise.resolve();
+
+// do all creations/deletions in a single global lock
+// obviously this makes this operation slow... but hopefully you aren't
+// creating and destroying a lot of databases all the time
+function doSequentially(fun) {
+ promiseChain = promiseChain.then(fun);
+ return promiseChain;
+}
+
+function getOrCreateDB(PouchDB, dbName) {
+ var map = PouchDB.__dbCacheMap;
+ if (!map) {
+ // cache DBs to avoid costly re-allocations
+ map = PouchDB.__dbCacheMap = new PouchMap();
+ }
+ return doSequentially(function () {
+ var db = map.get(dbName);
+ if (db) {
+ return db;
+ }
+ db = new PouchDB(dbName);
+ map.set(dbName, db);
+ return db;
+ });
+}
+
+function deleteDB(PouchDB, dbName, opts) {
+ return doSequentially(function () {
+ var dbCache = PouchDB.__dbCacheMap;
+ var db = dbCache && dbCache.get(dbName);
+ if (!db) {
+ // if the db wasn't cached, we may still need to destroy it
+ // if it's saved to disk
+ return PouchDB.destroy(dbName, opts);
+ }
+ // if this db was cached, we need to remove it from the cache immediately
+ dbCache.delete(dbName);
+ return db.destroy(opts);
+ });
+}
+
+exports.getOrCreateDB = getOrCreateDB;
+exports.deleteDB = deleteDB;
\ No newline at end of file
diff --git a/packages/node_modules/express-pouchdb/lib/daemon-manager.js b/packages/node_modules/express-pouchdb/lib/daemon-manager.js
new file mode 100644
index 00000000..76de44db
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/daemon-manager.js
@@ -0,0 +1,34 @@
+"use strict";
+
+var Promise = require('pouchdb-promise');
+var utils = require('./utils');
+
+var resolved = Promise.resolve();
+
+function DaemonManager() {
+ // There are a few things express-pouchdb needs to do outside of
+ // requests. These things can't get a PouchDB object from the request
+ // like other code does. Instead, they register themselves here and
+ // get an object passed in. By providing both a start and stop
+ // function, it is possible to switch PouchDB objects on the fly.
+ this._daemons = [];
+}
+
+DaemonManager.prototype.registerDaemon = function (daemon) {
+ this._daemons.push(daemon);
+};
+
+['start', 'stop'].forEach(function (name) {
+ DaemonManager.prototype[name] = function (PouchDB) {
+ var funcs = this._daemons.map(function (daemon) {
+ return daemon[name];
+ });
+ return utils.callAsyncRecursive(funcs, function (func, next) {
+ return resolved.then(function () {
+ return func(PouchDB);
+ }).then(next);
+ });
+ };
+});
+
+module.exports = DaemonManager;
diff --git a/packages/node_modules/express-pouchdb/lib/db-wrapper.js b/packages/node_modules/express-pouchdb/lib/db-wrapper.js
new file mode 100644
index 00000000..5506fde3
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/db-wrapper.js
@@ -0,0 +1,38 @@
+"use strict";
+
+var Promise = require('pouchdb-promise');
+var utils = require('./utils');
+
+function DatabaseWrapper() {
+ // Databases can be wrapped to let them provide extra functionality.
+ // Examples of this include document validation, authorisation, and
+ // keeping track of the documents in _replicator.
+ //
+ // The logic of that is spread out through different parts of
+ // express-pouchdb, all of which register their functionality on this
+ // object. This way, the actual wrapping process is abstracted away.
+ this._wrappers = [];
+}
+module.exports = DatabaseWrapper;
+
+DatabaseWrapper.prototype.wrap = function (name, db) {
+ if (typeof db === 'undefined') {
+ throw new Error("no db defined!");
+ }
+ if (db.__wrappedByExpressPouch) {
+ // dbs are cached, so it might already be wrapped
+ return Promise.resolve(db);
+ }
+ db.__wrappedByExpressPouch = true;
+ return utils.callAsyncRecursive(this._wrappers, function (wrapper, next) {
+ return Promise.resolve(wrapper(name, db, next));
+ }).then(function () {
+ // https://github.com/pouchdb/pouchdb/issues/1940
+ delete db.then;
+ return db;
+ });
+};
+
+DatabaseWrapper.prototype.registerWrapper = function (wrapper) {
+ this._wrappers.push(wrapper);
+};
diff --git a/packages/node_modules/express-pouchdb/lib/disk-size.js b/packages/node_modules/express-pouchdb/lib/disk-size.js
new file mode 100644
index 00000000..1e14cd00
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/disk-size.js
@@ -0,0 +1,13 @@
+"use strict";
+
+module.exports = function enableDiskSize(app) {
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ PouchDB.plugin(require('pouchdb-size'));
+ }
+ });
+ app.dbWrapper.registerWrapper(function (name, db, next) {
+ db.installSizeWrapper();
+ return next();
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/eval-safely.js b/packages/node_modules/express-pouchdb/lib/eval-safely.js
new file mode 100644
index 00000000..5d5ea347
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/eval-safely.js
@@ -0,0 +1,11 @@
+'use strict';
+
+var vm = require('vm');
+
+function evalSafely(code) {
+ return vm.runInNewContext('(function() {"use strict"; return ' +
+ code +
+ ';})()');
+}
+
+module.exports = evalSafely;
\ No newline at end of file
diff --git a/packages/node_modules/express-pouchdb/lib/index.js b/packages/node_modules/express-pouchdb/lib/index.js
new file mode 100644
index 00000000..551027ae
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/index.js
@@ -0,0 +1,255 @@
+"use strict";
+
+var wrappers = require('pouchdb-wrappers'),
+ express = require('express'),
+ DatabaseWrapper = require('./db-wrapper'),
+ DaemonManager = require('./daemon-manager'),
+ Promise = require('pouchdb-promise'),
+ utils = require('./utils');
+
+var modes = {};
+modes.custom = [];
+modes.minimumForPouchDB = [
+ // sorted alphabetically
+ 'compression',
+ 'routes/404',
+ 'routes/all-dbs',
+ 'routes/all-docs',
+ 'routes/attachments',
+ 'routes/bulk-docs',
+ 'routes/bulk-get',
+ 'routes/changes',
+ 'routes/compact',
+ 'routes/db',
+ 'routes/documents',
+ 'routes/revs-diff',
+ 'routes/root',
+ 'routes/session-stub',
+ 'routes/temp-views',
+ 'routes/view-cleanup',
+ 'routes/views'
+];
+
+modes.fullCouchDB = [
+ // sorted alphabetically
+ 'config-infrastructure',
+ 'disk-size',
+ 'logging-infrastructure',
+ 'replicator',
+ 'routes/active-tasks',
+ 'routes/authentication',
+ 'routes/authorization',
+ 'routes/cluster',
+ 'routes/cluster-rewrite',
+ 'routes/config',
+ 'routes/db-updates',
+ 'routes/ddoc-info',
+ 'routes/fauxton',
+ 'routes/find',
+ 'routes/http-log',
+ 'routes/list',
+ 'routes/log',
+ 'routes/replicate',
+ 'routes/rewrite',
+ 'routes/security',
+ 'routes/session',
+ 'routes/show',
+ 'routes/special-test-auth',
+ 'routes/stats',
+ 'routes/update',
+ 'routes/uuids',
+ 'routes/vhosts',
+ 'validation'
+].concat(modes.minimumForPouchDB);
+
+function toObject(array) {
+ var result = {};
+ array.forEach(function (item) {
+ result[item] = true;
+ });
+ return result;
+}
+
+module.exports = function (startPouchDB, opts) {
+ var currentPouchDB;
+
+ // both PouchDB and opts are optional
+ if (startPouchDB && !startPouchDB.defaults) {
+ opts = startPouchDB;
+ startPouchDB = null;
+ }
+ opts = opts || {};
+
+ var app = express();
+ app.enable('case sensitive routing');
+ app.opts = opts;
+
+ // determine which parts of express-pouchdb to activate
+ opts.overrideMode = opts.overrideMode || {};
+ opts.overrideMode.include = opts.overrideMode.include || [];
+ opts.overrideMode.exclude = opts.overrideMode.exclude || [];
+ opts.overrideMode.include.forEach(function (part) {
+ if (modes.fullCouchDB.indexOf(part) === -1) {
+ throw new Error(
+ "opts.overrideMode.include contains the unknown part '" +
+ part + "'."
+ );
+ }
+ });
+
+ var modeIncludes = modes[app.opts.mode || 'fullCouchDB'];
+ if (!modeIncludes) {
+ throw new Error('Unknown mode: ' + app.opts.mode);
+ }
+ var allIncludes = modeIncludes.concat(app.opts.overrideMode.include);
+ app.includes = toObject(allIncludes);
+ app.opts.overrideMode.exclude.forEach(function (part) {
+ if (!app.includes[part]) {
+ throw new Error(
+ "opts.overrideMode.exclude contains the not included part '" +
+ part + "'."
+ );
+ }
+ delete app.includes[part];
+ });
+
+ // the daemon manager is a non-negotiable part of express-pouchdb,
+ // it's needed for static method wrappers & installing
+ // pouchdb-all-dbs. Both are required for nearly everything.
+ app.daemonManager = new DaemonManager();
+ app.setPouchDB = function (newPouchDB) {
+ var oldPouchDB = currentPouchDB;
+ currentPouchDB = newPouchDB;
+
+ var stoppingDone = Promise.resolve();
+ if (oldPouchDB) {
+ stoppingDone = app.daemonManager.stop(oldPouchDB);
+ }
+ return stoppingDone.then(function () {
+ return app.daemonManager.start(newPouchDB);
+ });
+ };
+
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ // add PouchDB.new() - by default it just returns 'new PouchDB()'
+ // also re-adds PouchDB.destroy(), see for reasoning:
+ // https://github.com/pouchdb/express-pouchdb/pull/231#issuecomment-136095649
+ wrappers.installStaticWrapperMethods(PouchDB, {});
+
+ return Promise.resolve().then(function () {
+ return PouchDB.allDbs();
+ }).catch(function () {
+ require('pouchdb-all-dbs')(PouchDB);
+ });
+ }
+ });
+
+ // the dbWrapper is also a vital part of express-pouchdb which can't
+ // be disabled. Some methods of dbs need to be wrapped at the very
+ // least with a noop, to work around incompatible api.
+ app.dbWrapper = new DatabaseWrapper();
+ app.dbWrapper.registerWrapper(function (name, db, next) {
+ //'fix' the PouchDB api (support opts arg everywhere)
+ function noop(orig) {
+ return orig();
+ }
+ var wrapperMethods = {};
+ ['info', 'removeAttachment'].forEach(function (name) {
+ wrapperMethods[name] = noop;
+ });
+ wrappers.installWrapperMethods(db, wrapperMethods);
+ return next();
+ });
+
+ app.use(function (req, res, next) {
+ var prop;
+
+ // Normalize query string parameters for direct passing
+ // into PouchDB queries.
+ for (prop in req.query) {
+ if (Object.prototype.hasOwnProperty.call(req.query, prop)) {
+ try {
+ req.query[prop] = JSON.parse(req.query[prop]);
+ } catch (e) {}
+ }
+ }
+
+ // Provide the request access to the current PouchDB object.
+ if (!currentPouchDB) {
+ var msg = "express-pouchdb needs a PouchDB object to route a request!";
+ throw new Error(msg);
+ }
+ req.PouchDB = currentPouchDB;
+
+ next();
+ });
+
+ // load all modular files
+ [
+ // order matters in this list!
+ 'config-infrastructure',
+ 'logging-infrastructure',
+ 'compression',
+ 'disk-size',
+ 'replicator',
+ 'routes/http-log',
+ 'routes/authentication',
+ 'routes/special-test-auth',
+ 'routes/authorization',
+ 'routes/vhosts',
+ 'routes/cluster-rewrite',
+ 'routes/rewrite',
+ 'routes/root',
+ 'routes/log',
+ 'routes/session',
+ 'routes/session-stub',
+ 'routes/fauxton',
+ 'routes/cluster',
+ 'routes/config',
+ 'routes/uuids',
+ 'routes/all-dbs',
+ 'routes/replicate',
+ 'routes/active-tasks',
+ 'routes/db-updates',
+ 'routes/stats',
+ 'routes/db',
+ 'routes/bulk-docs',
+ 'routes/bulk-get',
+ 'routes/all-docs',
+ 'routes/changes',
+ 'routes/compact',
+ 'routes/revs-diff',
+ 'routes/security',
+ 'routes/view-cleanup',
+ 'routes/temp-views',
+ 'routes/find',
+ 'routes/views',
+ 'routes/ddoc-info',
+ 'routes/show',
+ 'routes/list',
+ 'routes/update',
+ 'routes/attachments',
+ 'routes/documents',
+ 'validation',
+ 'routes/404'
+ ].forEach(function (file) {
+ if (app.includes[file]) {
+ require('./' + file)(app);
+ }
+ });
+
+ if (app.couchConfig) {
+ app.couchConfig.registerDefault(
+ 'couchdb',
+ 'max_document_size',
+ utils.maxDocumentSizeDefault
+ );
+ }
+
+ if (startPouchDB) {
+ app.setPouchDB(startPouchDB);
+ }
+
+ return app;
+};
diff --git a/packages/node_modules/express-pouchdb/lib/logging-infrastructure.js b/packages/node_modules/express-pouchdb/lib/logging-infrastructure.js
new file mode 100644
index 00000000..a19c6ed9
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/logging-infrastructure.js
@@ -0,0 +1,95 @@
+"use strict";
+
+var fs = require('fs');
+var utils = require('./utils');
+
+// sorted from a lot of output to no output
+var LOG_LEVELS = ['debug', 'info', 'warning', 'error', 'none'];
+
+function CouchLogger(file, level) {
+ // set start values
+ this.setFile(file);
+ this.setLevel(level);
+}
+
+CouchLogger.prototype.setFile = function (file) {
+ if (this._stream) {
+ this._stream.end();
+ }
+ this._file = file;
+ this._stream = fs.createWriteStream(file, {
+ flags: 'a',
+ encoding: 'UTF-8'
+ });
+};
+
+CouchLogger.prototype.setLevel = function (level) {
+ var newLevel = LOG_LEVELS.indexOf(level);
+ if (newLevel === -1) {
+ newLevel = LOG_LEVELS.indexOf("info"); // the default
+ }
+ this._level = newLevel;
+};
+
+CouchLogger.prototype._write = function (level, message) {
+ if (LOG_LEVELS.indexOf(level) < this._level) {
+ //don't log
+ return;
+ }
+ var date = new Date().toUTCString();
+ var pid = '0.000.0';
+
+ var str = '[{0}] [{1}] [<{2}>] {3}\n'
+ .replace('{0}', date)
+ .replace('{1}', level)
+ .replace('{2}', pid)
+ .replace('{3}', message);
+
+ this._stream.write(str);
+};
+
+// defines CouchLogger.debug(); CouchLogger.info(),
+// CouchLogger.warning() & CouchLogger.error()
+
+// slice so the 'none' level doesn't get a function.
+LOG_LEVELS.slice(0, -1).forEach(function (logLevel) {
+ CouchLogger.prototype[logLevel] = function (message) {
+ this._write(logLevel, message);
+ };
+});
+
+CouchLogger.prototype.getLog = function (bytes, offset, callback) {
+ var self = this;
+ fs.stat(self._file, function (err, info) {
+ if (err) {
+ return callback(err);
+ }
+ var opts = {
+ start: Math.max(info.size - bytes - offset - 1, 0),
+ end: Math.max(info.size - offset - 1, 0),
+ encoding: 'UTF-8'
+ };
+ callback(null, {
+ stream: fs.createReadStream(self._file, opts),
+ length: opts.end - opts.start
+ });
+ });
+};
+
+module.exports = function (app) {
+ utils.requires(app, 'config-infrastructure');
+
+ // set up log file
+ var logPath = app.opts.logPath || './log.txt';
+ app.couchConfig.registerDefault('log', 'file', logPath);
+ app.couchConfig.registerDefault('log', 'level', 'info');
+ var getFile = app.couchConfig.get.bind(app.couchConfig, 'log', 'file');
+ var getLevel = app.couchConfig.get.bind(app.couchConfig, 'log', 'level');
+ app.couchLogger = new CouchLogger(getFile(), getLevel());
+ app.couchConfig.on('log.file', function () {
+ app.couchLogger.setFile(getFile());
+ });
+ app.couchConfig.on('log.level', function () {
+ app.couchLogger.setLevel(getLevel());
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/replicator.js b/packages/node_modules/express-pouchdb/lib/replicator.js
new file mode 100644
index 00000000..d44eefd0
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/replicator.js
@@ -0,0 +1,83 @@
+"use strict";
+
+var Promise = require('pouchdb-promise');
+var utils = require('./utils');
+var getOrCreateDB = require('./create-or-delete-dbs').getOrCreateDB;
+
+module.exports = function enableReplicator(app) {
+ var db, PouchDB;
+
+ utils.requires(app, 'config-infrastructure');
+
+ // explain how to activate the replicator db logic.
+ app.dbWrapper.registerWrapper(function (name, db, next) {
+ if (name === getReplicatorDBName()) {
+ return db.useAsReplicatorDB();
+ }
+ return next();
+ });
+
+ app.couchConfig.registerDefault('replicator', 'db', '_replicator');
+ function getReplicatorDBName() {
+ return app.couchConfig.get('replicator', 'db');
+ }
+
+ app.couchConfig.on('replicator.db', restartDaemon);
+
+ function onDestroy(dbName) {
+ // if the replicator db was removed, it should re-appear.
+ if (dbName === getReplicatorDBName()) {
+ restartDaemon();
+ }
+ }
+
+ function restartDaemon() {
+ return daemon.stop().then(function () {
+ return daemon.start();
+ });
+ }
+
+ var currentActions = Promise.resolve();
+ function serialExecution(func) {
+ currentActions = currentActions.then(func);
+ return currentActions;
+ }
+
+ var daemon = {
+ start: function (thePouchDB) {
+ if (thePouchDB) {
+ PouchDB = thePouchDB;
+
+ PouchDB.plugin(require('pouchdb-replicator'));
+ }
+ if (PouchDB.isHTTPPouchDB) {
+ return;
+ }
+
+ return serialExecution(function () {
+ var name = getReplicatorDBName();
+ return getOrCreateDB(PouchDB, name).then(function (db) {
+ PouchDB.on('destroyed', onDestroy);
+ return db.startReplicatorDaemon();
+ });
+ });
+ },
+ stop: function () {
+ if (PouchDB.isHTTPPouchDB) {
+ return;
+ }
+ return serialExecution(function () {
+ PouchDB.removeListener('destroyed', onDestroy);
+ // stop old replicator daemon
+ return Promise.resolve().then(function () {
+ return db.stopReplicatorDaemon();
+ }).catch(function () {
+ // no worries if stopping failed, the database is probably
+ // just not there (or deleted).
+ });
+ });
+ }
+ };
+
+ app.daemonManager.registerDaemon(daemon);
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/404.js b/packages/node_modules/express-pouchdb/lib/routes/404.js
new file mode 100644
index 00000000..f174c514
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/404.js
@@ -0,0 +1,13 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ // 404 handler
+ app.use(function (req, res) {
+ utils.sendJSON(res, 404, {
+ error: "not_found",
+ reason: "missing"
+ });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/active-tasks.js b/packages/node_modules/express-pouchdb/lib/routes/active-tasks.js
new file mode 100644
index 00000000..852292b7
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/active-tasks.js
@@ -0,0 +1,11 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ // Active tasks (stub for now)
+ app.get('/_active_tasks', function (req, res) {
+ // TODO: implement
+ utils.sendJSON(res, 200, []);
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/all-dbs.js b/packages/node_modules/express-pouchdb/lib/routes/all-dbs.js
new file mode 100644
index 00000000..5402844d
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/all-dbs.js
@@ -0,0 +1,19 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ // List all databases.
+ app.get('/_all_dbs', function (req, res) {
+ req.PouchDB.allDbs(function (err, response) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+
+ response = response.filter(function (name) {
+ return name !== 'pouch__auth_sessions__';
+ });
+ utils.sendJSON(res, 200, response);
+ });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/all-docs.js b/packages/node_modules/express-pouchdb/lib/routes/all-docs.js
new file mode 100644
index 00000000..c131cbe2
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/all-docs.js
@@ -0,0 +1,30 @@
+"use strict";
+
+var utils = require('../utils'),
+ extend = require('extend');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ var isAllowedMethod = function (method, allowedMethods) {
+ return allowedMethods.indexOf(method) !== -1;
+ };
+
+ // All docs operations
+ app.all('/:db/_all_docs', utils.jsonParser, function (req, res, next) {
+ if (!isAllowedMethod(req.method, ['GET', 'HEAD', 'POST'])) {
+ return next();
+ }
+
+ // Check that the request body, if present, is an object.
+ if (req.body && (typeof req.body !== 'object' || Array.isArray(req.body))) {
+ return utils.sendJSON(res, 400, {
+ reason: "Request body must be a JSON object",
+ error: 'bad_request'
+ });
+ }
+
+ var opts = utils.makeOpts(req, extend({}, req.body, req.query));
+ req.db.allDocs(opts, utils.sendCallback(res, 400));
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/attachments.js b/packages/node_modules/express-pouchdb/lib/routes/attachments.js
new file mode 100644
index 00000000..5c19e041
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/attachments.js
@@ -0,0 +1,113 @@
+"use strict";
+
+var utils = require('../utils');
+var Promise = require('pouchdb-promise');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ // Put a document attachment
+ function putAttachment(db, name, req, res) {
+ utils.parseRawBody(req, res, function () {
+ var attachment = req.params.attachment,
+ rev = req.query.rev,
+ type = req.get('Content-Type') || 'application/octet-stream',
+ body = new Buffer(req.rawBody || '', 'binary'),
+ opts = utils.makeOpts(req);
+
+ function cb(err, response) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+ res.set('ETag', JSON.stringify(response.rev));
+ var attachmentURI = encodeURIComponent(attachment);
+ utils.setLocation(res, db + '/' + name + '/' + attachmentURI);
+ utils.sendJSON(res, 201, response);
+ }
+ req.db.putAttachment(name, attachment, rev, body, type, opts, cb);
+ });
+ }
+
+ app.put('/:db/_design/:id/:attachment(*)', function (req, res) {
+ putAttachment(req.params.db, '_design/' + req.params.id, req, res);
+ });
+
+ app.put('/:db/:id/:attachment(*)', function (req, res, next) {
+ // Be careful not to catch normal design docs or local docs
+ if (req.params.id === '_design' || req.params.id === '_local') {
+ return next();
+ }
+ putAttachment(req.params.db, req.params.id, req, res);
+ });
+
+ // Retrieve a document attachment
+ function getAttachment(name, req, res) {
+ var attachment = req.params.attachment;
+ var opts = utils.makeOpts(req, req.query);
+
+ return Promise.all([
+ req.db.get(name, opts),
+ req.db.getAttachment(name, attachment, opts)
+ ]).then(function (results) {
+ var doc = results[0];
+ var att = results[1];
+
+ var attInfo = doc._attachments && doc._attachments[attachment];
+
+ if (!attInfo) {
+ return utils.sendJSON(res, 404, {
+ error: 'not_found',
+ reason: 'missing'
+ });
+ }
+
+ var type = attInfo.content_type;
+ var md5 = attInfo.digest.slice(4);
+
+ res.set('ETag', JSON.stringify(md5));
+ res.setHeader('Content-Type', type);
+ res.status(200).send(att);
+ }).catch(function (err) {
+ utils.sendError(res, err);
+ });
+ }
+
+ app.get('/:db/_design/:id/:attachment(*)', function (req, res) {
+ getAttachment('_design/' + req.params.id, req, res);
+ });
+
+ app.get('/:db/:id/:attachment(*)', function (req, res, next) {
+ // Be careful not to catch normal design docs or local docs
+ if (req.params.id === '_design' || req.params.id === '_local') {
+ return next();
+ }
+ getAttachment(req.params.id, req, res);
+ });
+
+ // Delete a document attachment
+ function deleteAttachment(name, req, res) {
+ var attachment = req.params.attachment,
+ rev = req.query.rev,
+ opts = utils.makeOpts(req);
+
+ function cb(err, response) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+ utils.sendJSON(res, 200, response);
+ }
+ req.db.removeAttachment(name, attachment, rev, opts, cb);
+ }
+
+ app.delete('/:db/_design/:id/:attachment(*)', function (req, res) {
+ deleteAttachment('_design/' + req.params.id, req, res);
+ });
+
+ app.delete('/:db/:id/:attachment(*)', function (req, res, next) {
+ // Be careful not to catch normal design docs or local docs
+ if (req.params.id === '_design' || req.params.id === '_local') {
+ return next();
+ }
+ deleteAttachment(req.params.id, req, res);
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/authentication.js b/packages/node_modules/express-pouchdb/lib/routes/authentication.js
new file mode 100644
index 00000000..444d1561
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/authentication.js
@@ -0,0 +1,148 @@
+"use strict";
+
+var cookieParser = require('cookie-parser'),
+ basicAuth = require('basic-auth'),
+ utils = require('../utils'),
+ Promise = require('pouchdb-promise'),
+ Auth = require('pouchdb-auth');
+
+var SECTION = 'couch_httpd_auth';
+
+module.exports = function (app) {
+ var usersDBPromise, refreshUsersDBImpl;
+
+ utils.requires(app, 'config-infrastructure');
+ utils.requires(app, 'logging-infrastructure');
+
+ app.couchConfig.registerDefault(SECTION, 'authentication_db', '_users');
+ app.couchConfig.registerDefault(SECTION, 'timeout', 600);
+ app.couchConfig.registerDefault(SECTION, 'secret', Auth.generateSecret());
+ app.couchConfig.registerDefault(SECTION, 'iterations', 10);
+
+ // explain how to activate the auth db logic.
+ app.dbWrapper.registerWrapper(function (name, db, next) {
+ if (name === getUsersDBName()) {
+ return db.useAsAuthenticationDB({
+ isOnlineAuthDB: false,
+ timeout: app.couchConfig.get(SECTION, 'timeout'),
+ secret: app.couchConfig.get(SECTION, 'secret'),
+ iterations: app.couchConfig.get(SECTION, 'iterations'),
+ admins: app.couchConfig.getSection('admins')
+ });
+ }
+ return next();
+ });
+
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ PouchDB.plugin(Auth);
+
+ refreshUsersDBImpl = function () {
+ usersDBPromise = utils.getUsersDB(app, PouchDB);
+ };
+ refreshUsersDB();
+ PouchDB.on('destroyed', onDestroyed);
+ },
+ stop: function (PouchDB) {
+ PouchDB.removeListener('destroyed', onDestroyed);
+ }
+ });
+
+ // utils
+ var getUsersDBName = utils.getUsersDBName.bind(null, app);
+
+ function getUsersDB() {
+ // calls itself until usersDBPromise is a available
+ if (!usersDBPromise) {
+ return new Promise(function (resolve) {
+ setImmediate(function () {
+ resolve(getUsersDB());
+ });
+ });
+ }
+ return usersDBPromise;
+ }
+
+ function onDestroyed(dbName) {
+ // if the users db was removed, it should re-appear.
+ if (dbName === getUsersDBName()) {
+ refreshUsersDB();
+ }
+ }
+
+ function refreshUsersDB() {
+ return refreshUsersDBImpl();
+ }
+
+ // ensure there's always a users db
+ app.couchConfig.on(SECTION + '.authentication_db', refreshUsersDB);
+ app.couchConfig.on(SECTION + '.timeout', refreshUsersDB);
+ app.couchConfig.on(SECTION + '.secret', refreshUsersDB);
+ app.couchConfig.on(SECTION + '.iterations', refreshUsersDB);
+ app.couchConfig.on('admins', refreshUsersDB);
+
+ // routing
+ app.use(cookieParser());
+
+ app.use(function (req, res, next) {
+ // TODO: TIMING ATTACK
+ Promise.resolve().then(function () {
+ return buildCookieSession(req, res);
+ }).catch(function () {
+ return buildBasicAuthSession(req);
+ }).then(function (result) {
+ req.couchSession = result;
+ req.couchSession.info.authentication_handlers = ['cookie', 'default'];
+ next();
+ }).catch(function (err) {
+ utils.sendError(res, err);
+ });
+ });
+
+ function buildCookieSession(req, res) {
+ var sessionID = (req.cookies || {}).AuthSession;
+ if (!sessionID) {
+ throw new Error("No cookie, so no cookie auth.");
+ }
+ return getUsersDB().then(function (db) {
+ return db.multiUserSession(sessionID);
+ }).then(function (session) {
+ if (session.info.authenticated) {
+ res.cookie('AuthSession', session.sessionID, {httpOnly: true});
+ delete session.sessionID;
+ session.info.authenticated = 'cookie';
+ logSuccess('cookie', session);
+ }
+ return session;
+ });
+ }
+
+ function logSuccess(type, session) {
+ var msg = 'Successful ' + type + ' auth as: "' + session.userCtx.name + '"';
+ app.couchLogger.debug(msg);
+ }
+
+ function buildBasicAuthSession(req) {
+ var userInfo = basicAuth(req);
+ var db;
+ var initializingDone = getUsersDB().then(function (theDB) {
+ db = theDB;
+ });
+ if (userInfo) {
+ initializingDone = initializingDone.then(function () {
+ return db.multiUserLogIn(userInfo.name, userInfo.pass);
+ });
+ }
+ return initializingDone.then(function (info) {
+ return db.multiUserSession((info || {}).sessionID);
+ }).then(function (session) {
+ delete session.sessionID;
+
+ if (session.info.authenticated) {
+ session.info.authenticated = 'default';
+ logSuccess('http basic', session);
+ }
+ return session;
+ });
+ }
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/authorization.js b/packages/node_modules/express-pouchdb/lib/routes/authorization.js
new file mode 100644
index 00000000..2963e200
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/authorization.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/authentication');
+
+ // routes that need server admin protection
+ app.get('/_config', requiresServerAdmin);
+ app.get('/_config/:section', requiresServerAdmin);
+ app.get('/_config/:section/:key', requiresServerAdmin);
+ app.put('/_config/:section/:key', requiresServerAdmin);
+ app.delete('/_config/:section/:key', requiresServerAdmin);
+
+ app.get('/_log', requiresServerAdmin);
+ app.get('/_active_tasks', requiresServerAdmin);
+ app.get('/_db_updates', requiresServerAdmin);
+ app.post('/_restart', requiresServerAdmin);
+
+ function requiresServerAdmin(req, res, next) {
+ if (req.couchSession.userCtx.roles.indexOf('_admin') !== -1) {
+ return next();
+ }
+ utils.sendJSON(res, 401, {
+ error: 'unauthorized',
+ reason: "You are not a server admin."
+ });
+ }
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/bulk-docs.js b/packages/node_modules/express-pouchdb/lib/routes/bulk-docs.js
new file mode 100644
index 00000000..b31fbc79
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/bulk-docs.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ // Bulk docs operations
+ app.post('/:db/_bulk_docs', utils.jsonParser, function (req, res) {
+
+ // Maybe this should be moved into the leveldb adapter itself? Not sure
+ // how uncommon it is for important options to come through in the body
+ // https://github.com/daleharvey/pouchdb/issues/435
+ var opts = 'new_edits' in req.body ?
+ { new_edits: req.body.new_edits } : {};
+ opts = utils.makeOpts(req, opts);
+
+ if (Array.isArray(req.body)) {
+ return utils.sendJSON(res, 400, {
+ error: "bad_request",
+ reason: "Request body must be a JSON object"
+ });
+ }
+ if (!req.body.docs) {
+ return utils.sendJSON(res, 400, {
+ error: "bad_request",
+ reason: "Missing JSON list of 'docs'"
+ });
+ }
+
+ req.db.bulkDocs(req.body, opts, function (err, response) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+ utils.sendJSON(res, 201, response.map(function (info) {
+ if (info.error) {
+ return {
+ id: info.id,
+ error: info.name,
+ reason: info.message
+ };
+ }
+ return info;
+ }));
+ });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/bulk-get.js b/packages/node_modules/express-pouchdb/lib/routes/bulk-get.js
new file mode 100644
index 00000000..09c14f08
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/bulk-get.js
@@ -0,0 +1,59 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ // Bulk docs operations
+ app.post('/:db/_bulk_get', utils.jsonParser, function (req, res) {
+
+ var opts = req.body || {};
+
+ if (Array.isArray(req.body)) {
+ return utils.sendJSON(res, 400, {
+ error: "bad_request",
+ reason: "Request body must be a JSON object"
+ });
+ }
+ if (!req.body.docs) {
+ return utils.sendJSON(res, 400, {
+ error: "bad_request",
+ reason: "Missing JSON list of 'docs'"
+ });
+ }
+
+ if (typeof req.db.bulkGet !== 'function') {
+ return utils.sendJSON(res, 400, {
+ error: "bad_request",
+ reason: "bulkGet not supported by this version of PouchDB"
+ });
+ }
+
+ // some options can be specified via query, e.g. ?revs=true
+ Object.keys(req.query).forEach(function (param) {
+ opts[param] = req.query[param];
+ });
+
+ req.db.bulkGet(opts, function (err, response) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+ var httpResults = response.results.map(function (docInfo) {
+ return {
+ id: docInfo.id,
+ docs: docInfo.docs.map(function (info) {
+ if (info.error) {
+ return {
+ error: info.name,
+ reason: info.message
+ };
+ }
+ return info;
+ })
+ };
+ });
+ utils.sendJSON(res, 200, { results: httpResults });
+ });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/changes.js b/packages/node_modules/express-pouchdb/lib/routes/changes.js
new file mode 100644
index 00000000..db24a59e
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/changes.js
@@ -0,0 +1,103 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ // Monitor database changes
+ function changes(req, res) {
+
+ utils.setJsonOrPlaintext(res);
+
+ // api.changes expects a property `query_params`
+ // This is a pretty inefficient way to do it.. Revisit?
+ req.query.query_params = JSON.parse(JSON.stringify(req.query));
+
+ req.query = utils.makeOpts(req, req.query);
+
+ if (req.body && req.body.doc_ids) {
+ req.query.doc_ids = req.body.doc_ids;
+ }
+
+ if (req.query.feed === 'continuous' || req.query.feed === 'longpoll') {
+ var heartbeatInterval;
+ // 60000 is the CouchDB default
+ // TODO: figure out if we can make this default less aggressive
+ var heartbeat = (typeof req.query.heartbeat === 'number') ?
+ req.query.heartbeat : 6000;
+ var written = false;
+ heartbeatInterval = setInterval(function () {
+ written = true;
+ res.write('\n');
+ }, heartbeat);
+
+ var cleanup = function () {
+ if (heartbeatInterval) {
+ clearInterval(heartbeatInterval);
+ }
+ };
+
+ if (req.query.feed === 'continuous') {
+ req.query.live = req.query.continuous = true;
+ req.db.changes(req.query).on('change', function (change) {
+ written = true;
+ utils.writeJSON(res, change);
+ }).on('error', function (err) {
+ if (!written) {
+ utils.sendError(res, err);
+ } else {
+ res.end();
+ }
+ cleanup();
+ });
+ } else { // longpoll
+ // first check if there are >0. if so, return them immediately
+ req.query.live = req.query.continuous = false;
+ req.db.changes(req.query).then(function (complete) {
+ if (complete.results.length) {
+ utils.writeJSON(res, complete);
+ res.end();
+ cleanup();
+ } else { // do the longpolling
+ // mimicking CouchDB, start sending the JSON immediately
+ res.write('{"results":[\n');
+ req.query.live = req.query.continuous = true;
+ var changes = req.db.changes(req.query)
+ .on('change', function (change) {
+ utils.writeJSON(res, change);
+ res.write('],\n"last_seq":' + change.seq + '}\n');
+ res.end();
+ changes.cancel();
+ cleanup();
+ }).on('error', function (err) {
+ // shouldn't happen
+ console.log(err);
+ res.end();
+ cleanup();
+ }).on('complete', function () {
+ cleanup();
+ });
+
+ req.connection.on('close', function () {
+ changes.cancel();
+ });
+ }
+ }, function (err) {
+ if (!written) {
+ utils.sendError(res, err);
+ }
+ cleanup();
+ });
+ }
+ } else { // straight shot, not continuous
+ req.db.changes(req.query).then(function (response) {
+ utils.sendJSON(res, 200, response);
+ }).catch(function (err) {
+ utils.sendError(res, err);
+ });
+ }
+ }
+ app.get('/:db/_changes', changes);
+ app.post('/:db/_changes', utils.jsonParser, changes);
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/cluster-rewrite.js b/packages/node_modules/express-pouchdb/lib/routes/cluster-rewrite.js
new file mode 100644
index 00000000..7badba55
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/cluster-rewrite.js
@@ -0,0 +1,10 @@
+'use strict';
+
+module.exports = function (app) {
+ app.use(function (req, res, next) {
+ // magical route for "clustering" to support new Fauxton UI
+ var regex = /^\/_node\/node1@127.0.0.1/;
+ req.url = req.url.replace(regex, '');
+ next();
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/cluster.js b/packages/node_modules/express-pouchdb/lib/routes/cluster.js
new file mode 100644
index 00000000..7d042015
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/cluster.js
@@ -0,0 +1,21 @@
+'use strict';
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+
+ // there is only one node, but these APIs
+ // are required for fauxton
+ app.get('/_membership', function (req, res) {
+ utils.sendJSON(res, 200, {
+ all_nodes: ['node1@127.0.0.1'],
+ cluster_nodes: ['node1@127.0.0.1']
+ });
+ });
+
+ app.get('/_cluster_setup', function (req, res) {
+ utils.sendJSON(res, 201, {
+ state: 'cluster_disabled'
+ });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/compact.js b/packages/node_modules/express-pouchdb/lib/routes/compact.js
new file mode 100644
index 00000000..9b01df9d
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/compact.js
@@ -0,0 +1,17 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ // DB Compaction
+ app.post('/:db/_compact', utils.jsonParser, function (req, res) {
+ req.db.compact(utils.makeOpts(req), function (err) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+ utils.sendJSON(res, 200, {ok: true});
+ });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/config.js b/packages/node_modules/express-pouchdb/lib/routes/config.js
new file mode 100644
index 00000000..df8b7663
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/config.js
@@ -0,0 +1,155 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'config-infrastructure');
+
+ // Config
+ app.get('/_config', function (req, res) {
+ utils.sendJSON(res, 200, app.couchConfig.getAll());
+ });
+
+ app.get('/_config/:section', function (req, res) {
+ utils.sendJSON(res, 200, app.couchConfig.getSection(req.params.section));
+ });
+
+ app.get('/_config/:section/:key', function (req, res) {
+ var value = app.couchConfig.get(req.params.section, req.params.key);
+ sendConfigValue(res, value);
+ });
+
+ function sendConfigValue(res, value) {
+ if (typeof value === "undefined") {
+ return utils.sendJSON(res, 404, {
+ error: "not_found",
+ reason: "unknown_config_value"
+ });
+ }
+ if (typeof value !== 'string') {
+ value = JSON.stringify(value);
+ }
+ utils.sendJSON(res, 200, value);
+ }
+
+ function putHandler(req, res) {
+ // Custom JSON parsing, because the default JSON body parser
+ // middleware only supports JSON lists and objects. (Not numbers etc.)
+ var value;
+ try {
+ value = JSON.parse(req.rawBody.toString('utf-8'));
+ } catch (err) {
+ return utils.sendJSON(res, 400, {
+ error: "bad_request",
+ reason: "invalid_json"
+ });
+ }
+ if (typeof value !== "string") {
+ return utils.sendJSON(res, 500, {
+ error: "unknown_error",
+ reason: "badarg"
+ });
+ }
+ var section = req.params.section;
+ var key = req.params.key;
+ if (!whitelisted(section, key)) {
+ return sendModificationNotAllowed(res);
+ }
+ try {
+ value = JSON.parse(value);
+ } catch (err) {}
+
+ function cb(err, oldValue) {
+ utils.sendJSON(res, 200, oldValue || "");
+ }
+ app.couchConfig.set(section, key, value, cb);
+ }
+ app.put('/_config/:section/:key', utils.parseRawBody, putHandler);
+
+ app.delete('/_config/:section/:key', function (req, res) {
+ var section = req.params.section;
+ var key = req.params.key;
+ if (!whitelisted(section, key)) {
+ return sendModificationNotAllowed(res);
+ }
+ app.couchConfig.delete(section, key, function (err, oldValue) {
+ sendConfigValue(res, oldValue);
+ });
+ });
+
+ function getWhitelist() {
+ // parses an erlang term like:
+ // [{httpd,config_whitelist}, {<<"test">>,<<"foo">>}]
+ // into a JS array like:
+ // [['httpd', 'config_whitelist'], ['test', 'foo']]
+ //
+ // returns undefined when a parse error occurs, or when there is
+ // no whitelist
+ var val = app.couchConfig.get('httpd', 'config_whitelist');
+ val = (val || '').trim();
+ if (!val) {
+ return;
+ }
+ if (val[0] + val[val.length - 1] !== '[]') {
+ // invalid syntax
+ return;
+ }
+ val = val.slice(1, -1).trim();
+ var whitelist = [];
+ if (!val) {
+ return whitelist;
+ }
+
+ var foundAtLeastOneMatch = false;
+ var re = /{([^}]*)}/g;
+ var match;
+ while ((match = re.exec(val))) {
+ foundAtLeastOneMatch = true;
+
+ var part = match[1];
+ var subParts = parseToJSStrings(part.split(','));
+ if (subParts.length !== 2) {
+ // invalid syntax
+ return;
+ }
+ whitelist.push(subParts);
+ }
+ if (foundAtLeastOneMatch) {
+ return whitelist;
+ }
+ // invalid syntax, return undefined
+ }
+
+ function whitelisted(section, key) {
+ var wl = getWhitelist();
+ if (!wl) {
+ // when the whitelist doesn't exist or can't be parsed every
+ // change is allowed
+ return true;
+ }
+ // only section/key pairs on the whitelist are allowed when there is
+ // one
+ return wl.some(function (item) {
+ return item[0] === section && item[1] === key;
+ });
+ }
+};
+
+function parseToJSStrings(erlangStrings) {
+ return erlangStrings.map(function (str) {
+ return str.trim();
+ }).map(function (str) {
+ // "string" -> string
+ return (/"([^"]*)"/.exec(str) || {})[1] || str;
+ }).map(function (str) {
+ // <<"string">> -> string
+ return (/<<"([^"]*)">>/.exec(str) || {})[1] || str;
+ });
+}
+
+function sendModificationNotAllowed(res) {
+ utils.sendJSON(res, 400, {
+ name: "modification_not_allowed",
+ reason: "This config variable is read-only"
+ });
+}
diff --git a/packages/node_modules/express-pouchdb/lib/routes/db-updates.js b/packages/node_modules/express-pouchdb/lib/routes/db-updates.js
new file mode 100644
index 00000000..5e9a32f4
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/db-updates.js
@@ -0,0 +1,33 @@
+"use strict";
+
+var events = require('events');
+
+module.exports = function (app) {
+ // init DbUpdates
+ var couchDbUpdates = new events.EventEmitter();
+
+ function onDBCreated(dbName) {
+ couchDbUpdates.emit('update', {db_name: dbName, type: 'created'});
+ }
+ function onDBDestroyed(dbName) {
+ couchDbUpdates.emit('update', {db_name: dbName, type: 'deleted'});
+ }
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ PouchDB.on('created', onDBCreated);
+ PouchDB.on('destroyed', onDBDestroyed);
+ },
+ stop: function (PouchDB) {
+ PouchDB.removeListener('created', onDBCreated);
+ PouchDB.removeListener('destroyed', onDBDestroyed);
+ }
+ });
+
+ app.all('/_db_updates', function (req, res) {
+ // TODO: implement
+ res.status(400).end();
+ // app.couch_db_updates.on('update', function(update) {
+ // utils.sendJSON(res, 200, update);
+ // });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/db.js b/packages/node_modules/express-pouchdb/lib/routes/db.js
new file mode 100644
index 00000000..60a9eb6b
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/db.js
@@ -0,0 +1,142 @@
+"use strict";
+
+var startTime = new Date().getTime(),
+ utils = require('../utils'),
+ wrappers = require('pouchdb-wrappers'),
+ mkdirp = require('mkdirp'),
+ pathResolve = require('path').resolve,
+ cleanFilename = require('../clean-filename'),
+ deleteDB = require('../create-or-delete-dbs').deleteDB;
+
+module.exports = function (app) {
+ // Create a database.
+ app.put('/:db', utils.jsonParser, function (req, res) {
+ var name = cleanFilename(req.params.db);
+
+ req.PouchDB.allDbs(function (err, dbs) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+
+ if (dbs.indexOf(name) !== -1) {
+ return utils.sendJSON(res, 412, {
+ 'error': 'file_exists',
+ 'reason': 'The database could not be created.'
+ });
+ }
+
+ // PouchDB.new() instead of new PouchDB() because that adds
+ // authorisation logic
+ var db = req.PouchDB.new(name, utils.makeOpts(req));
+ db.info(function (err) {
+ if (err) {
+ // temoperary workaround for leveldb adapter, see
+ // https://github.com/pouchdb/pouchdb/issues/5668
+ // when removing this code, also remove mkdir
+ // from dependencies in package.json
+ if (err.name === 'OpenError') {
+ var path = db.__opts.prefix ? db.__opts.prefix + name : name;
+
+ mkdirp(pathResolve(path), function (err) {
+ if (err) {
+ return utils.sendError(res, err, 412);
+ }
+
+ db = req.PouchDB.new(name, utils.makeOpts(req));
+ db.info(function (err) {
+
+ if (err) {
+ return utils.sendError(res, err, 412);
+ }
+ utils.setLocation(res, name);
+ utils.sendJSON(res, 201, {ok: true});
+ });
+ });
+ return;
+ }
+
+ return utils.sendError(res, err, 412);
+ }
+ utils.setLocation(res, name);
+ utils.sendJSON(res, 201, {ok: true});
+ });
+ });
+ });
+
+ // Delete a database
+ app.delete('/:db', function (req, res) {
+ if (req.query.rev) {
+ return utils.sendJSON(res, 400, {
+ error: 'bad_request',
+ reason: (
+ "You tried to DELETE a database with a ?rev= parameter. " +
+ "Did you mean to DELETE a document instead?"
+ )
+ });
+ }
+
+ var dbName = cleanFilename(req.params.db);
+ deleteDB(req.PouchDB, dbName, utils.makeOpts(req)).then(function () {
+ utils.sendJSON(res, 200, {ok: true});
+ }, function (err) {
+ utils.sendError(res, err);
+ });
+ });
+
+ // At this point, some route middleware can take care of identifying the
+ // correct PouchDB instance.
+ ['/:db/*', '/:db'].forEach(function (route) {
+ app.all(route, function (req, res, next) {
+ utils.setDBOnReq(req.params.db, app.dbWrapper, req, res, next);
+ });
+ });
+
+ // Get database information
+ app.get('/:db', function (req, res) {
+ req.db.info(utils.makeOpts(req), function (err, info) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+ info.instance_start_time = startTime.toString();
+ // TODO: data_size?
+ utils.sendJSON(res, 200, info);
+ });
+ });
+
+ // Ensure all commits are written to disk
+ app.post('/:db/_ensure_full_commit', function (req, res) {
+ // TODO: implement. Also check security then: who is allowed to
+ // access this? (db & server admins?)
+ utils.sendJSON(res, 201, {
+ ok: true,
+ instance_start_time: startTime.toString()
+ });
+ });
+
+ app.dbWrapper.registerWrapper(function (name, db, next) {
+ // db.info() should just return the non-uri encoded db name
+ wrappers.installWrapperMethods(db, {
+ info: function (orig) {
+ return orig().then(function (info) {
+ info.db_name = decodeURIComponent(info.db_name);
+ return info;
+ });
+ }
+ });
+
+ return next();
+ });
+
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ // PouchDB.allDbs() should return the non-uri encoded db name
+ wrappers.installStaticWrapperMethods(PouchDB, {
+ allDbs: function (orig) {
+ return orig().then(function (dbs) {
+ return dbs.map(decodeURIComponent);
+ });
+ }
+ });
+ }
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/ddoc-info.js b/packages/node_modules/express-pouchdb/lib/routes/ddoc-info.js
new file mode 100644
index 00000000..25dcf7cd
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/ddoc-info.js
@@ -0,0 +1,20 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ // Query design document info
+ app.get('/:db/_design/:id/_info', function (req, res) {
+ // Dummy data for Fauxton - when implementing fully also take into
+ // account req.couchSessionObj - this needs at least db view rights it
+ // seems.
+ //
+ // Also, if the implementation uses req.db, don't forget to add
+ // utils.requires(app, 'routes/db') at the top of the main function
+ // of this file.
+ utils.sendJSON(res, 200, {
+ 'name': req.query.id,
+ 'view_index': 'Not implemented.'
+ });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/documents.js b/packages/node_modules/express-pouchdb/lib/routes/documents.js
new file mode 100644
index 00000000..ccf24a82
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/documents.js
@@ -0,0 +1,269 @@
+"use strict";
+
+var fs = require('fs'),
+ multiparty = require('multiparty'),
+ utils = require('../utils'),
+ uuids = require('../uuids'),
+ extend = require('extend'),
+ Promise = require('pouchdb-promise'),
+ denodeify = require('denodeify'),
+ readFile = denodeify(fs.readFile);
+
+function onPutOrPostResponse(req, res) {
+ return function (response) {
+ res.set('ETag', '"' + response.rev + '"');
+ utils.setLocation(res, req.params.db + '/' + response.id);
+ utils.sendJSON(res, 201, response);
+ };
+}
+
+function mergeAttachments(doc, attachments) {
+ if (!doc._attachments) {
+ doc._attachments = {};
+ }
+
+ // don't store the "follows" key
+ Object.keys(doc._attachments).forEach(function (filename) {
+ delete doc._attachments[filename].follows;
+ });
+ // merge, since it could be a mix of stubs and non-stubs
+ doc._attachments = extend(true, doc._attachments, attachments);
+}
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ if (app.couchConfig) {
+ app.use('/' +
+ app.couchConfig.get('couch_httpd_auth', 'authentication_db') + '/:id',
+ function (req, res, next) {
+ var userCtx = (req.couchSession || {}).userCtx;
+ if (userCtx && userCtx.name && !~userCtx.roles.indexOf('_admin')) {
+ req.db.get(req.params.id, function (err, response) {
+ if (!err && response.name === userCtx.name) {
+ userCtx.roles.push('_admin');
+ }
+ next();
+ });
+ return;
+ }
+ next();
+ }
+ );
+ }
+
+ // Slightly unusual endpoint where you can POST an attachment to a doc.
+ // Used by the Fauxton UI for uploading attachments.
+ app.post('/:db/:id(*)', utils.jsonParser, function (req, res) {
+ if (!/^multipart\/form-data/.test(req.headers['content-type'])) {
+ return utils.sendJSON(res, 400, {
+ error: "bad_request",
+ reason: "only_multipart_accepted"
+ });
+ }
+
+ var opts = utils.makeOpts(req, req.query);
+
+ var promise = Promise.resolve();
+ var attachments = {};
+ var form = new multiparty.Form();
+ var doc;
+ form.on('error', function (err) {
+ promise = promise.then(function () {
+ throw err;
+ });
+ }).on('field', function (name, field) {
+ if (name !== '_rev') {
+ return;
+ }
+ promise = promise.then(function () {
+ return req.db.get(req.params.id, {rev: field});
+ }).then(function (theDoc) {
+ doc = theDoc;
+ });
+ }).on('file', function (_, file) {
+ var type = file.headers['content-type'];
+ var filename = file.originalFilename;
+ promise = promise.then(function () {
+ return readFile(file.path);
+ }).then(function (body) {
+ attachments[filename] = {
+ content_type: type,
+ data: body
+ };
+ });
+ }).on('close', function () {
+ promise.then(function () {
+ if (!doc) {
+ var err = new Error('bad_request');
+ err.reason = 'no_doc_provided';
+ err.status = 400;
+ throw err;
+ }
+ mergeAttachments(doc, attachments);
+ return req.db.put(doc, opts);
+ }).then(
+ onPutOrPostResponse(req, res)
+ ).catch(function (err) {
+ utils.sendError(res, err);
+ });
+ });
+ form.parse(req);
+ });
+
+ // Create or update document that has an ID
+ app.put('/:db/:id(*)', utils.jsonParser, function (req, res) {
+
+ var opts = utils.makeOpts(req, req.query);
+
+ if (/^multipart\/related/.test(req.headers['content-type'])) {
+ var doc;
+ var promise = Promise.resolve();
+ var form = new multiparty.Form();
+ var attachments = {};
+ form.on('error', function (err) {
+ promise = promise.then(function () {
+ throw err;
+ });
+ }).on('field', function (_, field) {
+ doc = JSON.parse(field);
+ }).on('file', function (_, file) {
+ var type = file.headers['content-type'];
+ var filename = file.originalFilename;
+ promise = promise.then(function () {
+ return readFile(file.path);
+ }).then(function (body) {
+ attachments[filename] = {
+ content_type: type,
+ data: body
+ };
+ });
+ }).on('close', function () {
+ promise.then(function () {
+ mergeAttachments(doc, attachments);
+ return req.db.put(doc, opts);
+ }).then(
+ onPutOrPostResponse(req, res)
+ ).catch(function (err) {
+ utils.sendError(res, err);
+ });
+ });
+ form.parse(req);
+ } else {
+ // normal PUT
+ req.body._id = req.body._id || req.query.id;
+ if (!req.body._id) {
+ req.body._id = (!!req.params.id && req.params.id !== 'null') ?
+ req.params.id : null;
+ }
+ req.body._rev = getRev(req, req.body);
+ req.db.put(req.body, opts).then(
+ onPutOrPostResponse(req, res)
+ ).catch(function (err) {
+ utils.sendError(res, err);
+ });
+ }
+ });
+
+ function getRev(req, doc) {
+ return doc._rev || req.query.rev;
+ }
+
+ // Create a document
+ app.post('/:db', utils.jsonParser, function (req, res) {
+ var opts = utils.makeOpts(req, req.query);
+
+ req.body._id = req.body._id || uuids(1)[0];
+ req.db.put(req.body, opts, function (err, response) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+ utils.setLocation(res, req.params.db + '/' + response.id);
+ utils.sendJSON(res, 201, response);
+ });
+ });
+
+ // Retrieve a document
+ app.get('/:db/:id(*)', function (req, res) {
+ var opts = utils.makeOpts(req, req.query);
+
+ req.db.get(req.params.id, opts, function (err, doc) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+
+ utils.sendJSON(res, 200, doc);
+ });
+ });
+
+ // Delete a document
+ app.delete('/:db/:id(*)', function (req, res) {
+ var opts = utils.makeOpts(req, req.query);
+ opts.rev = getRev(req, {});
+
+ req.db.get(req.params.id, opts, function (err, doc) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+ req.db.remove(doc, opts, function (err, response) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+ utils.sendJSON(res, 200, response);
+ });
+ });
+ });
+
+ // Copy a document
+ app.copy('/:db/:id', function (req, res) {
+ var dest = req.get('Destination');
+ var rev, match;
+
+ if (!dest) {
+ return utils.sendJSON(res, 400, {
+ 'error': 'bad_request',
+ 'reason': 'Destination header is mandatory for COPY.'
+ });
+ }
+
+ if (isHTTP(dest) || isHTTPS(dest)) {
+ return utils.sendJSON(res, 400, {
+ 'error': 'bad_request',
+ 'reason': 'Destination URL must be relative.'
+ });
+ }
+
+ if ((match = /(.+?)\?rev=(.+)/.exec(dest))) {
+ dest = match[1];
+ rev = match[2];
+ }
+
+ var opts = utils.makeOpts(req, req.query);
+
+ req.db.get(req.params.id, opts, function (err, doc) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+ doc._id = dest;
+ doc._rev = rev;
+ req.db.put(doc, opts, function (err) {
+ if (err) {
+ return utils.sendError(res, err, 409);
+ }
+ utils.sendJSON(res, 201, {ok: true});
+ });
+ });
+ });
+};
+
+function isHTTP(url) {
+ return hasPrefix(url, 'http://');
+}
+
+function isHTTPS(url) {
+ return hasPrefix(url, 'https://');
+}
+
+function hasPrefix(haystack, needle) {
+ return haystack.substr(0, needle.length) === needle;
+}
diff --git a/packages/node_modules/express-pouchdb/lib/routes/fauxton.js b/packages/node_modules/express-pouchdb/lib/routes/fauxton.js
new file mode 100644
index 00000000..cb263266
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/fauxton.js
@@ -0,0 +1,22 @@
+"use strict";
+
+var express = require('express');
+var path = require('path');
+
+// "main" for pouchdb-fauxton is "www/index.html", so this returns "www"
+var FAUXTON_PATH = path.dirname(require.resolve('pouchdb-fauxton'));
+
+module.exports = function (app) {
+ app.get('/_utils', function (req, res) {
+ // We want to force /_utils to /_utils/ as this is the CouchDB behavior
+ // https://git.io/vDMOD#L78
+ // https://git.io/vDMOQ#L974
+ if (req.originalUrl === '/_utils') {
+ res.redirect(301, '/_utils/');
+ } else {
+ res.sendFile(path.normalize(FAUXTON_PATH + '/index.html'));
+ }
+ });
+
+ app.use('/_utils', express.static(FAUXTON_PATH));
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/find.js b/packages/node_modules/express-pouchdb/lib/routes/find.js
new file mode 100644
index 00000000..776d30ad
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/find.js
@@ -0,0 +1,35 @@
+"use strict";
+
+var utils = require('../utils');
+
+// TODO: /_security considerations? (What's CouchDB going to do there?)
+// Should be possible to add that on top by just upgrading
+// pouchdb-security though.
+
+module.exports = function (app) {
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ PouchDB.plugin(require('pouchdb-find'));
+ }
+ });
+
+ app.get('/:db/_index', function (req, res) {
+ req.db.getIndexes(utils.sendCallback(res));
+ });
+
+ app.post('/:db/_index', utils.jsonParser, function (req, res) {
+ req.db.createIndex(req.body, utils.sendCallback(res, 400));
+ });
+
+ app.delete('/:db/_index/:ddoc/:type/:name', function (req, res) {
+ req.db.deleteIndex({
+ ddoc: req.params.ddoc,
+ type: req.params.type,
+ name: req.params.name
+ }, utils.sendCallback(res));
+ });
+
+ app.post('/:db/_find', utils.jsonParser, function (req, res) {
+ req.db.find(req.body, utils.sendCallback(res, 400));
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/http-log.js b/packages/node_modules/express-pouchdb/lib/routes/http-log.js
new file mode 100644
index 00000000..fff94390
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/http-log.js
@@ -0,0 +1,53 @@
+"use strict";
+
+var onFinished = require('on-finished'),
+ utils = require('../utils'),
+ normalizeHeader = require('header-case-normalizer');
+
+module.exports = function (app) {
+ utils.requires(app, 'logging-infrastructure');
+
+ app.use(function (req, res, next) {
+ var method = req.method;
+ var rawPath = utils.rawPath(req);
+ var ip = req.ip;
+
+ var str = "'{0}' {1} {1,1} from \"{2}\"\n"
+ .replace('{0}', method)
+ .replace('{1}', rawPath)
+ .replace('{2}', ip);
+
+ str += 'Headers: ' + formatHeaders(req.headers);
+ app.couchLogger.debug(str);
+
+ onFinished(res, function () {
+ var msg = '{0} - - {1} {2} {3}'
+ .replace('{0}', ip)
+ .replace('{1}', method)
+ .replace('{2}', rawPath)
+ .replace('{3}', res.statusCode);
+ app.couchLogger.info(msg);
+ });
+ next();
+ });
+};
+
+function formatHeaders(headers) {
+ var result = "[";
+ var first = true;
+ var keys = Object.keys(headers);
+ keys.sort();
+ keys.forEach(function (key) {
+ if (first) {
+ first = false;
+ } else {
+ result += ',\n ';
+ }
+ var value = headers[key];
+
+ result += "{'{0}', {1}}"
+ .replace('{0}', normalizeHeader(key))
+ .replace('{1}', value);
+ });
+ return result;
+}
diff --git a/packages/node_modules/express-pouchdb/lib/routes/list.js b/packages/node_modules/express-pouchdb/lib/routes/list.js
new file mode 100644
index 00000000..46ed9725
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/list.js
@@ -0,0 +1,31 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ PouchDB.plugin(require('pouchdb-list'));
+ }
+ });
+
+ // Query design document list handler
+ function handler(req, res) {
+ var queryParts = [req.params.id, req.params.func];
+ if ('id2' in req.params) {
+ queryParts.push(req.params.id2);
+ }
+ queryParts.push(req.params.view);
+ var query = queryParts.join("/");
+ var cb = utils.sendCouchDBResp.bind(null, res);
+ req.db.list(query, req.couchDBReq, cb);
+ }
+ app.all('/:db/_design/:id/_list/:func/:view',
+ utils.couchDBReqMiddleware, handler
+ );
+ app.all('/:db/_design/:id/_list/:func/:id2/:view',
+ utils.couchDBReqMiddleware, handler
+ );
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/log.js b/packages/node_modules/express-pouchdb/lib/routes/log.js
new file mode 100644
index 00000000..25e05f24
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/log.js
@@ -0,0 +1,22 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'logging-infrastructure');
+
+ // Log
+ app.get('/_log', function (req, res) {
+ var bytes = req.query.bytes || 1000;
+ var offset = req.query.offset || 0;
+
+ app.couchLogger.getLog(bytes, offset, function (err, log) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+ res.set('Content-Type', 'text/plain; charset=utf-8');
+ res.set('Content-Length', log.length);
+ log.stream.pipe(res);
+ });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/replicate.js b/packages/node_modules/express-pouchdb/lib/routes/replicate.js
new file mode 100644
index 00000000..aa1a790f
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/replicate.js
@@ -0,0 +1,71 @@
+"use strict";
+
+var utils = require('../utils'),
+ extend = require('extend');
+
+module.exports = function (app) {
+ var histories = {};
+
+ // Replicate a database
+ app.post('/_replicate', utils.jsonParser, function (req, res) {
+
+ var source = req.body.source,
+ target = req.body.target,
+ opts = utils.makeOpts(req, {continuous: !!req.body.continuous});
+
+ if (req.body.filter) {
+ opts.filter = req.body.filter;
+ }
+ if (req.body.query_params) {
+ opts.query_params = req.body.query_params;
+ }
+
+ var startDate = new Date();
+ // TODO: actually use the _replicator database
+ req.PouchDB.replicate(source, target, opts).then(function (response) {
+
+ var historyObj = extend(true, {
+ start_time: startDate.toJSON(),
+ end_time: new Date().toJSON()
+ }, response);
+
+ var currentHistories = [];
+
+ if (!/^https?:\/\//.test(source)) {
+ histories[source] = histories[source] || [];
+ currentHistories.push(histories[source]);
+
+ }
+ if (!/^https?:\/\//.test(target)) {
+ histories[target] = histories[target] || [];
+ currentHistories.push(histories[target]);
+ }
+
+ currentHistories.forEach(function (history) {
+ // CouchDB caps history at 50 according to
+ // http://guide.couchdb.org/draft/replication.html
+ history.push(historyObj);
+ if (history.length > 50) {
+ history.splice(0, 1); // TODO: this is slow, use a stack instead
+ }
+ });
+
+ response.history = histories[source] || histories[target] || [];
+ if (!opts.continuous) {
+ utils.sendJSON(res, 200, response);
+ }
+ }, function (err) {
+ console.log(err);
+ console.log(err.stack);
+ if (!opts.continuous) {
+ utils.sendError(res, err);
+ }
+ });
+
+ // if continuous pull replication return 'ok' since we cannot wait
+ // for callback
+ if (opts.continuous) {
+ utils.sendJSON(res, 200, {ok: true});
+ }
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/revs-diff.js b/packages/node_modules/express-pouchdb/lib/routes/revs-diff.js
new file mode 100644
index 00000000..8e7e7aef
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/revs-diff.js
@@ -0,0 +1,13 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ // Revs Diff
+ app.post('/:db/_revs_diff', utils.jsonParser, function (req, res) {
+ var cb = utils.sendCallback(res);
+ req.db.revsDiff(req.body || {}, utils.makeOpts(req), cb);
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/rewrite.js b/packages/node_modules/express-pouchdb/lib/routes/rewrite.js
new file mode 100644
index 00000000..c39bbe03
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/rewrite.js
@@ -0,0 +1,64 @@
+"use strict";
+
+var utils = require('../utils');
+var REGEX = /\/([^\/]*)\/_design\/([^\/]*)\/_rewrite\/([^?]*)/;
+var cleanFilename = require('../clean-filename');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+ utils.requires(app, 'config-infrastructure');
+ utils.requires(app, 'logging-infrastructure');
+
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ PouchDB.plugin(require('pouchdb-rewrite'));
+ }
+ });
+ app.couchConfig.registerDefault('httpd', 'rewrite_limit', 100);
+
+ function rewriteMiddleware(req, res, next, rewritesSoFar) {
+ // Prefers regex over setting the first argument of app.use(), because
+ // the last makes req.url relative, which in turn makes most rewrites
+ // impossible.
+
+ var match = REGEX.exec(req.url);
+ if (!match) {
+ return next();
+ }
+ if (rewritesSoFar >= app.couchConfig.get('httpd', 'rewrite_limit')) {
+ return utils.sendJSON(res, 400, {
+ error: 'bad_request',
+ reason: "Exceeded rewrite recursion limit"
+ });
+ }
+
+ var dbName = cleanFilename(decodeURIComponent(match[1]));
+ utils.setDBOnReq(dbName, app.dbWrapper, req, res, function () {
+ var query = match[2] + "/" + match[3];
+ var opts = utils.expressReqToCouchDBReq(req);
+ // We don't know opts.path yet - that's the point.
+ delete opts.path;
+ req.db.rewriteResultRequestObject(query, opts, function (err, resp) {
+ if (err) {
+ return utils.sendError(res, err);
+ }
+
+ req.rawBody = resp.body;
+ req.cookies = resp.cookie;
+ req.headers = resp.headers;
+ req.method = resp.method;
+ req.url = "/" + resp.path.join("/");
+ req.query = resp.query;
+
+ app.couchLogger.debug('rewrite to "' + req.url + '"');
+ // Handle the newly generated request.
+ rewriteMiddleware(req, res, next, rewritesSoFar + 1);
+ });
+ });
+ }
+
+ // Query design document rewrite handler
+ app.use(function (req, res, next) {
+ rewriteMiddleware(req, res, next, 0);
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/root.js b/packages/node_modules/express-pouchdb/lib/routes/root.js
new file mode 100644
index 00000000..8bb04812
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/root.js
@@ -0,0 +1,42 @@
+"use strict";
+
+var pkg = require('../../package'),
+ utils = require('../utils'),
+ uuids = require('../uuids');
+
+module.exports = function (app) {
+ if (app.couchConfig) {
+ app.couchConfig.registerDefault('vendor', 'name', 'PouchDB authors');
+ app.couchConfig.registerDefault('vendor', 'version', pkg.version);
+ }
+ // Root route, return welcome message
+ app.get('/', function (req, res) {
+ var json = {
+ 'express-pouchdb': 'Welcome!',
+ 'version': pkg.version
+ };
+ function sendResp() {
+ utils.sendJSON(res, 200, json);
+ }
+ if (app.couchConfig) {
+ json.vendor = app.couchConfig.getSection('vendor');
+ getServerUUID(app.couchConfig, function (uuid) {
+ json.uuid = uuid;
+ sendResp();
+ });
+ } else {
+ sendResp();
+ }
+ });
+};
+
+function getServerUUID(config, cb) {
+ var uuid = config.get('couchdb', 'uuid');
+ if (uuid) {
+ return cb(uuid);
+ }
+ uuid = uuids(1)[0];
+ config.set('couchdb', 'uuid', uuid, function () {
+ cb(uuid);
+ });
+}
diff --git a/packages/node_modules/express-pouchdb/lib/routes/security.js b/packages/node_modules/express-pouchdb/lib/routes/security.js
new file mode 100644
index 00000000..6fe99846
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/security.js
@@ -0,0 +1,40 @@
+"use strict";
+
+var Security = require('pouchdb-security');
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ PouchDB.plugin(Security);
+ Security.installStaticSecurityMethods(PouchDB);
+ }
+ });
+
+ app.dbWrapper.registerWrapper(function (name, db, next) {
+ db.installSecurityMethods();
+ return next();
+ });
+
+ // Routing
+ ['/:db/*', '/:db'].forEach(function (url) {
+ app.use(url, function (req, res, next) {
+ req.db.getSecurity().then(function (secObj) {
+ req.couchSecurityObj = secObj;
+
+ next();
+ });
+ });
+ });
+
+ app.get('/:db/_security', function (req, res) {
+ req.db.getSecurity(utils.makeOpts(req), utils.sendCallback(res));
+ });
+
+ app.put('/:db/_security', utils.jsonParser, function (req, res) {
+ var cb = utils.sendCallback(res);
+ req.db.putSecurity(req.body || {}, utils.makeOpts(req), cb);
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/session-stub.js b/packages/node_modules/express-pouchdb/lib/routes/session-stub.js
new file mode 100644
index 00000000..c4e821a8
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/session-stub.js
@@ -0,0 +1,23 @@
+"use strict";
+
+var utils = require('../utils');
+
+// The PouchDB test suite needs some valid response at /_session. The
+// full session support is quite heavy (in terms of amount of code), so
+// this is a fallback that's enabled in the minimumForPouchDB profile.
+module.exports = function (app) {
+ if (app.includes.session) {
+ // no need for the stub if the real thing is going to be used.
+ return;
+ }
+
+ app.get('/_session', function (req, res) {
+ utils.sendJSON(res, 200, {
+ ok: true,
+ userCtx: {
+ name: null,
+ roles: ['_admin']
+ }
+ });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/session.js b/packages/node_modules/express-pouchdb/lib/routes/session.js
new file mode 100644
index 00000000..5fb72f3d
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/session.js
@@ -0,0 +1,40 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/authentication');
+ utils.requires(app, 'config-infrastructure');
+
+ function getUsersDB(req) {
+ return utils.getUsersDB(app, req.PouchDB);
+ }
+
+ app.get('/_session', function (req, res) {
+ utils.sendJSON(res, 200, req.couchSession);
+ });
+
+ function postHandler(req, res) {
+ var name = req.body.name;
+ var password = req.body.password;
+ getUsersDB(req).then(function (db) {
+ return db.multiUserLogIn(name, password);
+ }).then(function (resp) {
+ res.cookie('AuthSession', resp.sessionID, {httpOnly: true});
+ delete resp.sessionID;
+ if (req.query.next) {
+ utils.setLocation(res, req.query.next);
+ return res.status(302).end();
+ }
+ utils.sendJSON(res, 200, resp);
+ }).catch(function (err) {
+ utils.sendError(res, err);
+ });
+ }
+ app.post('/_session', utils.jsonParser, utils.urlencodedParser, postHandler);
+
+ app.delete('/_session', function (req, res) {
+ res.clearCookie('AuthSession');
+ utils.sendJSON(res, 200, {ok: true});
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/show.js b/packages/node_modules/express-pouchdb/lib/routes/show.js
new file mode 100644
index 00000000..c66a694d
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/show.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ PouchDB.plugin(require('pouchdb-show'));
+ }
+ });
+
+ // Query design document show handler
+ function handler(req, res) {
+ var queryBase = [req.params.id, req.params.func].join("/");
+ var query = queryBase + req.params[0];
+ var cb = utils.sendCouchDBResp.bind(null, res);
+ req.db.show(query, req.couchDBReq, cb);
+ }
+ app.all('/:db/_design/:id/_show/:func*',
+ utils.couchDBReqMiddleware, handler
+ );
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/special-test-auth.js b/packages/node_modules/express-pouchdb/lib/routes/special-test-auth.js
new file mode 100644
index 00000000..cf763778
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/special-test-auth.js
@@ -0,0 +1,57 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ // inspired by special_test_authentication_handler/1 in:
+ // couchdb-couch/src/couch_httpd_auth.erl
+ utils.requires(app, 'config-infrastructure');
+
+ app.use(function (req, res, next) {
+ var value = app.couchConfig.get('httpd', 'authentication_handlers') || "";
+ if (value.indexOf('special_test_authentication_handler') === -1) {
+ return next();
+ }
+
+ var header = req.get('WWW-Authenticate') || "";
+ if (header.indexOf('X-Couch-Test-Auth ') !== 0) {
+ // No X-Couch-Test-Auth credentials sent, give admin access so the
+ // previous authentication can be restored after the test
+ return setSession(req, {
+ roles: ['_admin']
+ }, next);
+ }
+ var namePass = header.slice('X-Couch-Test-Auth '.length);
+ var name = namePass.split(':')[0];
+ var pass = namePass.split(':')[1];
+
+ var expectedPw = {
+ "Jan Lehnardt": "apple",
+ "Christopher Lenz": "dog food",
+ "Noah Slater": "biggiesmalls endian",
+ "Chris Anderson": "mp3",
+ "Damien Katz": "pecan pie"
+ }[name];
+ if (expectedPw !== pass) {
+ return utils.sendJSON(res, 401, {
+ error: 'unauthorized',
+ reason: "Name or password is incorrect."
+ });
+ }
+ setSession(req, {
+ name: name
+ }, next);
+ });
+};
+
+function setSession(req, userCtx, next) {
+ if (!userCtx.roles) {
+ userCtx.roles = [];
+ }
+ req.couchSession = {
+ ok: true,
+ userCtx: userCtx,
+ authenticated: 'special',
+ };
+ next();
+}
diff --git a/packages/node_modules/express-pouchdb/lib/routes/stats.js b/packages/node_modules/express-pouchdb/lib/routes/stats.js
new file mode 100644
index 00000000..4e36edc5
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/stats.js
@@ -0,0 +1,13 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ // Stats (stub for now)
+ app.get('/_stats', function (req, res) {
+ // TODO: implement
+ utils.sendJSON(res, 200, {
+ 'pouchdb-server': 'has not impemented _stats yet. PRs welcome!'
+ });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/temp-views.js b/packages/node_modules/express-pouchdb/lib/routes/temp-views.js
new file mode 100644
index 00000000..e482b85d
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/temp-views.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var utils = require('../utils'),
+ extend = require('extend'),
+ evalSafely = require('../eval-safely');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ // Temp Views
+ app.all('/:db/_temp_view', utils.jsonParser, function (req, res, next) {
+ if (req.method !== 'GET' && req.method !== 'POST' &&
+ req.method !== 'HEAD') {
+ return next();
+ }
+ // Check that the request body, if present, is an object.
+ if (req.body && (typeof req.body !== 'object' || Array.isArray(req.body))) {
+ return utils.sendJSON(res, 400, {
+ reason: "Request body must be a JSON object",
+ error: 'bad_request'
+ });
+ }
+ if (req.body.map) {
+ req.body.map = evalSafely(req.body.map);
+ }
+ var opts = utils.makeOpts(req, extend({}, req.body, req.query));
+ req.db.query(req.body, opts, utils.sendCallback(res));
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/update.js b/packages/node_modules/express-pouchdb/lib/routes/update.js
new file mode 100644
index 00000000..b701d54b
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/update.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ PouchDB.plugin(require('pouchdb-update'));
+ }
+ });
+
+ // Query design document update handler
+ function handler(req, res) {
+ var baseQuery = [req.params.id, req.params.func].join("/");
+ var query = baseQuery + req.params[0];
+ var cb = utils.sendCouchDBResp.bind(null, res);
+ req.db.update(query, req.couchDBReq, cb);
+ }
+ app.all('/:db/_design/:id/_update/:func*',
+ utils.couchDBReqMiddleware, handler
+ );
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/uuids.js b/packages/node_modules/express-pouchdb/lib/routes/uuids.js
new file mode 100644
index 00000000..573ccd85
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/uuids.js
@@ -0,0 +1,28 @@
+"use strict";
+
+var utils = require('../utils'),
+ uuids = require('../uuids');
+
+module.exports = function (app) {
+ utils.requires(app, 'config-infrastructure');
+
+ app.couchConfig.registerDefault('uuids', 'max_count', 1000);
+
+ // Generate UUIDs
+ app.all('/_uuids', utils.restrictMethods(["GET"]), function (req, res) {
+ res.set({
+ "Cache-Control": "must-revalidate, no-cache",
+ "Pragma": "no-cache"
+ });
+ var count = typeof req.query.count === 'number' ? req.query.count : 1;
+ if (count > app.couchConfig.get('uuids', 'max_count')) {
+ return utils.sendJSON(res, 403, {
+ error: "forbidden",
+ reason: "count parameter too large"
+ });
+ }
+ utils.sendJSON(res, 200, {
+ uuids: uuids(count)
+ });
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/vhosts.js b/packages/node_modules/express-pouchdb/lib/routes/vhosts.js
new file mode 100644
index 00000000..b4dfdf14
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/vhosts.js
@@ -0,0 +1,27 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'config-infrastructure');
+ utils.requires(app, 'logging-infrastructure');
+
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ require('pouchdb-vhost')(PouchDB);
+ }
+ });
+
+ // Query design document rewrite handler
+ app.use(function (req, res, next) {
+ var couchReq = utils.expressReqToCouchDBReq(req);
+ var vhosts = app.couchConfig.getSection('vhosts');
+ var newUrl = req.PouchDB.resolveVirtualHost(couchReq, vhosts);
+
+ if (newUrl !== req.url) {
+ req.url = newUrl;
+ app.couchLogger.debug("Vhost Target: '\"" + newUrl + "\"'");
+ }
+ next();
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/view-cleanup.js b/packages/node_modules/express-pouchdb/lib/routes/view-cleanup.js
new file mode 100644
index 00000000..08fa2f45
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/view-cleanup.js
@@ -0,0 +1,12 @@
+"use strict";
+
+var utils = require('../utils');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ // View Cleanup
+ app.post('/:db/_view_cleanup', utils.jsonParser, function (req, res) {
+ req.db.viewCleanup(utils.makeOpts(req), utils.sendCallback(res));
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/routes/views.js b/packages/node_modules/express-pouchdb/lib/routes/views.js
new file mode 100644
index 00000000..08bdaf6e
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/routes/views.js
@@ -0,0 +1,28 @@
+"use strict";
+
+var utils = require('../utils'),
+ extend = require('extend');
+
+module.exports = function (app) {
+ utils.requires(app, 'routes/db');
+
+ app.all('/:db/_design/:id/_view/:view', utils.jsonParser,
+ function (req, res, next) {
+ if (req.method !== 'GET' && req.method !== 'POST' &&
+ req.method !== 'HEAD') {
+ return next();
+ }
+
+ // Check that the request body, if present, is an object.
+ if (req.body && (typeof req.body !== 'object' || Array.isArray(req.body))) {
+ return utils.sendJSON(res, 400, {
+ reason: "Request body must be a JSON object",
+ error: 'bad_request'
+ });
+ }
+
+ var query = req.params.id + '/' + req.params.view;
+ var opts = utils.makeOpts(req, extend({}, req.body, req.query));
+ req.db.query(query, opts, utils.sendCallback(res));
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/lib/utils.js b/packages/node_modules/express-pouchdb/lib/utils.js
new file mode 100644
index 00000000..e46b3265
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/utils.js
@@ -0,0 +1,308 @@
+"use strict";
+
+var pathResolve = require('path').resolve;
+var rawBody = require('raw-body');
+var Promise = require('pouchdb-promise');
+var mkdirp = require('mkdirp');
+var cleanFilename = require('./clean-filename');
+var getOrCreateDB = require('./create-or-delete-dbs').getOrCreateDB;
+
+//shared middleware
+
+exports.maxDocumentSizeDefault = 4 * 1024 * 1024 * 1024;
+
+function buildJSONParser(tolerateInvalid) {
+ return function (req, res, next) {
+ var limit;
+ if (req.app.couchConfig) {
+ limit = req.app.couchConfig.get('couchdb', 'max_document_size');
+ } else {
+ limit = exports.maxDocumentSizeDefault;
+ }
+ var jsonParser = require('body-parser').json({limit: limit});
+ jsonParser(req, res, function (err) {
+ if (err) {
+ if (err.status === 400) {
+ if (!tolerateInvalid) {
+ return exports.sendJSON(res, 400, {
+ error: "bad_request",
+ reason: "invalid_json"
+ });
+ }
+ } else if (err.status === 413) {
+ return exports.sendJSON(res, 413, {
+ error: "too_large",
+ reason: "the request entity is too large"
+ });
+ } else {
+ return exports.sendError(res, err, 500);
+ }
+ }
+ next();
+ });
+ };
+}
+
+exports.jsonParser = buildJSONParser(false);
+exports.urlencodedParser = require('body-parser').urlencoded({extended: false});
+
+exports.makeOpts = function (req, startOpts) {
+ // fill in opts so it can be used by authorisation logic
+ var opts = startOpts || {};
+ opts.userCtx = (req.couchSession || {}).userCtx;
+ opts.secObj = req.couchSecurityObj;
+
+ if (opts.userCtx) {
+ // add db name to userCtx (#218)
+ var dbname = req.db && req.db._db_name;
+ if (dbname) {
+ opts.userCtx.db = decodeURIComponent(dbname);
+ }
+ }
+
+ return opts;
+};
+
+exports.setDBOnReq = function (dbName, dbWrapper, req, res, next) {
+ dbName = cleanFilename(dbName);
+ req.PouchDB.allDbs(function (err, dbs) {
+ if (err) {
+ return exports.sendError(res, err);
+ }
+
+ if (dbs.indexOf(dbName) === -1) {
+ return exports.sendJSON(res, 404, {
+ error: 'not_found',
+ reason: 'no_db_file'
+ });
+ }
+ getOrCreateDB(req.PouchDB, dbName).then(function (db) {
+ // temporary workaround for https://github.com/pouchdb/pouchdb/issues/5668
+ // see also https://github.com/pouchdb/express-pouchdb/issues/274
+ if (/\//.test(dbName)) {
+ var path = db.__opts.prefix ? db.__opts.prefix + dbName : dbName;
+ mkdirp.sync(pathResolve(path));
+ }
+
+ dbWrapper.wrap(dbName, db).then(function () {
+ req.db = db;
+ next();
+ });
+ });
+ });
+};
+
+exports.rawPath = function (req) {
+ var rawPath = req.originalUrl.slice(req.baseUrl.length);
+ if (rawPath[0] !== '/') {
+ rawPath = '/' + rawPath;
+ }
+ return rawPath;
+};
+
+exports.expressReqToCouchDBReq = function (req) {
+ var rawPath = exports.rawPath(req);
+ return exports.makeOpts(req, {
+ body: (req.rawBody ? req.rawBody.toString() : "") || "undefined",
+ cookie: req.cookies || {},
+ form: req.couchDBForm || {},
+ json: req.couchDBJSON || {},
+ headers: req.headers,
+ method: req.method,
+ path: splitPath(req.url.split("?")[0]),
+ peer: req.ip,
+ query: req.query,
+ requested_path: splitPath(rawPath),
+ raw_path: rawPath,
+ });
+};
+
+var sloppyJSONParser = buildJSONParser(true);
+
+exports.couchDBReqMiddleware = function (req, res, next) {
+ var i = 0;
+ function cb() {
+ i++;
+ if (i === 2) {
+ req.couchDBReq = exports.expressReqToCouchDBReq(req);
+ next();
+ }
+ }
+ exports.parseRawBody(req, res, cb);
+
+ sloppyJSONParser(req, res, function () {
+ req.couchDBJSON = req.body;
+ delete req.body;
+ exports.urlencodedParser(req, res, function () {
+ req.couchDBForm = req.body;
+ delete req.body;
+
+ cb();
+ });
+ }, true);
+};
+
+function splitPath(path) {
+ return path.split("/").filter(function (part) {
+ return part;
+ });
+}
+
+exports.sendCouchDBResp = function (res, err, couchResp) {
+ if (err) {
+ return exports.sendError(res, err);
+ }
+
+ for (var header in couchResp.headers) {
+ if (couchResp.headers.hasOwnProperty(header)) {
+ // use setHeader instead of res.set to prevent modification of
+ // headers by express.
+ res.setHeader(header, couchResp.headers[header]);
+ }
+ }
+ var body;
+ if (couchResp.base64) {
+ body = new Buffer(couchResp.base64, 'base64');
+ } else {
+ //convert to buffer so express doesn't add the ; charset=utf-8 if it
+ //isn't already there by now. No performance problem: express does
+ //this internally anyway.
+ body = new Buffer(couchResp.body, 'utf-8');
+ }
+ res.status(couchResp.code).send(body);
+};
+
+exports.sendError = function (res, err, baseStatus) {
+ var status = err.status || baseStatus || 500;
+
+ // last argument is optional
+ if (err.name && err.message) {
+ if (err.name === 'Error' || err.name === 'TypeError') {
+ if (err.message.indexOf("Bad special document member") !== -1) {
+ err.name = 'doc_validation';
+ // add more clauses here if the error name is too general
+ } else {
+ err.name = 'bad_request';
+ }
+ }
+ err = {
+ error: err.name,
+ reason: err.message
+ };
+ }
+ exports.sendJSON(res, status, err);
+};
+
+function setJsonOrPlaintext(res) {
+ // Send the client application/json if they asked for it,
+ // else send text/plain; charset=utf-8. This mimics CouchDB.
+ var type = res.req.accepts(['text', 'json']);
+ if (type === "json") {
+ res.setHeader('Content-Type', 'application/json');
+ } else {
+ //adds ; charset=utf-8
+ res.type('text/plain');
+ }
+}
+
+function jsonToBuffer(body) {
+ //convert to buffer so express doesn't add the ; charset=utf-8 if it
+ //isn't already there by now. No performance problem: express does
+ //this internally anyway.
+ return new Buffer(JSON.stringify(body) + "\n", 'utf8');
+}
+
+exports.setJsonOrPlaintext = setJsonOrPlaintext;
+
+exports.writeJSON = function (res, body) {
+ res.write(jsonToBuffer(body));
+};
+
+exports.sendJSON = function (res, status, body) {
+ res.status(status);
+ setJsonOrPlaintext(res);
+ res.send(jsonToBuffer(body));
+};
+
+exports.sendCallback = function (res, errCode, successCode) {
+ return function (err, response) {
+ if (err) {
+ return exports.sendError(res, err, errCode);
+ }
+ exports.sendJSON(res, successCode || 200, response);
+ };
+};
+
+exports.setLocation = function (res, path) {
+ //CouchDB location headers are always non-relative.
+ var loc = (
+ res.req.protocol +
+ '://' +
+ ((res.req.hostname === '127.0.0.1') ?
+ '' : res.req.subdomains.join('.') + '.') +
+ res.req.hostname +
+ ':' + res.req.socket.localPort +
+ '/' + path
+ );
+ res.location(loc);
+};
+
+exports.restrictMethods = function (methods) {
+ return function (req, res, next) {
+ if (methods.indexOf(req.method) === -1) {
+ res.set("Allow", methods.join(", "));
+ return exports.sendJSON(res, 405, {
+ error: 'method_not_allowed',
+ reason: "Only " + methods.join(",") + " allowed"
+ });
+ }
+ next();
+ };
+};
+
+exports.parseRawBody = function (req, res, next) {
+ // Custom bodyParsing because bodyParser chokes
+ // on 'malformed' requests, and also because we need the
+ // rawBody for attachments
+ rawBody(req, {
+ length: req.headers['content-length']
+ }, function (err, string) {
+ if (err) {
+ return next(err);
+ }
+ req.rawBody = string;
+ next();
+ });
+};
+
+exports.getUsersDBName = function (app) {
+ return app.couchConfig.get('couch_httpd_auth', 'authentication_db');
+};
+
+exports.getUsersDB = function (app, PouchDB) {
+ var name = exports.getUsersDBName(app);
+ return app.dbWrapper.wrap(name, new PouchDB(name));
+};
+
+exports.requires = function (app, part) {
+ if (!app.includes[part]) {
+ var msg = part + (
+ " is required, but won't be active. Please adjust your " +
+ "opts.profile/opts.profileDiff accordingly."
+ );
+ throw new Error(msg);
+ }
+};
+
+exports.callAsyncRecursive = function (funcs, handleCall) {
+ var i = 0;
+ function next() {
+ var func = funcs[i];
+ if (typeof func === 'undefined') {
+ return Promise.resolve();
+ }
+ i++;
+ return handleCall(func, next);
+ }
+ return next();
+};
diff --git a/packages/node_modules/express-pouchdb/lib/uuids.js b/packages/node_modules/express-pouchdb/lib/uuids.js
new file mode 100644
index 00000000..457b3460
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/uuids.js
@@ -0,0 +1,12 @@
+"use strict";
+
+var uuid = require('uuid/v4');
+
+module.exports = function generate(limit) {
+ var output = [];
+ var i = -1;
+ while (++i < limit) {
+ output.push(uuid());
+ }
+ return output;
+};
diff --git a/packages/node_modules/express-pouchdb/lib/validation.js b/packages/node_modules/express-pouchdb/lib/validation.js
new file mode 100644
index 00000000..4fd621e9
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/lib/validation.js
@@ -0,0 +1,15 @@
+"use strict";
+
+module.exports = function enableValidation(app) {
+ app.daemonManager.registerDaemon({
+ start: function (PouchDB) {
+ PouchDB.plugin(require('pouchdb-validation'));
+ }
+ });
+
+ app.dbWrapper.registerWrapper(function (name, db, next) {
+ db.installValidationMethods();
+
+ next();
+ });
+};
diff --git a/packages/node_modules/express-pouchdb/package.json b/packages/node_modules/express-pouchdb/package.json
new file mode 100644
index 00000000..df1d8bf6
--- /dev/null
+++ b/packages/node_modules/express-pouchdb/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "express-pouchdb",
+ "description": "Express submodule with a CouchDB-style REST interface to PouchDB.",
+ "homepage": "https://github.com/pouchdb/pouchdb-server",
+ "license": "Apache-2.0",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/pouchdb/pouchdb-server.git"
+ },
+ "main": "./lib/index.js",
+ "files": [
+ "lib"
+ ]
+}
diff --git a/packages/node_modules/pouchdb-server/LICENSE b/packages/node_modules/pouchdb-server/LICENSE
new file mode 100644
index 00000000..f6cd2bc8
--- /dev/null
+++ b/packages/node_modules/pouchdb-server/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/node_modules/pouchdb-server/README.md b/packages/node_modules/pouchdb-server/README.md
new file mode 100644
index 00000000..7445b7ee
--- /dev/null
+++ b/packages/node_modules/pouchdb-server/README.md
@@ -0,0 +1,15 @@
+# express-pouchdb
+
+[![Build Status](https://travis-ci.org/pouchdb/pouchdb-server.svg)](https://travis-ci.org/pouchdb/pouchdb-server)
+
+A drop-in replacement for CouchDB, built on Node.js and PouchDB.
+
+### Source
+
+PouchDB Server and its sub-packages are distributed as a [monorepo](https://github.com/babel/babel/blob/master/doc/design/monorepo.md).
+
+For a full list of packages, see [the GitHub source](https://github.com/pouchdb/pouchdb-server/tree/master/packages/node_modules).
+
+## License
+
+The Apache 2 License. See [the LICENSE file](https://github.com/pouchdb/pouchdb-server/blob/master/LICENSE) for more information.
\ No newline at end of file
diff --git a/packages/node_modules/pouchdb-server/bin/pouchdb-server b/packages/node_modules/pouchdb-server/bin/pouchdb-server
new file mode 100755
index 00000000..cec1dd28
--- /dev/null
+++ b/packages/node_modules/pouchdb-server/bin/pouchdb-server
@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+
+// The only reason this file exists is for backwards compatibility, because
+// otherwise we break folks doing:
+// `./node_modules/pouchdb-server/bin/pouchdb-server`.
+
+require('../lib/index.js');
\ No newline at end of file
diff --git a/packages/node_modules/pouchdb-server/favicon.ico b/packages/node_modules/pouchdb-server/favicon.ico
new file mode 100644
index 00000000..99c00b83
Binary files /dev/null and b/packages/node_modules/pouchdb-server/favicon.ico differ
diff --git a/lib/cors.js b/packages/node_modules/pouchdb-server/lib/cors.js
similarity index 100%
rename from lib/cors.js
rename to packages/node_modules/pouchdb-server/lib/cors.js
diff --git a/lib/customLevelAdapter.js b/packages/node_modules/pouchdb-server/lib/customLevelAdapter.js
similarity index 100%
rename from lib/customLevelAdapter.js
rename to packages/node_modules/pouchdb-server/lib/customLevelAdapter.js
diff --git a/lib/index.js b/packages/node_modules/pouchdb-server/lib/index.js
similarity index 100%
rename from lib/index.js
rename to packages/node_modules/pouchdb-server/lib/index.js
diff --git a/lib/logging.js b/packages/node_modules/pouchdb-server/lib/logging.js
similarity index 100%
rename from lib/logging.js
rename to packages/node_modules/pouchdb-server/lib/logging.js
diff --git a/packages/node_modules/pouchdb-server/package.json b/packages/node_modules/pouchdb-server/package.json
new file mode 100644
index 00000000..76309eef
--- /dev/null
+++ b/packages/node_modules/pouchdb-server/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "pouchdb-server",
+ "description": "A drop-in replacement for CouchDB, built on Node.js and PouchDB",
+ "homepage": "https://github.com/pouchdb/pouchdb-server",
+ "license": "Apache-2.0",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/pouchdb/pouchdb-server.git"
+ },
+ "main": "lib/index.js",
+ "files": [
+ "lib",
+ "bin/pouchdb-server",
+ "favicon.ico"
+ ],
+ "bin": {
+ "pouchdb-server": "./bin/pouchdb-server"
+ }
+}
diff --git a/tests/express-pouchdb/test.js b/tests/express-pouchdb/test.js
new file mode 100644
index 00000000..0d4594a4
--- /dev/null
+++ b/tests/express-pouchdb/test.js
@@ -0,0 +1,262 @@
+"use strict";
+
+/*globals before */
+
+var buildApp = require('../../packages/node_modules/express-pouchdb'),
+ PouchDB = require('pouchdb'),
+ express = require('express'),
+ request = require('supertest'),
+ Promise = require('bluebird'),
+ fse = Promise.promisifyAll(require('fs-extra')),
+ memdown = require('memdown');
+
+var TEST_DATA = __dirname + '/testdata/';
+var LARGE_TIMEOUT = 5000;
+
+var expressApp, expressApp2;
+
+var customApp = buildApp(PouchDB.defaults({
+ db: memdown,
+ prefix: 'c'
+}), {
+ mode: 'custom',
+ overrideMode: {
+ include: ['routes/404']
+ }
+});
+
+var coreApp = buildApp(PouchDB.defaults({
+ db: memdown,
+ prefix: 'd'
+}), {
+ mode: 'minimumForPouchDB',
+ overrideMode: {
+ include: ['routes/fauxton']
+ }
+});
+
+var inMemoryConfigApp = buildApp(PouchDB.defaults({
+ db: memdown,
+ prefix: 'e'
+}), {
+ inMemoryConfig: true
+});
+
+before(function (done) {
+ this.timeout(LARGE_TIMEOUT);
+ cleanUp().then(function () {
+ return fse.mkdirsAsync(TEST_DATA + 'a');
+ }).then(function () {
+ return fse.mkdirsAsync(TEST_DATA + 'b');
+ }).then(function () {
+ expressApp = buildApp(PouchDB.defaults({
+ prefix: TEST_DATA + 'a/'
+ }));
+ expressApp2 = buildApp(PouchDB.defaults({
+ prefix: TEST_DATA + 'b/',
+ }), {
+ configPath: TEST_DATA + 'b-config.json',
+ logPath: TEST_DATA + 'b-log.txt'
+ });
+ done();
+ }).catch(done);
+});
+
+after(function (done) {
+ cleanUp().then(function () {
+ done();
+ }).catch(done);
+});
+
+function cleanUp() {
+ return Promise.all([
+ fse.removeAsync(TEST_DATA),
+ fse.removeAsync('./config.json'),
+ fse.removeAsync('./log.txt')
+ ]);
+}
+
+describe('config', function () {
+ it('should not create empty config file', function (done) {
+ fse.exists('./config.json', function (exists) {
+ if (exists) {
+ return done(new Error("config.json should not have been created!"));
+ }
+ done();
+ });
+ });
+ it('should support in memory config', function (done) {
+ // make sure the file is written to disk.
+ inMemoryConfigApp.couchConfig.set('demo', 'demo', true, function () {
+ fse.exists('./config.json', function (exists) {
+ if (exists) {
+ return done(new Error("config.json exists!"));
+ }
+ done();
+ });
+ });
+ });
+ it('should have ./config.json as default config path', function (done) {
+ expressApp.couchConfig.set('demo', 'demo', true, function () {
+ fse.exists('./config.json', function (exists) {
+ if (!exists) {
+ return done(new Error("config.json doesn't exist!"));
+ }
+ done();
+ });
+ });
+ });
+ it('should support setting a different config path', function (done) {
+ // make sure the file is written to disk.
+ expressApp2.couchConfig.set('demo', 'demo', true, function () {
+ fse.exists(TEST_DATA + 'b-config.json', function (exists) {
+ if (!exists) {
+ return done(new Error("b-config.json doesn't exist!"));
+ }
+ done();
+ });
+ });
+ });
+ it('should support setting a different log path', function (done) {
+ // make sure the file is written to disk.
+ expressApp2.couchConfig.set('demo', 'demo', true, function () {
+ fse.exists(TEST_DATA + 'b-log.txt', function (exists) {
+ if (!exists) {
+ return done(new Error("b-log.txt doesn't exist!"));
+ }
+ done();
+ });
+ });
+ });
+ it('should support externally adding a default', function (done) {
+ expressApp.couchConfig.registerDefault('a', 'b', 'c');
+ request(expressApp)
+ .get('/_config')
+ .expect(200)
+ .expect(function (res) {
+ var a = JSON.parse(res.text).a;
+ if (!(typeof a === "object" && a.b === "c")) {
+ return "Default not shown";
+ }
+ })
+ .end(done);
+ });
+ it('should support externally getting a config value', function (done) {
+ request(expressApp)
+ .put('/_config/test/a')
+ .send('"b"')
+ .expect(200)
+ .end(function () {
+ if (expressApp.couchConfig.get('test', 'a') !== 'b') {
+ return done(new Error("Can't read setting that's just been set"));
+ }
+ done();
+ });
+ });
+ it('should support external listeners to a config change', function (done) {
+ var changed = false;
+ expressApp.couchConfig.on('test2.a', function () {
+ changed = true;
+ });
+ request(expressApp)
+ .put('/_config/test2/a')
+ .send('"b"')
+ .expect(200)
+ .expect(function () {
+ if (!changed) {
+ return "Didn't get notice of the setting change";
+ }
+ })
+ .end(done);
+ });
+});
+
+var prefixes = ['/', '/db/'];
+
+prefixes.forEach(function (prefix) {
+ describe('basics for ' + prefix, function () {
+ it('GET / should respond with a welcome page', function (done) {
+ var app = express();
+ app.use(prefix, expressApp);
+
+ testWelcome(app, done, prefix);
+ });
+ });
+});
+
+function testWelcome(app, done, path) {
+ request(app)
+ .get(path)
+ .expect(200)
+ .expect(function (res) {
+ if (!/Welcome!/.test(res.text)) {
+ return "No 'Welcome!' in response";
+ }
+ })
+ .end(done);
+}
+
+describe('modes', function () {
+ it('should always return a 404 in our custom configuration', function (done) {
+ request(customApp)
+ .get('/')
+ .expect(404)
+ .expect(function (res) {
+ if (JSON.parse(res.text).error !== 'not_found') {
+ return "Wrong response body";
+ }
+ })
+ .end(done);
+ });
+ it('should generate a functioning core app', function (done) {
+ testWelcome(coreApp, done, '/');
+ });
+ it('should throw an error when given an invalid mode', function () {
+ assertException(function () {
+ buildApp(PouchDB, {mode: 'unexisting-mode'});
+ }, /Unknown mode: unexisting-mode/);
+ });
+ it('should throw an error when a not included part is excluded', function () {
+ assertException(function () {
+ buildApp(PouchDB, {overrideMode: {exclude: ['abc']}});
+ }, /exclude contains the not included part 'abc'/);
+ });
+ it('should throw an error when an unknown part is included', function () {
+ assertException(function () {
+ buildApp(PouchDB, {overrideMode: {include: ['abc']}});
+ }, /include contains the unknown part 'abc'/);
+ });
+});
+
+describe('redirects', function () {
+ it('GET /_utils should redirect to /_utils/', function (done) {
+ request(coreApp)
+ .get('/_utils')
+ .expect(301)
+ .end(done);
+ });
+ it('GET /_utils/ should return fauxton', function (done) {
+ request(coreApp)
+ .get('/_utils/')
+ .expect(200)
+ .expect(function (res) {
+ if (!/PouchDB Server<\/title>/.test(res.text)) {
+ return "No 'PouchDB Server ' in response";
+ }
+ })
+ .end(done);
+ });
+});
+
+function assertException(func, re) {
+ var e;
+ try {
+ func();
+ } catch (err) {
+ if (re.test(err.toString())) {
+ return;
+ }
+ e = err;
+ }
+ throw (e || new Error('no error was thrown'));
+}
diff --git a/tests/mocha.opts b/tests/mocha.opts
new file mode 100644
index 00000000..d52764dd
--- /dev/null
+++ b/tests/mocha.opts
@@ -0,0 +1,2 @@
+--colors
+-R spec