pouchdb Documentation

Kangaroo: - PouchDB is a pocket-sized database.

17,617 stars JavaScript JavaScript 71 files · ~125,779 tokens #couchdb#database#hacktoberfest#javascript#pouchdb
RAW Doc

Technical Documentation: apache/pouchdb

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)

[Apache PouchDB](https://pouchdb.apache.org/) – The Database that Syncs!

Apache PouchDB is an open-source JavaScript database inspired by Apache CouchDB that is designed to run well within the browser.

PouchDB was created to help web developers build applications that work as well offline as they do online.

Apache PouchDB is currently undergoing Incubation at the Apache Software Foundation. Read the ASF Disclaimer for incubator projects.

Using PouchDB

To get started using PouchDB, check out the web site and API documentation.

Getting Help

The PouchDB community is active in #pouchdb on the CouchDB Slack, in the Google Groups mailing list, and on StackOverflow. Or you can mastodon @pouchdb!

If you think you've found a bug in PouchDB, please write a reproducible test case and file a Github issue.

Prerelease builds

If you like to live on the bleeding edge, you can build PouchDB from source using these steps:

text
git clone https://github.com/pouchdb/pouchdb.git
cd pouchdb
npm install

After running these steps, the browser build can be found in packages/node_modules/pouchdb/dist/pouchdb.js.

Changelog

PouchDB follows semantic versioning. To see a changelog with all PouchDB releases, check out the Github releases page.

For a concise list of breaking changes, there's the wiki list of breaking changes.

Keep in mind that PouchDB is auto-migrating, so a database created in 1.0.0 will still work if you open it in 4.0.0+. Any release containing a migration is clearly marked in the release notes.

Contributing

We're always looking for new contributors! If you'd like to try your hand at writing code, writing documentation, designing the website, writing a blog post, or answering questions on StackOverflow, then we'd love to have your input.

If you have a pull request that you'd like to submit, please read the contributing guide for info on style, commit message format, and other (slightly!) nitpicky things like that. PouchDB is heavily tested, so you'll also want to check out the testing guide.

The pouchdb.com Website

The documentation website at pouchdb.com is also part of this repo, in the /docs folder. It has its own /docs/WEBSITE_README.md

Website deployment happens automatically once the changes land in master.


Overview (`docs/WEBSITE_README.md`)

pouchdb.com

This is the PouchDB documentation site, built with 11ty, a node.js static site generator.

To run the site locally, in the repo root:

sh
$ npm i
$ npm run build-site

This starts a development server. It watches your files and should rebuild automatically in most cases, but it doesn’t reload the browser for you.

You can have full hot reloading, but this doesn’t catch changes to the styles (LESS files):

sh
$ npm run build11 -- --serve

To deactivate the dev server and run a regular build without the file watcher:

sh
$ BUILD=1 npm run build-site

Testing Serviceworker

sh
$ npm i
$ npm run build-site

By default, SW will only cache the pages you’ve seen plus offline.html, which it will show whenever you try to access a page offline that hasn’t been cached.

To test the "Content updated, reload now?" toast that indicates that a new version of the site is available, run the dev server, make a change to the site (e.g. change some text somewhere), then navigate once or reload the page with a normal (not hard) reload. The update toast should show. When you click it, the page and the serviceworker cache will be updated.

PouchDB Version Updates

If you’re releasing a new PouchDB version, the site also needs some updates:

  1. Replace docs/static/js/pouchdb.min.js with your newly built version of the same file (this is the PouchDB that is hosted by the website itself and available in the browser dev console).
  2. Update the version key in docs/_data/site.js to the new PouchDB version.

Then deploy the webite.

Website Deployment

Currently, the website is deployed manually by members of the team. In the near future, any website changes merged into master will be deployed automatically.

Technology Choices

The PouchDB website is over a decade old and some parts of it are somewhat out of date by now, but they haven’t been updated for good reasons. The most significant legacy bit is Bootstrap 3.11 in the LESS version, which depends on JQuery for interactivity. There is currently zero benefit to, for example, migrating to SASS/SCSS here or trying to untangle JQuery from Bootstrap in order to use vanilla JS. A design update is on the horizon, and we’ll take that as an opportunity to remove Bootstrap, LESS and JQuery alltogether and replace everything with vanilla web standard JS and CSS, and maybe a slim UI library for base styling. Then we can also get rid of /bin/build-site.js and just use 11ty for everything. Until then, we’re still rocking Bootstrap and JQuery.


Getting Started (`docs/getting-started.md`)


layout: 2ColLeft.html title: Getting Started Guide sidebar: nav.html

{% include alert/start.html variant="info" %}

If you get stuck, download a working version,
or check out the full repo
and commit history.

{% include alert/end.html %}

In this tutorial we will write a basic Todo web application based on TodoMVC that syncs to an online CouchDB server. It should take around 10 minutes.

{% include anchor.html class="h3" title="Video Tutorial" hash="video_tutorial" %}

Prefer video tutorials? This guide is available in video format:

{% include iframe.html src="https://www.youtube.com/embed/-Z7UF2TuSp0" %}

{% include anchor.html class="h3" title="Download Assets" hash="download" %}

We will start with a template of the project where all the data related functions have been replaced with empty stubs. Download and unzip pouchdb-getting-started-todo.zip. When dealing with XHR and IndexedDB you are better off running web pages from a server as opposed to a filesystem. To do this you can run:

{% highlight bash %}
$ cd pouchdb-getting-started-todo
$ python -m SimpleHTTPServer # for Python 2
$ python -m http.server # for Python 3
{% endhighlight %}

Then visit http://127.0.0.1:8000/. If you see the following screenshot, you are good to go:

{% include img.html src="screenshots/todo-1.png" alt="Todo Screenshot" %}

It's also a good idea to open your browser's console so you can see any errors or confirmation messages.

{% include anchor.html class="h3" title="Installing PouchDB" hash="installing_pouchdb" %}

Open index.html and include PouchDB in the app by adding a script tag:

html
<script src="https://cdn.jsdelivr.net/npm/pouchdb@{{site.version}}/dist/pouchdb.min.js"></script>
<script src="js/base.js"></script>
<script src="js/app.js"></script>

PouchDB is now installed in your app and ready to use! (In production, you should use a local copy of the script.)

{% include anchor.html class="h3" title="Creating a database" hash="creating_a_database" %}

The rest of the work will be done inside app.js. We will start by creating a database to enter your todos. To create a database simply instantiate a new PouchDB object with the name of the database:

{% highlight js %}
// EDITING STARTS HERE (you don't need to edit anything above this line)

const db = new PouchDB('todos');
const remoteCouch = false;
{% endhighlight %}

You don't need to create a schema for the database. After giving it a name, you can immediately start writing objects to it.

{% include anchor.html class="h3" title="Write todos to the database" hash="write_todos_to_database" %}

The first thing we shall do is start writing items to the database. The main input will call addTodo with the current text when the user presses Enter. We can complete this function with the following code:

{% highlight js %}
function addTodo(text) {
const todo = {
_id: new Date().toISOString(),
title: text,
completed: false
};
db.put(todo, function callback(err, result) {
if (!err) {
console.log('Successfully posted a todo!');
}
});
}
{% endhighlight %}

In PouchDB each document is required to have a unique _id. Any subsequent writes to a document with the same _id will be considered updates. Here we are using a date string as an _id. For our use case, it will be unique, and it can also be used to sort items in the database. You can use db.post() if you want random ids. The _id is the only thing required when creating a new document. The rest of the object you can create as you like.

The callback function will be called once the document has been written (or failed to write). If the err argument is not null, then it will have an object explaining the error, otherwise the result will hold the result.

{% include anchor.html class="h3" title="Show items from the database" hash="show_database_items" %}

We have included a helper function redrawTodosUI that takes an array of todos to display, so all we need to do is read the todos from the database. Here we will simply read all the documents using db.allDocs. The include_docs option tells PouchDB to give us the data within each document, and the descending option tells PouchDB how to order the results based on their _id field, giving us newest first.

{% highlight js %}
function showTodos() {
db.allDocs({include_docs: true, descending: true}, function(err, doc) {
redrawTodosUI(doc.rows);
});
}
{% endhighlight %}

Once you have included this code, you should be able to refresh the page to see any todos you have entered.

{% include anchor.html class="h3" title="Update the UI" hash="update_the_ui" %}

We don't want to refresh the page to see new items. More typically you would update the UI manually when you write data to it, however, in PouchDB you may be syncing data remotely, so you want to make sure you update whenever the remote data changes. To do this we will call db.changes which subscribes to updates to the database, wherever they come from. You can enter this code between the remoteCouch and addTodo declaration:

{% highlight js %}
const remoteCouch = false;

db.changes({
since: 'now',
live: true
}).on('change', showTodos);

// We have to create a new todo document and enter it in the database
function addTodo(text) {
{% endhighlight %}

So every time an update happens to the database, we redraw the UI to show the new data. The live flag means this function will continue to run indefinitely. Now try entering a new todo and it should appear immediately.

{% include anchor.html class="h3" title="Edit a todo" hash="edit_a_todo" %}

When the user checks a checkbox, the checkboxChanged function will be called, so we'll fill in the code to edit the object and call db.put:

{% highlight js %}
function checkboxChanged(todo, event) {
todo.completed = event.target.checked;
db.put(todo);
}
{% endhighlight %}

This is similar to creating a document, however the document must also contain a _rev field (in addition to _id), otherwise the write will be rejected. This ensures that you don't accidentally overwrite changes to a document.

You can test that this works by checking a todo item and refreshing the page. It should stay checked.

{% include anchor.html class="h3" title="Delete an object" hash="delete_an_object" %}

To delete an object you can call db.remove with the object.

{% highlight js %}
function deleteButtonPressed(todo) {
db.remove(todo);
}
{% endhighlight %}

Similar to editing a document, both the _id and _rev properties are required. You may notice that we are passing around the full object that we previously read from the database. You can of course manually construct the object, like: {_id: todo._id, _rev: todo._rev}, but passing around the existing object is usually more convenient and less error prone.

{% include anchor.html class="h3" title="Complete rest of the Todo UI" hash="complete_todo_ui" %}

todoBlurred is called when the user edits a document. Here we'll delete the document if the user has entered a blank title, and we'll update it otherwise.

{% highlight js %}
function todoBlurred(todo, event) {
const trimmedText = event.target.value.trim();
if (!trimmedText) {
db.remove(todo);
} else {
todo.title = trimmedText;
db.put(todo);
}
}
{% endhighlight %}

{% include anchor.html class="h3" title="Installing CouchDB" hash="installing_couchdb" %}

Now we'll implement the syncing. You need to have a compatible server instance. You can install either PouchDB-Server, CouchDB or use an hosted Couch service such as Cloudant

{% include anchor.html class="h3" title="Enabling CORS" hash="enabling_cors" %}

To replicate directly with CouchDB, you need to make sure CORS is enabled. Only set the username and password if you have set them previously. By default, CouchDB will be installed in "Admin Party," where username and password are not needed. You will need to replace myname.example.com with your own host (127.0.0.1:5984 if installed locally):

You can enable CORS in CouchDB using curl or the Futon web interface, but we've saved you some time by making a Node script called add-cors-to-couchdb. Just run:

{% highlight bash %}
$ npm install add-cors-to-couchdb
$ add-cors-to-couchdb
{% endhighlight %}

Or if your database is not at 127.0.0.1:5984:

{% highlight bash %}
$ add-cors-to-couchdb http://me.example.com -u myusername -p mypassword
{% endhighlight %}

You can check that CORS is now enabled by visiting http://localhost:5984/_utils/config.html in your browser. You should see something like this:

{% include img.html src="cors_in_couchdb.png" alt="CORS settings in CouchDB" %}

{% include anchor.html class="h3" title="Implement basic two way sync" hash="basic_two_way_sync" %}

Now we will have the todo list sync. Back in app.js we need to specify the address of the remote database. Remember to replace user, pass and myname.example.com with the credentials of your own CouchDB instance:

{% highlight js %}
// EDITING STARTS HERE (you don't need to edit anything above this line)

const db = new PouchDB('todos');
const remoteCouch = 'http://user:[email protected]/todos';
{% endhighlight %}

Then we can implement the sync function like so:

{% highlight js %}
function sync() {
syncDom.setAttribute('data-sync-state', 'syncing');
const opts = {live: true};
db.replicate.to(remoteCouch, opts, syncError);
db.replicate.from(remoteCouch, opts, syncError);
}
{% endhighlight %}

db.replicate() tells PouchDB to transfer all the documents to or from the remoteCouch. This can either be a string identifier or a PouchDB object. We call this twice: once to receive remote updates, and once to push local changes. Again, the live flag is used to tell PouchDB to carry on doing this indefinitely. The callback will be called whenever this finishes. For live replication, this will mean an error has occurred, like losing your connection or you canceled the replication.

You should be able to open the todo app in another browser and see that the two lists stay in sync with any changes you make to them. You may also want to look at your CouchDB's Futon administration page and see the populated database.

{% include anchor.html class="h3" title="Congratulations!" hash="congratulations" %}

You've completed your first PouchDB application. This is a basic example, and a real world application will need to integrate more error checking, user signup, etc. But you should now understand the basics you need to start working on your own PouchDB project. If you have any more questions, please get in touch on IRC (web) or the mailing list.


Index (`docs/index.md`)


layout: default.html title: Apache PouchDB™, the JavaScript Database that Syncs!

text
<h1>The Database that Syncs!</h1>
    Apache PouchDB™ is an open-source JavaScript database inspired by [Apache CouchDB](http://couchdb.apache.org/) that is designed to run well within the browser.
    PouchDB was created to help web developers build applications that work as well offline as they do online.
    It enables applications to store data locally while offline, then synchronize it with CouchDB and compatible servers when the application is back online, keeping the user's data in sync no matter where they next login.
    Apache PouchDB is currently undergoing Incubation at the Apache Software Foundation.
    [Learn more]({{ site.baseurl }}/learn.html)

{% highlight js %}
const db = new PouchDB('dbname');

db.put({
_id: '[email protected]',
name: 'David',
age: 69
});

db.changes().on('change', function() {
console.log('Ch-Ch-Changes');
});

db.replicate.to('https://example.com/mydb');
{% endhighlight %}

text
<h3>Cross Browser</h3>
    Works in Firefox, Chrome, Opera, Safari, IE and Node.js
  
  
    
    <h3>Lightweight</h3>
    Apache PouchDB is just a script tag and 46KB (gzipped) away in the browser, or `$ npm install pouchdb` away
    in Node.
  
  
    
    <h3>Easy to Learn</h3>
    Requires some programming knowledge, however PouchDB is a piece of cake to learn.
  
  
    
    <h3>Open Source</h3>
    Everything is developed out in the open on GitHub, contributors always welcome!
  





<h3>Latest</h3>

{% for post in collections.posts limit:2 %}

{{ post.data.title }}
{% include post_details.html %}

{% endfor %}

View more


Guides Index (`docs/guides/index.md`)


index: 1 layout: guide.html nav: Intro title: Introduction to PouchDB sidebar: guides_nav.html

Welcome to the PouchDB guide! Consider this your starting point for anything and everything related to the world of PouchDB and CouchDB.

For a quicker TodoMVC-based tutorial, you can also check out the ["Getting Started" guide]({{ site.baseurl }}/getting-started.html).

Feel free to skip ahead using the sidebar at any time.

{% include anchor.html title="What is PouchDB?" hash="what-is-pouchdb" %}

PouchDB is a JavaScript implementation of CouchDB. Its goal is to emulate the CouchDB API with near-perfect fidelity, while running in the browser or in Node.js.

{% include anchor.html title="What is CouchDB?" hash="what-is-couchdb" %}

CouchDB is a NoSQL database created in 2005 by Damien Katz, and now maintained by the Apache Software Foundation. If you are a JavaScript developer, you probably use CouchDB every day, because it's the core technology that powers npm.

{% include anchor.html title="Couchbase, CouchDB, Couch-what?" hash="couchbase-couchdb-couch-what" %}

Today there are two major database companies that
can trace their lineage back to CouchDB: Couchbase and Cloudant. Both of them are separate products compared to CouchDB.

However, all three of these databases share the same CouchDB sync protocol. This means that PouchDB can sync with either one of them, and you can always swap out one database for another. You're never locked in.

In a sense, these databases are like competing phone companies, and the CouchDB sync protocol is the underlying telephony infrastructure.

{% include anchor.html title="CouchDB's one-two punch: HTTP and sync" hash="http-and-sync" %}

With so many SQL and NoSQL databases out there – MongoDB, PostgreSQL, MySQL, etc. – you may wonder why we chose to implement CouchDB instead of the others.

We have two very good answers to that question: HTTP and sync.

{% include anchor.html title="HTTP: the little protocol that could" hash="http" %}

When working with databases, we're often accustomed to writing some kind of conversion layer between the database and our client-side applications. This means, however, that we are just translating database queries into RESTful HTTP calls, over and over. For every app we write.

CouchDB throws this out the window by daring us to talk to the database directly, from our client-side apps. And it does so by using HTTP as its primary means of communication. No special protocol, no special drivers: just REST and HTTP. You can communicate with CouchDB entirely through your browser, curl, or a REST client like Postman.

In this way, CouchDB truly is a "database for the web."

{% include anchor.html title="Sync: CouchDB's killer feature" hash="sync" %}

Another unique feature of CouchDB is that it was designed from the bottom-up to enable easy synchronization between different databases.

For example, if you are worried about latency in your client-side applications, you can simply set up one CouchDB in Europe, another in North America, and another in Asia. After enabling continuous two-way replication between these databases, your clients can simply talk to whichever one is closer.

PouchDB takes this one step further by putting the database inside your browser.

{% include anchor.html title="Next" hash="next" %}

Now that you understand the basics of the PouchDB/CouchDB universe, let's set up CouchDB!


Adapters (`docs/adapters.md`)


layout: 2ColLeft.html title: Adapters sidebar: nav.html

PouchDB is not a self-contained database; it is a CouchDB-style abstraction layer over other databases. By default, PouchDB ships with the IndexedDB adapter for the browser, and a LevelDB adapter in Node.js. This can be visualized as so:

PouchDB attempts to provide a consistent API that "just works" across every browser and JavaScript environment, and in most cases, you can just use the defaults. However, if you're trying to reach the widest possible audience, or if you want the best performance, then you will sometimes want to tinker with the adapter settings.

Topics:

{% include anchor.html title="PouchDB in the browser" hash="pouchdb_in_the_browser"%}

In the browser, PouchDB prefers IndexedDB.

{% include alert/start.html variant="info"%}
Prior to PouchDB 7.0.0, the WebSQL adapter was used for Safari/iOS. The WebSQL adapter no longer ships in PouchDB, but may be installed separately.
{% include alert/end.html%}

If you're ever curious which adapter is being used in a particular browser, you can use the following method:

js
const pouch = new PouchDB('myDB');
console.log(pouch.adapter); // prints 'idb'

SQLite plugin for Cordova/PhoneGap

On Cordova/PhoneGap/Ionic, the native SQLite database is often a popular choice, because it allows unlimited storage (compared to IndexedDB/WebSQL storage limits). It also offers more flexibility in backing up and pre-loading databases, because the SQLite files are directly accessible to app developers.

There are various Cordova plugins that can provide access to native SQLite, such as
Cordova-sqlite-storage,
cordova-plugin-sqlite-2, or
cordova-plugin-websql.

To use them, you must install them separately into your Cordova application, and then add a special third-party PouchDB adapter
called pouchdb-adapter-cordova-sqlite. Once you do
that, you can use it via:

js
const db = new PouchDB('myDB.db', {adapter: 'cordova-sqlite'});

{% include alert/start.html variant="info"%}
In PouchDB pre-6.0.0, Cordova SQLite support was available out-of-the-box, but it has been moved to a separate plugin
to reduce confusion and to make it explicit whether you are using WebSQL or Cordova SQLite.
{% include alert/end.html%}

We recommend avoiding Cordova SQLite unless you are hitting the 50MB storage limit in iOS, you
require native or preloaded access to the database files, or there's some other reason to go native.
The built-in IndexedDB adapter is nearly always more performant and stable.

Browser adapter plugins

PouchDB also offers separate browser plugins that use backends other than IndexedDB. These plugins fully pass the PouchDB test suite and are rigorously tested in our CI process.

Downloads:

{% include alert/start.html variant="warning"%}
{% markdown %}
These plugins add a hefty footprint due to external dependencies, so take them with a grain of salt.
{% endmarkdown %}
{% include alert/end.html%}

In-memory adapter

If you want a quick database for your unit tests, you can use the pouchdb.memory.js plugin, which offers a pure in-memory PouchDB:

html
<script src="pouchdb.js"></script>
<script src="pouchdb.memory.js"></script>
<script>
  // this pouch is ephemeral; it only exists in memory
  const pouch = new PouchDB('mydb', {adapter: 'memory'});
</script>

This pouch will act exactly like a normal one – replicating, storing attachments, pagination, etc. – but it will be deleted as soon as the user closes their browser. However, multiple PouchDB objects with the same database name will share the same data:

js
// pouch1 and pouch2 will share the same data
const pouch1 = new PouchDB('myDB', {adapter: 'memory'});
const pouch2 = new PouchDB('myDB', {adapter: 'memory'});

// pouch3 will have its own data
const pouch3 = new PouchDB('myOtherDB', {adapter: 'memory'});

LocalStorage adapter

If you need to support very old browsers, such as IE ≤ 9.0 and Opera Mini, you can use the pouchdb.localstorage.js plugin, which allows PouchDB to fall back to LocalStorage on browsers that don't support either IndexedDB or WebSQL. The es5-shims will also be necessary.

html
<script src="pouchdb.js"></script>
<script src="pouchdb.localstorage.js"></script>
<script>
  // this pouch is backed by LocalStorage
  const pouch = new PouchDB('mydb', {adapter: 'localstorage'});
</script>

{% include alert/start.html variant="warning"%}
The LocalStorage plugin should be considered highly experimental, and the underlying structure may change in the future. Currently it stores all document IDs in memory, which works fine on small databases but may crash on larger databases. You can follow localstorage-down to track our progress.
{% include alert/end.html %}

{% include anchor.html title="PouchDB in Node.js" hash="pouchdb_in_node_js"%}

In-memory

{% include alert/start.html variant="warning"%}
{% markdown %}
Warning: deprecation notice. The memory adapter will be deprecated in PouchDB version 10.0.0 and removed in version 11.0.0. You can read the migration guide here and more about the topic in this link.
{% endmarkdown %}
{% include alert/end.html%}

Just as in the browser, you can also create a pure in-memory PouchDB:

bash
$ npm install pouchdb-adapter-memory

then:

js
PouchDB.plugin(require('pouchdb-adapter-memory'));
const pouch = new PouchDB('myDB', {adapter: 'memory'});

This implementation is based on MemDOWN, and will not write any changes to disk.

Node SQLite adapter

You can also use PouchDB in Node.js' native SQLite module, when using Node.js' >22.5.0 version.

js
const PouchDB = require('pouchdb');
PouchDB.plugin(require('pouchdb-adapter-node-sqlite'));

const db = new PouchDB('mydatabase.db', {adapter: 'nodesqlite'});

Other LevelDOWN adapters

{% include alert/start.html variant="warning"%}
{% markdown %}
Warning: deprecation notice. The leveldb adapter will be deprecated in PouchDB version 10.0.0 and removed in version 11.0.0. You can read the migration guide here and more about the topic in this link.
{% endmarkdown %}
{% include alert/end.html%}

Technically you are free to use
any LevelDOWN-based implementation in either Node or the browser.
However this should be considered extremely experimental and not designed for production use.

See pouchdb-adapter-leveldb-core for details.

{% include anchor.html title="PouchDB over HTTP" hash="pouchdb_over_http"%}

In both the browser and in Node.js, PouchDB can also function as a straightforward API on top of any CouchDB-compliant database:

js
const pouch = new PouchDB('http://my-site.com:5984/my-db');
const securePouch = new PouchDB('https://my-secure-site.com:5984/my-secure-db');

You can also sync to and from these databases to your local PouchDB.

Currently PouchDB has full support for:

Drupal 8 has also announced support for PouchDB, and there is rcouch as well, but these are both untested by PouchDB.

If you are ever unsure about a server, consider replicating from PouchDB to CouchDB, then from that CouchDB to the other server.

PouchDB Server

PouchDB Server is a standalone REST server that implements the CouchDB API, while using a LevelDB-based PouchDB under the hood. It also supports an --in-memory mode and any LevelDOWN adapter, which you may find handy.

PouchDB Server passes the PouchDB test suite at 100%, but be aware that it is not as full-featured or battle-tested as CouchDB.

PouchDB Express

The underlying module for PouchDB Server, Express PouchDB is an Express submodule that mimics most of the CouchDB API within your Express application.

{% include anchor.html title="More resources" hash="more_resources"%}

The best place to look for information on which browsers support which databases is caniuse.com. You can consult their tables on browser support for various backends:


Asf (`docs/asf.md`)

ASF Podling Proposal for PouchDB

Abstract

PouchDB is an open-source JavaScript database inspired by [Apache CouchDB]
(http://couchdb.apache.org/) that is designed to build offline-capable web
applications.

Proposal

PouchDB is an open-source JavaScript database inspired by [Apache CouchDB]
(http://couchdb.apache.org/) that is designed to run well within the browser.
PouchDB was created to help web developers build applications that work as
well offline as they do online.

Background

Established in 2010, PouchDB has been designed as both a first-class document
database for use in a web browser as well as a first-class data replication
peer to Apache CouchDB. PouchDB’s JavaScript API closely mirrors CouchDB’s REST
API and CouchDB’s peer-to-peer data synchronisation protocol allows web
developers to build database driven applications that work when the web browser
is offline. The replication protocol handles seamless data reconciliation when
one or more peers reconnect after being offline. Peers can be other web
browser by replicating via WebRTC, or one or more CouchDB instances.

Rationale

PouchDB has always been a sister project to Apache CouchDB. Since its inception,
it has changed lead maintainers three times, all of which were informally
running the project on its GitHub repository. With ever increasing popularity
and reliance by mission-critical projects, PouchDB is looking for an open source
foundation to join and has selected the ASF to be close to CouchDB.

Initial Goals

PouchDB aims to benefit from the ASF’s mature and transparent project- and
community-management policies.

Current Status

Meritocracy

PouchDB has always, if informally, rewarded contributors that have a persistent
contribution history with commit access to the project directly. Over its 15-
year history, core and occasional contributors have come and gone, while an
active set of PouchDB practitioners has always looked after the project itself.

Community

PouchDB comes with an existing and robust developer and user community. By
establishing transparent community guidelines, we hope to grow this community
even further.

Core Developers

PouchDB is currently maintained by a handful of experienced developers that
have a long history in open source. The current interim project lead is an ASF
member and PMC Chair for Apache CouchDB. All developers are at least also end-
users of Apache CouchDB, while some of them have also been active contributors
on CouchDB.

Alignment

PouchDB is an established project that looks for long-term stability and the ASF
provides nothing but. PouchDB is used in various mission-critical projects and
would like to benefit from ASF stewardship as a means of making it easier to be
relied on long-term. PouchDB is also Apache 2 licensed already.

Known Risks

PouchDB’s main risk is stagnation through developer abandonment. In its history
PouchDB had high- medium and low-velocity timespans, but it has always attracted
enough developers (both volunteer and for-pay) to stay afloat. Most recently,
PouchDB is at medium-velocity. Again, the PouchDB project hopes that by
formalising project management structures, a long-term stability and
dependability can be achieved.

Project Name

PouchDB is a tongue-in-cheek pun on its sister project CouchDB. The current
holder of the project domain (pouchdb.com) and previous project lead Dale
Harvey is on board with transferring any naming rights to the ASF.

Orphaned Products

The current PouchDB maintainers already consist of representatives from various
distinct organisations with strong technical and financial incentives to keep
PouchDB actively maintained. It is unlikely that a significant number would up
and leave the project.

Inexperience with Open Source

PouchDB is an existing open source project and its maintainers consist mostly of
very experienced developers, including some with over 15 years in active open
source development and one ASF Member and committer since 2008. For other
maintainers, PouchDB was their first open source projects and they have since
contributed to various other projects as well based on their good experience
with PouchDB.

Length of Incubation

The project aims to graduate within 6–12 months based on the experience of the
Apache CouchDB incubation in 2008 and general maturity of the project itself.

Homogenous Developers

The current PouchDB maintainers consist of people from around the world,
collaborating across all timezones.

Reliance on Salaried Developers

There is also a healthy mix of maintainers some of which are paid to work on
PouchDB part time, but everyone also contributes in their spare time.

Relationships with Other Apache Products

PouchDB has been developed as a sister-project to Apache CouchDB. Both projects
are inextricably linked in the sense that every PouchDB user is at least also a
CouchDB user. The reverse is not necessarily true, but in a recent CouchDB
developer survey, 80% of participants stated to use PouchDB with CouchDB.

The question of whether PouchDB should join CouchDB as as sub-project has been
raised and it has been decided to not pursue this option. Both projects have
significant technical differences (CouchDB is written in Erlang and PouchDB is
written in JavaScript) so that a joint stewardship would raise more problems
than it solves. Both the PouchDB and CouchDB maintainers agree on this point.
Where needed (APIs, sync protocol), the two teams have worked together tightly
and productively in the past. We don’t see a need to formalise this relationship
to achieve future success.

A Excessive Fascination with the Apache Brand

As mentioned before, PouchDB is looking for an open source foundation that helps
with long-term project stability and dependability. Both the OpenJS Foundation
and the ASF have been evaluated. In the end, the PouchDB developers prefer the
close proximity to Apache CouchDB. The maintainers believe that people having
to make decision about whether to bet on a PouchDB/CouchDB development stack or
not will have an easier time integrating both projects from a single foundation.
If the ASF won’t have PouchDB, we are happy to reconsider alternatives.

Documentation

Project website: https://pouchdb.com/
Including blog with release announcements: https://pouchdb.com/blog
GitHub Project: https://github.com/pouchdb/pouchdb

Initial Source

The initial source tree lives at https://github.com/pouchdb/pouchdb

Source and Intellectual Property Submission Plan

The PouchDB maintainers have yet to decide if they want to migrate the existing
repository to the ASF GitHub organisation or if they want to submit a pristine
copy, but either way, submitting the source code is a few git commands away.

The pouchdb.com domain currently held by Dale Harvey is being transferred
to Neighbourhoodie Software which also holds the couchdb.com/net/org domains
in escrow for the Apache CouchDB project. ASF Member and CouchDB PMC Chair Jan
Lehnardt is a shareholder and chief executive at Neighbourhoodie and they are
trusted to hold and manage those domains in escrow until such time a transition
is needed. The website publishing toolchain can be easily adapted to the ASF-
provided web publishing mechanisms.

External Dependencies

PouchDB depends directly on the following other projects. An automated licensing
scan has revealed the following set of licenses in the dependency tree

Runtime dependencies: three dependencies with to-be-sorted-out licenses

  • argsarray (WTFPL), trivially replaced with a compatible version.
  • fetch-cookie (Unlicense, public-domain), could be fine, but check with ASF.
  • readable-stream (incorrectly labelled BSD license), newer versions are MIT
    licensed, we should upgrade.

Development dependencies: if applicable, we’ll need to go through those and see
what can be done about them

These are produced with the licensee tool, invocations for runtime
dependencies:

text
licensee --corrections --errors-only --production

and for development dependencies:

text
licensee --corrections --errors-only

With the following licenses being permitted

  • Apache-2.0
  • MIT
  • BSD-2-Clause
  • BSD-3-Clause
  • ISC
  • BSD

Cryptography

PouchDB does not directly include cryptography code, but makes use of in-browser
TLS. A PouchDB plugin crypto-pouch exists that does record-level encryption, but
it is currently not part of the PouchDB code base. If md5 hashing is considered
cryptography, PouchDB, like CouchDB, makes use of that, just note that md5
hashing is not used for security relevant operations.

Required Resources

Mailing lists

Subversion Directory

n/a

Git Repositories

Issue Tracking

GitHub Issues

Other Resources

PouchDB makes significant use of GitHub Actions for CI. For 2024, we have used
214,556 total minutes across 53,831 job runs. This can be migrated to another
setup if need be but it’d be great if this carefully crafted setup could remain
in place.

Initial Committers

Alba Herrerías Ramírez albaherreriasdev at gmail dot com
Alex Anderson alexanderandersonofandover at gmail dot com
Diana Belle garbados at apache dot org (CLA)
Diana Barsan twisteddiana at gmail dot com
Gareth Bowen gareth at bowenwebdesign dot co dot nz
Jan Lehnardt jan at apache dot org (CLA)
James Coglan james at neighbourhood dot ie
Johannes Schmidt schmidt at tf-fabrik dot de
Steven-John Lange sourcer85 at gmail dot com

Sponsors

Alba Herrerías Ramírez Neighbourhoodie Software
Alex Anderson independent, formerly Medic
Diana Belle independent
Diana Barsan Medic
Gareth Bowen independent, formerly Medic
Jan Lehnardt Neighbourhoodie Software
James Coglan Neighbourhoodie Software
Johannes Schmidt Mozilla / independent
Steven-John Lange HZData GmbH / independent

Champion

Jan Lehnardt, CouchDB PMC Chair.

Nominated Mentors

PJ Fanning (fanningpj at apache dot org)
Jean-Baptiste Onofré (jb at nanthrax dot net)

Sponsoring Entity

The Incubator


Custom (`docs/custom.md`)


layout: 2ColLeft.html title: Custom Builds sidebar: nav.html

PouchDB supports custom builds, meaning you can pick and choose the features of
PouchDB that you want to use, potentially resulting in smaller bundle sizes
and faster build times.

PouchDB exposes its custom builds via separate packages available on npm. All of
these packages follow the format pouchdb-<name> and can be installed using npm install.

Some packages are included by default in the main pouchdb package, whereas
others (including third-party packages) must be installed separately.

{% include alert/start.html variant="warning"%}
{% markdown %}

Custom builds require an npm-based build system, using a bundler
like Browserify, Webpack, SystemJS, Rollup, or JSPM. Tools like
Bower, as well as direct download of prebuilt JavaScript files, are not supported.

{% endmarkdown %}
{% include alert/end.html%}

PouchDB packages come in three flavors: presets, plugins, and
utilities.

Presets are a collection of plugins, which expose a PouchDB object that is ready to be used.

Plugins are features that can be added to a PouchDB instance using the PouchDB.plugin()
API.

Utilities are grab-bags of helper functions, and are only recommended for advanced use cases.

{% include anchor.html class="h2" title="Presets" hash="presets" %}

Presets export a PouchDB object and contain a built-in set of PouchDB
plugins. You are free to create your own presets, but PouchDB provides a few first-party presets to address common use cases.

[pouchdb-browser](https://npmjs.org/package/pouchdb-browser)

The pouchdb-browser preset contains the version of PouchDB that is designed
for the browser. In particular, it ships with the IndexedDB adapter
as its default adapter. It also contains the replication, HTTP, and map/reduce plugins.

Use this preset if you only want to use PouchDB in the browser,
and don't want to use it in Node.js. (E.g. to avoid installing LevelDB.)

Example Usage

bash
npm install pouchdb-browser
js
const PouchDB = require('pouchdb-browser');
const db = new PouchDB('mydb');

Source code (simplified)

js
const PouchDB = require('pouchdb-core')
  .plugin(require('pouchdb-adapter-idb'))
  .plugin(require('pouchdb-adapter-http'))
  .plugin(require('pouchdb-mapreduce'))
  .plugin(require('pouchdb-replication'));

[pouchdb-node](https://npmjs.org/package/pouchdb-node)

The pouchdb-node preset contains the version of PouchDB that is designed for
Node.js. In particular, it uses the LevelDB adapter and doesn't ship with the
IndexedDB or WebSQL adapters. It also contains the replication, HTTP, and map/reduce plugins.

Use this preset if you are only using PouchDB in Node, and not in the browser.

Example Usage

bash
npm install pouchdb-node
js
const PouchDB = require('pouchdb-node');
const db = new PouchDB('mydb');

Source code (simplified)

js
const PouchDB = require('pouchdb-core')
  .plugin(require('pouchdb-adapter-leveldb'))
  .plugin(require('pouchdb-adapter-http'))
  .plugin(require('pouchdb-mapreduce'))
  .plugin(require('pouchdb-replication'));

[pouchdb-core](https://npmjs.org/package/pouchdb-core)

The pouchdb-core package is a special preset in that it exposes the minimum
number of APIs. It contains zero plugins and is designed to be used in addition
with other plugins. By itself, it probably isn't very useful.

Example Usage

bash
npm install pouchdb-core
js
const PouchDB = require('pouchdb-core');
PouchDB.plugin(/* attach plugins to make me more interesting! */);

{% include anchor.html class="h2" title="Plugins" hash="plugins" %}

Plugins contain functionality that can be added to a PouchDB instance using PouchDB.plugin(). There are many third-party plugins, but the ones described below are first-party plugins, which are given the same level of support as PouchDB itself. Some first-party plugins are included in the default pouchdb build, whereas others aren't.

There is also a special type of plugin called an adapter plugin. Adapter plugins (such as IndexedDB, WebSQL, LevelDB, and HTTP) determine the storage format that
PouchDB uses. For the non-HTTP adapters, the plugin order matters, i.e. if you
want IndexedDB to be preferred to WebSQL, then you should load it first.

[pouchdb-adapter-idb](https://npmjs.org/package/pouchdb-adapter-idb)

The primary adapter used by PouchDB in the browser, using IndexedDB. The adapter
name is 'idb'.

Example usage

bash
npm install pouchdb-adapter-idb
js
PouchDB.plugin(require('pouchdb-adapter-idb'));
const db = new PouchDB('mydb', {adapter: 'idb'});
console.log(db.adapter); // 'idb'

[pouchdb-adapter-websql](https://npmjs.org/package/pouchdb-adapter-websql)

An adapter used by PouchDB in the browser, using WebSQL. The adapter
name is 'websql'.

Before PouchDB 7.0.0, this was shipped as a default adapter. As of PouchDB 7.0.0, it must be loaded as a separate plugin.

Example usage

bash
npm install pouchdb-adapter-websql
js
PouchDB.plugin(require('pouchdb-adapter-websql'));
const db = new PouchDB('mydb', {adapter: 'websql'});
console.log(db.adapter); // 'websql'

[pouchdb-adapter-leveldb](https://npmjs.org/package/pouchdb-adapter-leveldb)

{% include alert/start.html variant="warning"%}
{% markdown %}
Warning: deprecation notice. The leveldb adapter will be deprecated in PouchDB version 10.0.0 and removed in version 11.0.0. You can read the migration guide here and more about the topic in this link.
{% endmarkdown %}
{% include alert/end.html%}

The primary adapter used by PouchDB in Node.js, using LevelDB. The adapter name
is 'leveldb'.

Example usage

bash
npm install pouchdb-adapter-leveldb
js
PouchDB.plugin(require('pouchdb-adapter-leveldb'));
const db = new PouchDB('mydb', {adapter: 'leveldb'});
console.log(db.adapter); // 'leveldb'

[pouchdb-adapter-http](https://npmjs.org/package/pouchdb-adapter-http)

The primary adapter used by PouchDB in both Node.js and the browser for communicating
with external CouchDB (or CouchDB-like) servers.

This plugin can be added to PouchDB in any order, and is somewhat special in that
you must pass in a name like 'http://...' in order to use it. The adapter name
is either 'http' or 'https' depending on the protocol.

Example usage

bash
npm install pouchdb-adapter-http
js
PouchDB.plugin(require('pouchdb-adapter-http'));
const db = new PouchDB('http://127.0.0.1:5984/mydb');
console.log(db.adapter); // 'http'

[pouchdb-adapter-memory](https://npmjs.org/package/pouchdb-adapter-memory)

{% include alert/start.html variant="warning"%}
{% markdown %}
Warning: deprecation notice. The memory adapter will be deprecated in PouchDB version 10.0.0 and removed in version 11.0.0. You can read the migration guide here and more about the topic in this link.
{% endmarkdown %}
{% include alert/end.html%}

An optional adapter that works in the browser and Node.js, fully in-memory. The adapter name
is 'memory'.

Example usage

bash
npm install pouchdb-adapter-memory
js
PouchDB.plugin(require('pouchdb-adapter-memory'));
const db = new PouchDB('mydb', {adapter: 'memory'});
console.log(db.adapter); // 'memory'

[pouchdb-adapter-localstorage](https://npmjs.org/package/pouchdb-adapter-localstorage)

An optional adapter that works in the browser using LocalStorage. The adapter name
is 'localstorage'.

Example usage

bash
npm install pouchdb-adapter-localstorage
js
PouchDB.plugin(require('pouchdb-adapter-localstorage'));
const db = new PouchDB('mydb', {adapter: 'localstorage'});
console.log(db.adapter); // 'localstorage'

[pouchdb-adapter-node-websql](https://npmjs.org/package/pouchdb-adapter-node-websql)

An optional adapter that works in Node.js using SQLite via node-websql. The adapter name
is 'websql'.

Example usage

bash
npm install pouchdb-adapter-node-websql
js
PouchDB.plugin(require('pouchdb-adapter-node-websql'));
const db = new PouchDB('mydb', {adapter: 'websql'});
console.log(db.adapter); // 'websql'

[pouchdb-adapter-indexeddb](https://npmjs.org/package/pouchdb-adapter-indexeddb)

{% include alert/start.html variant="warning"%}
{% markdown %}

Warning: experimental. You probably don't want to use this yet. 😉

{% endmarkdown %}
{% include alert/end.html%}

An experimental next-generation IndexedDB adapter, also known as "idb-next". Currently not shipped as part of PouchDB. The adapter name is 'indexeddb'.

Example usage

bash
npm install pouchdb-adapter-indexeddb
js
PouchDB.plugin(require('pouchdb-adapter-indexeddb'));
const db = new PouchDB('mydb', {adapter: 'indexeddb'});
console.log(db.adapter); // 'indexeddb'

[pouchdb-find](https://npmjs.org/package/pouchdb-find)

PouchDB's "Mango" query API, exposed via the find(), listIndexes(), createIndex(), and deleteIndex()` methods. Not shipped by default in PouchDB.

Example usage

bash
npm install pouchdb-find
js
PouchDB.plugin(require('pouchdb-find'));
const db = new PouchDB('mydb');
db.find(/* see API docs for full info */);

[pouchdb-mapreduce](https://npmjs.org/package/pouchdb-mapreduce)

PouchDB's map/reduce API, exposed via the query() and viewCleanup() methods. Ships by default in PouchDB.

Example usage

bash
npm install pouchdb-mapreduce
js
PouchDB.plugin(require('pouchdb-mapreduce'));
const db = new PouchDB('mydb');
db.query(/* see query API docs for full info */);

[pouchdb-replication](https://npmjs.org/package/pouchdb-replication)

PouchDB's replication API, exposed via the replicate() and sync() methods. Ships by default in PouchDB.

Example usage

bash
npm install pouchdb-replication
js
PouchDB.plugin(require('pouchdb-replication'));
const db = new PouchDB('mydb');
db.replicate(/* see replicate/sync API docs for full info */);

{% include anchor.html class="h2" title="Utilities" hash="utilities" %}

These utilities are intended only for advanced users of PouchDB, such as
third-party plugin authors. Formerly, many of them were exposed via the extras/ API, which
is now deprecated.

Most of these are internal, and the APIs are not thoroughly documented. You will
most likely need to read the source code to understand how they work.

{% include alert/start.html variant="warning"%}
{% markdown %}

Warning: you are entering a semver-free zone.

In contrast to the presets and plugins listed above, none of the following packages
follow semver
. Their versions are pinned to PouchDB's, and may change at any time
without warning. You are strongly recommended to use exact versions when installing these packages.

{% endmarkdown %}
{% include alert/end.html%}

[pouchdb-abstract-mapreduce](https://npmjs.org/package/pouchdb-abstract-mapreduce)

The underlying logic for secondary indexes, as expressed in both pouchdb-mapreduce and pouchdb-find. Both packages use this package under the hood.

Example usage

bash
npm install --save-exact pouchdb-abstract-mapreduce

[pouchdb-adapter-utils](https://npmjs.org/package/pouchdb-adapter-utils)

Utilities for PouchDB adapters.

Example usage

bash
npm install --save-exact pouchdb-adapter-utils

[pouchdb-ajax](https://npmjs.org/package/pouchdb-ajax)

PouchDB's ajax() function.

Example usage

bash
npm install --save-exact pouchdb-ajax

[pouchdb-binary-utils](https://npmjs.org/package/pouchdb-binary-utils)

Utilities for operating on binary strings and Buffers/Blobs.

Example usage

bash
npm install --save-exact pouchdb-binary-utils

[pouchdb-checkpointer](https://npmjs.org/package/pouchdb-checkpointer)

Tool to write a checkpoint, e.g. during replication.

Example usage

bash
npm install --save-exact pouchdb-checkpointer

[pouchdb-collate](https://npmjs.org/package/pouchdb-collate)

Collation functions for PouchDB map/reduce.

Example usage

bash
npm install --save-exact pouchdb-collate

[pouchdb-collections](https://npmjs.org/package/pouchdb-collections)

ES6 shims for Map and Set as used in PouchDB.

Example usage

bash
npm install --save-exact pouchdb-collections

[pouchdb-errors](https://npmjs.org/package/pouchdb-errors)

Errors exposed by PouchDB.

Example usage

bash
npm install --save-exact pouchdb-errors

[pouchdb-generate-replication-id](https://npmjs.org/package/pouchdb-generate-replication-id)

Function to generate a replication ID to mark progress during replications.

Example usage

bash
npm install --save-exact pouchdb-generate-replication-id

[pouchdb-json](https://npmjs.org/package/pouchdb-json)

Utilities for safely stringifying and parsing JSON.

Example usage

bash
npm install --save-exact pouchdb-json

[pouchdb-mapreduce-utils](https://npmjs.org/package/pouchdb-mapreduce-utils)

Utilities used by pouchdb-mapreduce.

Example usage

bash
npm install --save-exact pouchdb-mapreduce-utils

[pouchdb-md5](https://npmjs.org/package/pouchdb-md5)

Utilities for calculating MD5 checksums.

Example usage

bash
npm install --save-exact pouchdb-md5

[pouchdb-merge](https://npmjs.org/package/pouchdb-merge)

PouchDB's CouchDB-style document merge algorithm.

Example usage

bash
npm install --save-exact pouchdb-merge

[pouchdb-promise](https://npmjs.org/package/pouchdb-promise)

A Promise object, polyfilled using lie if Promises aren't available globally.

Example usage

bash
npm install --save-exact pouchdb-promise

[pouchdb-selector-core](https://npmjs.org/package/pouchdb-selector-core)

The core Mango selector logic, which allows the selector to be used both by pouchdb-find and for filtering/replication.

Example usage

bash
npm install --save-exact pouchdb-selector-core

[pouchdb-utils](https://npmjs.org/package/pouchdb-utils)

A potpourri of miscellaneous utilities used by PouchDB and its sub-packages.

Example usage

bash
npm install --save-exact pouchdb-utils

[sublevel-pouchdb](https://npmjs.org/package/sublevel-pouchdb)

Fork of level-sublevel
with only the subset of the API that PouchDB uses.

Example usage

bash
npm install --save-exact sublevel-pouchdb

Download (`docs/download.md`)


layout: 2ColLeft.html title: Download sidebar: nav.html

Apache PouchDB™ is an open-source JavaScript database inspired by Apache CouchDB that is designed to run well within the browser.

{% include anchor.html class="h3" title="Quick Start" hash="file" %}

html
<script src="https://cdn.jsdelivr.net/npm/pouchdb@{{site.version}}/dist/pouchdb.min.js"></script>
<script>
  const db = new PouchDB('my_database');
</script>

PouchDB can also be directly downloaded:

  • [pouchdb-{{ site.version }}.min.js][latest-min] (compressed for production)
  • [pouchdb-{{ site.version }}.js][latest] (uncompressed for debugging)

If you are using PouchDB in Internet Explorer a Promise and Fetch polyfill will be needed.

{% include anchor.html class="h3" title="npm" hash="npm" %}

PouchDB can be installed through npm:

{% highlight bash %}npm install --save pouchdb{% endhighlight %}

After installing, call require() to use it:

{% highlight javascript %}
const PouchDB = require('pouchdb');
const db = new PouchDB('my_database');
{% endhighlight %}

PouchDB can be used either in Node or in the browser. A bundler such as Browserify, Webpack, or Rollup is needed for browser usage.

Browser only

If you're only using PouchDB in the browser, you can use pouchdb-browser for
faster install times:

{% highlight bash %}npm install --save pouchdb-browser{% endhighlight %}

{% highlight javascript %}
const PouchDB = require('pouchdb-browser');
const db = new PouchDB('my_database');
{% endhighlight %}

See [custom builds]({{ site.baseurl }}/custom.html) for more options.

{% include anchor.html class="h3" title="CDNs" hash="cdn" %}

PouchDB is hosted at these CDNs:

{% highlight bash %}bower install --save pouchdb{% endhighlight %}

{% include anchor.html class="h3" title="Past releases" hash="past-releases" %}

For past releases and changelog, check out the Github releases page.

{% include anchor.html class="h3" title="Plugins" hash="plugins" %}

For third-party plugins, see the plugins page.

{% include anchor.html class="h3" title="Custom builds" hash="custom" %}

For custom builds and first-party plugins, see the [custom builds]({{ site.baseurl }}/custom.html) page.

[latest]: https://github.com/apache/pouchdb/releases/download/{{ site.version }}/pouchdb-{{ site.version }}.js
[latest-min]: https://github.com/apache/pouchdb/releases/download/{{ site.version }}/pouchdb-{{ site.version }}.min.js


Errors (`docs/errors.md`)


layout: 2ColLeft.html title: Common Errors sidebar: nav.html

{% include anchor.html class="h3" title="No Access-Control-Allow-Origin header" hash="no_access_control_allow_origin_header" %}

If you see the error:

bash
XMLHttpRequest cannot load [...]
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin [...] is therefore not allowed access.

or this one:

bash
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://[couchDBIP]:[couchDBPort]/[dbname]/?_nonce=[request hash]. This can be fixed by moving the resource to the same domain or enabling CORS

it's because you need to enable CORS on CouchDB/Cloudant/whatever you're using. Otherwise, your scripts can only access the server database if they're served from the same origin — the protocol (ex: http://, https://), domain, and port number must match.

You can enable CORS in CouchDB using curl or the Futon web interface, but we've saved you some time by making a Node script called add-cors-to-couchdb. Just run:

{% highlight bash %}
$ npm install add-cors-to-couchdb
$ add-cors-to-couchdb
{% endhighlight %}

Or if your database is not at 127.0.0.1:5984:

{% highlight bash %}
$ add-cors-to-couchdb http://me.example.com
-u myusername -p mypassword
{% endhighlight %}

You can check that CORS is now enabled by visiting http://localhost:5984/_utils/config.html in your browser. You should see something like this:

{% include img.html src="cors_in_couchdb.png" alt="CORS settings in CouchDB" %}

{% include anchor.html class="h3" title="PouchDB throws a No valid adapter found error" hash="no_valid_adapter" %}

Reading from/writing to a local database from an iframe with a different origin will cause PouchDB to throw a No valid adapter found error in Firefox. This is due to Firefox's IndexedDB implementation.

IndexedDB has a same-origin restriction. Read/write operations from another origin will always fail, but only Firefox triggers a No valid adapter found error. Chrome / Opera will instead throw an UnknownError.

{% include anchor.html class="h3" title="iOS/Safari: "there was not enough remaining storage space"" hash="not_enough_space" %}

On iOS and Safari, if you expect your app to use more than 5MB of space, you will need to request the space up-front from the user. In certain versions of Safari, you can never request more than what the user originally grants you.

{% include img.html src="safari_popup.png" alt="Safari storage quota popup" %}

To get around this, when you create your PouchDB, use the opts.size option for the expected maximum size of the database in MB. Valid increments are 10, 50, 100, 500, and 1000. For instance, if you request 50, then Safari will show a popup saying "allow 50MB?" but if you request 51, then Safari will show a popup saying "allow 100MB?".

If you don't use the size option, then you'll be able to use up to 5MB without any popup, but then once you use more, there will be a popup asking for 10.

js
new PouchDB('mydb', {size: 10}); // request 10 MB with a popup
new PouchDB('mydb', {size: 50}); // request 50 MB with a popup
new PouchDB('mydb'); // implicitly request 5 MB, no popup until you exceed 5MB

This does not affect any backend other than Web SQL. Chrome, Android, and Opera do not show the popup. On PhoneGap/Cordova apps, you can also use the SQLite plugin to get around this problem. Here's more information about storage quotas and details on the Safari/iOS bug.

{% include anchor.html class="h3" title="PouchDB throws 404 (Object Not Found) for '_local' document" hash="404__local_document" %}

Don't worry, nothing is amiss, this is expected behaviour:
During PouchDB's initial replication PouchDB will check for a checkpoint, if it doesn't exist a 404 will be returned and a checkpoint will subsequently be written.

{% include anchor.html class="h3" title="PouchDB is throwing InvalidStateError" hash="throwing_invalidstateerror" %}

Are you in private browsing mode? IndexedDB is disabled in private browsing mode in Firefox.

{% include anchor.html class="h3" title="Can't open second database on Android WebView with Cordova/Phone Gap" hash="phonegap_cordova_second_database" %}

There is a limit of one database per app in some versions of the Android WebView. Install the SQLite plugin, then PouchDB will use that if it is available.

{% include anchor.html class="h3" title="Possible EventEmitter memory leak detected" hash="event_emitter_limit" %}

If you see this warning:

bash
(node) warning: possible EventEmitter memory leak detected. 11 listeners added.
Use emitter.setMaxListeners() to increase limit.

This is because PouchDB uses Node-style EventEmitters for its events. An EventEmitter is any object that has an .on() or once() method, such as db.changes().on('change', ....

By default, all EventEmitters have 10 listeners, and if you exceed that limit, e.g. by attaching many changes() listeners or running many simultaneous replicate() or sync() events, then you may exceed this limit.

This could indicate a memory leak in your code. Check to make sure that you are calling cancel() on any changes(), replicate(), or sync() handlers, if you are constantly starting and stopping those events.

If you're sure it's not a memory leak, though, you can increase the limit by doing:

{% highlight javascript %}
db.setMaxListeners(20); // or 30 or 40 or however many you need
{% endhighlight %}

In the above example, db refers to a database object you created using new PouchDB('dbname').

{% include anchor.html class="h3" title="Database size limitation of ~5MB on iOS with Cordova/Phone Gap" hash="size_limitation_5mb" %}

If you're storing large amounts of data, such as PNG attachments, the SQLite plugin is again your friend. (See issue #1260 for details.)

{% include anchor.html class="h3" title="CouchDB returns a 404 for GETs from a CouchApp" hash="404_get_couchapp" %}

Certain URL rewrites are broken by PouchDB's cache-busting; try adding {cache : false} to the PouchDB constructor. (See issue #1233 for details.)

{% include anchor.html class="h3" title="Uncaught TypeError: object is not a function" hash="typeerror_object_is_not_a_function" %}

Did you include the es6-promise shim library? Not every browser implements ES6 Promises correctly. (See issue #1747 for details.)

{% include anchor.html class="h3" title="SyntaxError: Parse error (Cordova on Android)" hash="cordova_android_parse_error" %}

Did you include the es5-shim library? PouchDB is written in ES5, which is supported by modern browsers, but requires shims for older browsers (e.g. IE 9, Android 2.1 WebView).

In Android, if you're loading PouchDB directly via webView.loadUrl('javascript://' + js'), you should prefer the minified PouchDB javascript file to the unminified one, since code comments can also cause parse errors.

{% include anchor.html class="h3" title="PouchDB object fails silently (Safari)" hash="safari_object_silent_fail" %}

Safari requires users to confirm that they want to allow an app to store data locally ("Allow this website to use space on your disk?"). If PouchDB is loaded in an iframe or some other unusual way, the dialog might not be shown, and the database will silently fail.

{% include anchor.html class="h3" title="window.localStorage is not available (Chrome apps)" hash="window_localstorage_chrome_apps" %}

In Chrome apps, you'll see the warning "window.localStorage is not available in packaged apps. Use chrome.storage.local instead." This is harmless; since PouchDB doesn't use localStorage if it's not available.

{% include anchor.html class="h3" title="Error: UnknownError (Firefox)" hash="unknown_error_ff" %}

Are you using a webserver to host and run your code? This error can be caused by running your script/file locally using the file:/// setting in Firefox, since Firefox does not allow access to IndexedDB locally. You can use the SimpleHTTPServer to deploy your script by running python -m SimpleHTTPServer from the directory containing the script, or use the Apache webserver and then access the script by using http://localhost/{path_to_your_script}.

{% include anchor.html class="h3" title="Error: UnknownError (Chrome / Opera)" hash="unknown_error_chrome" %}

This can occur when attempting to read from or write to IndexedDB from a different origin. IndexedDB has a same-origin restriction. Attempting to write to the database associated with http://example.com from an iframe served from http://api.example.com, for example, will fail.

In Firefox, PouchDB instead throws a No valid adapter found error.

{% include anchor.html class="h3" title="DataCloneError: An object could not be cloned" hash="could_not_be_cloned" %}

If you ever see:

bash
Uncaught DataCloneError:
  Failed to execute 'put' on 'IDBObjectStore':
  An object could not be cloned.

Or:

bash
DataCloneError: The object could not be cloned.

Then the problem is that the document you are trying to store is not a pure JSON object. For example, an object with its own class (new Foo()) or with special methods like getters and setters cannot be stored in PouchDB/CouchDB.

If you are ever unsure, then run this on the document:

js
JSON.parse(JSON.stringify(myDocument));

If the object you get out is the same as the object you put in, then you are storing the right kind of object.

Note that this also means that you cannot store Dates in your document. You must convert them to strings or numbers first. Dates will be stored as-is in IndexedDB, but in the other adapters and in CouchDB, they will be automatically converted to ISO string format, e.g. '2015-01-01T12:00:00.000Z'. This can caused unwanted results. See #2351 and #2158 for details.

{% include anchor.html class="h3" title="DOM Exception 18 in Android pre-Kitkat WebView" hash="android_pre_kitkat" %}

This applies to hybrid apps designed to run in Android pre-Kitkat (i.e. before 4.4).

If you are directly using a WebView and not using Cordova/PhoneGap, you will probably either run into an error where PouchDB silently fails or you see Error: SECURITY_ERR: DOM Exception 18 in the console. As a sanity test, you can run this JavaScript:

js
openDatabase('mydatabase', 1, 'mydatabase', 5000000, function (db) { console.log('it works!'); });

If you see "it works" in the console, then everything's peachy. Otherwise there are a few things you have to do.

First, make sure Web SQL is enabled on your WebView in the first place using setDatabaseEnabled:

java
myWebView.getSettings().setDatabaseEnabled(true);

Second, specify a path for the database. Yes, you need to do this, even though it's deprecated in Kitkat:

java
String databasePath = getContext().getApplicationContext().getDir(
  "database", Context.MODE_PRIVATE).getPath();
webView.getSettings().setDatabasePath(databasePath);

Third, you'll need to set an onExceededDatabaseQuota handler. Yes, it's also deprecated in Kitkat. Yes, you still need to do it.

java
webView.setWebChromeClient(new WebChromeClient() {

  @Override
  public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
                                      long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
    quotaUpdater.updateQuota(estimatedSize * 2);
  }
});

If you skip any one of these three steps, then you will get the DOM Exception 18 error. You need to do all three.

Alternatively, you can also load the WebView with a fake http:// URL, but this may cause other errors when you try to fetch files based on a relative path:

java
webView.loadDataWithBaseURL("http://www.example.com",
    htmlContent,
    "text/html",
    "utf-8",
    null);

{% include anchor.html class="h3" title="Replication with attachments is slow or fails" hash="replicating_attachments_slow" %}

The symptoms for this issues are:

  1. Replicating a database that has many attachments from a CouchDB server is either slow or fails randomly.
  2. You get server error message of the nature No buffer space available.

Chances are that your server runs inside a virtual machine. The host system, or hypervisor, imposes limits on how much data each virtual machine can use for networking. If you are on a cheap virtual server, it is possible, that the default settings for PouchDB pull-replication (10 parallel batches of 100 documents each) exhaust the narrow limit of your server. Even a single client can cause this.

The solution is to move to a better server, but if that is not an immediate option, a workaround would be reducing the options.batch_size and options.batches_limit [replication options]({{ site.baseurl }}/api.html#replication).

To find optimal values, start by setting them both to 1 (meaning that PouchDB should download one document after the other) and increase from there and stop when the symptoms begin again. Note that multiple concurrent clients can still cause an issue, if you get too many. If all your documents have one or more attachments (e.g. a photos database), setting both options to 1 is probably a good idea.

{% include alert/start.html variant="info" %}

Generally, reducing these options that replicating the database down will take more time. Please test various settings to see what works for you and your hardware.

{% include alert/end.html %}

{% include alert/start.html variant="info" %}

This issue has been found on OpenVZ systems, but other Hypervisors might also be affected. See
http://blog.aplikacja.info/105_no_buffer_space_available_on_openvz_vps.html
on how to diagnose this issue.

{% include alert/end.html %}

{% include anchor.html class="h3" title="Packaging PouchDB in an app with WebPack" hash="package_pouchdb_webpack" %}

PouchDB may have various dependencies that may not play nicely with WebPack. Here are some issues you may run into and their resolutions:

You may need an appropriate loader to handle this file type.

If you run into the following error (or similar):

bash
ERROR in ./~/pouchdb/~/levelup/package.json
Module parse failed: /path/to/node_modules/pouchdb/node_modules/levelup/package.json Line 2: Unexpected token :
You may need an appropriate loader to handle this file type.
| {
|   "name": "levelup",
|   "description": "Fast & simple storage - a Node.js-style LevelDB wrapper",
|   "version": "0.18.6",
 @ ./~/pouchdb/~/levelup/lib/util.js 102:30-56

WebPack needs to be configured to recognize how to load json files. Simply, install json-loader and edit webpack.config.js as follows:

js
module: {
    loaders: [
        // https://github.com/apache/pouchdb/issues/3319
        {
            test: /\.json$/,
            loader: "json-loader"
        }
    ]
}

{% include anchor.html class="h3" title="Failed to load resource: the server responded with a status of 400 (Bad request) " hash="couchbase_dbname" %}

If you are using Couchbase Lite to sync with PouchDB then you cannot use capital letters in your database name as Couchbase Lite has restrictions on valid database names.

{% include anchor.html class="h3" title="Live changes pass undetected (Web Extension)" hash="webextension_live_changes" %}

When using PouchDB in a WebExtension (at least in Chromium 55 and Firefox 50), but apparently only if the manifest.json contains the store permission, a [live changes feed]({{ site.baseurl }}/guides/changes.html#live-changes-feed) in one tab or background process may not detect changes made in another tab/process. It will of course still report those changes upon the next change that it does detect.
Minimal code to reproduce this can be found here.

{% include anchor.html class="h3" title="PouchDB install errors on Windows" hash="pouchdb_install_errors_windows" %}

Sometimes npm install pouchdb fails on windows, when no prebuilt binary is available. The error looks similar to this:

bash
C:\XXX\node_modules\project_name>if not defined npm_config_node_gyp (node "C:\XXX\node_modules\npm\bin\node-gyp-bin\....\node_modules\node-gyp\bin\node-gyp.js" rebuild ) else (node "" rebuild )
gyp ERR! configure error
gyp ERR! stack Error: Can't find Python executable "python", you can set the PYTHON env variable.
gyp ERR! stack at PythonFinder.failNoPython (C:\XXX\nodejs\node_modules\npm\node_modules\node-gyp\lib\configure.js:483:19)

or something like this

bash
gyp ERR! configure error
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] install: `prebuild-install || node-gyp rebuild`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] install script.

If you are on windows and getting any error messages mentioning leveldown and Python, please attempt the steps below.

Node-Gyp requires Python 2.7, it does not work with Python 3.x unfortunately.

Here are a few steps you can take:

  1. try npm install --production windows-build-tools (this command needs to be ran with admin privileges)
  2. read other pointers to using node-gyp on Windows here
  3. If you are only using PouchDB in the browser, you can install pouchdb-browser instead: npm install --save pouchdb-browser

External (`docs/external.md`)


layout: 2ColLeft.html title: Plugins and External Projects sidebar: nav.html

Below is a list of known plugins, tools and projects can be used with PouchDB.

{% include anchor.html title="Plugins" hash="plugins" %}

[PouchDB allDbs()](https://github.com/nolanlawson/pouchdb-all-dbs)

Revives the allDbs() function, which lists all PouchDB databases.

[PouchDB Authentication](https://github.com/nolanlawson/pouchdb-authentication)

Plugin for CouchDB's authentication system.

[Pouch Box](https://github.com/jo/pouch-box)

Allows decentralized authentication and access control per document, using asymmetric encryption.

[PouchDB Collate](https://github.com/apache/pouchdb/tree/master/packages/node_modules/pouchdb-collate)

Collation functions for PouchDB map/reduce. Used by PouchDB map/reduce to maintain consistent CouchDB collation ordering.

[Crypto Pouch](https://github.com/calvinmetcalf/crypto-pouch)

Encrypt a PouchDB/CouchDB database.

[Pouch Dat](https://github.com/calvinmetcalf/pouch-dat)

Replicate from PouchDB to Dat.

[PouchDB Hyperbee](https://github.com/RangerMauve/pouchdb-adapter-hyperbee)

Sparsely load data from Hyperbee over p2p networks in Node and the Browser.

[Pouch Datalog](https://github.com/dahjelle/pouch-datalog)

Implement the Datalog query language on PouchDB, with indexes.

[PouchDB Dump](https://github.com/nolanlawson/pouchdb-dump-cli) and [PouchDB Load](https://github.com/nolanlawson/pouchdb-load)

Dump a PouchDB/CouchDB to a file, then load it wholesale. Designed for fast initial replication.

[Delta Pouch](https://github.com/redgeoff/delta-pouch)

Implements the handy "every document is a delta" pattern, so you don't have to deal with conflicts.

[PouchDB Erase](https://github.com/marten-de-vries/pouchdb-erase)

A replicating db.destroy() alternative.

[PouchDB Full Sync](https://github.com/nolanlawson/pouchdb-full-sync)

Fully replicate two PouchDB/CouchDB databases, preserving absolutely all revision history.

[PouchDB GQL](https://github.com/pouchdb/GQL)

Google Query Language (GQL) queries with PouchDB.

[PouchDB Hoodie API](https://github.com/hoodiehq/pouchdb-hoodie-api)

Hoodie-like API for PouchDB. (Documentation)

[PouchDB Hoodie Store Client](https://www.npmjs.com/package/@hoodie/store-client)

PouchDB Hoodie-like API for data persistence & offline sync.

[PouchDB List](http://python-pouchdb.marten-de-vries.nl/plugins.html)

Allows you to re-use your CouchDB list functions on the client side. (Documentation)

[Pouch Mirror](https://github.com/colinskow/pouch-mirror)

Creates a synced in-memory mirror of a remote CouchDB for faster reads.

[PouchDB No-Eval Map/Reduce](https://github.com/evidenceprime/pouchdb.mapreduce.noeval)

Allows you to use the query() API in environments that disallow eval(), like Chrome packaged apps.

[Peer Pouch](https://github.com/natevw/PeerPouch)

PouchDB over WebRTC. (Note: only works with PouchDB 1.1.)

[PouchDB Resolve Conflicts](https://github.com/jo/pouch-resolve-conflicts)

Plugin to assist in PouchDB conflict resolving.

[PouchDB Migrate](https://github.com/eHealthAfrica/pouchdb-migrate)

PouchDB plugin for running data migrations.

Full-text search engine on top of PouchDB.

[Relational Pouch](https://github.com/nolanlawson/relational-pouch)

A relational database API on top of PouchDB/CouchDB.

[PouchDB Replication Stream](https://github.com/nolanlawson/pouchdb-replication-stream)

Replicate between CouchDB/PouchDB using streams.

[PouchDB Rewrite](http://python-pouchdb.marten-de-vries.nl/plugins.html)

A PouchDB plugin that allows you to re-use your CouchDB rewrites on the client side. (Documentation)

[PouchDB Show](http://python-pouchdb.marten-de-vries.nl/plugins.html)

A PouchDB plugin that allows you to re-use your CouchDB show functions on the client side. (Documentation)

[SocketPouch](https://github.com/nolanlawson/socket-pouch)

PouchDB/CouchDB replication over WebSockets, using Engine.io (Socket.io).

[PouchDB Spatial](https://github.com/pouchdb/geopouch)

Multidimensional and spatial queries with PouchDB.

[PouchDB Geospatial](https://github.com/dpmcmlxxvi/pouchdb-geospatial)

PouchDB geospatial querying of GeoJSON objects that supports the DE-9IM spatial predicates. (Documentation)

[Superlogin](https://www.npmjs.com/package/superlogin)

Powerful authentication for APIs and single page apps using the CouchDB ecosystem, which supports a variety of providers.

[Store.PouchDB](https://github.com/chunksnbits/store.pouchdb)

ORM-style storage plugin for PouchDB.

[Pouch Stream](https://github.com/calvinmetcalf/PouchStream)

A plugin to let PouchDB talk streams.

[Transform Pouch](https://github.com/nolanlawson/transform-pouch)

Transforms documents before and after storage, e.g. for encryption, compression, or massaging data.

[PouchDB Update](http://python-pouchdb.marten-de-vries.nl/plugins.html)

A PouchDB plugin that allows you to re-use your CouchDB update functions on the client side. (Documentation)

[PouchDB Upsert](https://github.com/nolanlawson/pouchdb-upsert)

Convenience functions for working with documents: upsert() and putIfNotExists().

[PouchDB Validation](http://python-pouchdb.marten-de-vries.nl/plugins.html)

A PouchDB plugin that allows you to re-use your CouchDB validate_doc_update functions on the client side. (Documentation)

[WorkerPouch](http://github.com/nolanlawson/worker-pouch)

PouchDB adapter for web workers, so that PouchDB blocks the DOM less.

{% include anchor.html title="Server Side" hash="Server Side" %}

[PouchDB Server](https://github.com/pouchdb/pouchdb-server)

A standalone CouchDB-style REST interface server to PouchDB.

[Express PouchDB](https://github.com/pouchdb/express-pouchdb)

An Express submodule with a CouchDB-style REST interface to PouchDB. Powers PouchDB Server.

[Express PouchDB Replication Stream](https://github.com/conor-mac-aoidh/express-pouchdb-replication-stream)

Server-side Express endpoint to deliver a stream from PouchDB Replication Stream.

[Howler](https://github.com/redgeoff/couchdb-howler)

Use web sockets to subscribe to CouchDB global changes

[Pouch Websocket Sync](https://github.com/pgte/pouch-websocket-sync)

Sync several PouchDBs through websockets. Supports reconnection, negotiation and authentication.

[Pouch Remote Stream](https://github.com/pgte/pouch-remote-stream#readme)

Consume a remote PouchDB stream. Goes well with pouch-stream-server on the server side.

[Pouch Stream Server](https://github.com/pgte/pouch-stream-server#readme)

PouchDB stream server. Serves generic PouchDB object streams. Goes well with pouch-remote-stream on the client.

[Spiegel](https://github.com/redgeoff/spiegel)

Scalable replication and change listening for CouchDB

{% include anchor.html title="Framework adapters" hash="framework_adapters" %}

Angular

[angular-pouchdb](https://github.com/angular-pouchdb/angular-pouchdb)

Wrapper for using PouchDB within Angular.js.

[Factoryng - AngularJS Adapter](https://github.com/redgeoff/factoryng)

An all-in-one AngularJS factory that wraps PouchDB and Delta Pouch.

[ngPouch](https://github.com/jrhicks/ngPouch)

Angular service to persist remote connection settings and maintain continuous replication.

[ng-pouchdb](https://github.com/danielzen/ng-pouchdb)

AngularJS binding for PouchDB.

Ampersand

[ampersand-collection-pouchdb-mixin](https://github.com/svnlto/ampersand-collection-pouchdb-mixin)

A mixin for extending ampersand-collection with pouchdb persistence.

Backbone

[Backbone PouchDB](https://github.com/jo/backbone-pouch)

Backbone PouchDB Sync Adapter.

Ember

[Ember Pouch](https://github.com/nolanlawson/ember-pouch)

Ember Data adapter for PouchDB/CouchDB.

[ember-pouchdb](https://github.com/taras/ember-pouchdb)

Promisy PouchDB wrapper for Ember.js.

[GopherJS](https://github.com/gopherjs/gopherjs)

[Kivik](https://github.com/go-kivik/kivik)

Kivik provides a common interface to CouchDB or CouchDB-like databases for Go and GopherJS. (PouchDB driver)

Kendo UI

[kendo-pouchdb](https://github.com/terikon/kendo-pouchdb)

Kendo UI DataSource adapter.

React/Flux

[react-pouchdb](https://github.com/ArnoSaine/react-pouchdb)

React wrapper for PouchDB that also subscribes to changes.

[pouch-redux](https://github.com/UXtemple/pouch-redux)

Pouch and Redux integration. With Pouch in control this time around.

[redux-pouchdb](https://github.com/vicentedealencar/redux-pouchdb)

Sync store state to PouchDB.

[redux-pouch](https://github.com/UXtemple/redux-pouch)

PouchDB-backed Redux.

[pouch-redux-middleware](https://github.com/pgte/pouch-redux-middleware#readme)

Redux middleware to sync a PouchDB database with the Redux state.

Vue.js

[pouch-vue](https://github.com/MDSLKTR/pouch-vue)

Syncs PouchDB data with Vue.js components using Mango Selectors

[vue-pouch-db](https://github.com/QurateInc/vue-pouch-db)

Vue Pouch DB is a VueJS Plugin that binds PouchDB with Vue and keeps a synchronised state with the database. Has support for Mango queries which are processed locally within the VuePouchDB state.

{% include anchor.html title="Other languages" hash="Other languages" %}

[Python-PouchDB](http://python-pouchdb.marten-de-vries.nl/)

A Python interface to PouchDB, with both a synchronous and an asynchronous API. Uses QtWebKit internally (via either PySide, PyQt4 or PyQt5). Some PouchDB plugins are also wrapped. (Documentation / Launchpad)

[PouchDroid](https://github.com/nolanlawson/PouchDroid/)

Android adapter with a native Java interface to PouchDB.

{% include anchor.html title="Tools" hash="Tools" %}

[blob-util](https://github.com/nolanlawson/blob-util)

Shims and utils for working with binary Blobs in the browser.

[Pouchy](https://www.npmjs.com/package/pouchy)

PouchDB sugar API. (Github)

[Puton](http://puton.jit.su/)

A bookmarklet for inspecting PouchDB databases within the browser. (Github)

[Revision Tree Visualizer](http://neojski.github.io/visualizeRevTree)

A tool drawing revision tree of a couchdb document. You can see what is a conflict, which revisions are deleted and which is winning. (Github)


Faq (`docs/faq.md`)


layout: 2ColLeft.html title: FAQ sidebar: nav.html

{% include anchor.html class="h3" title="Can PouchDB sync with MongoDB/MySQL/my current non-CouchDB database?" hash="sync_non_couchdb" %}

No, your backend needs to speak the CouchDB replication protocol. The magic of PouchDB <–> CouchDB sync comes from this design, which in particular requires all documents to be versioned with the _rev marker. This allows PouchDB and CouchDB to elegantly handle conflicts, among other benefits.

{% include anchor.html class="h3" title="What can PouchDB sync with?" hash="what_can_pouchdb_sync_with" %}

There are a number of databases that implement a CouchDB-like protocol, and PouchDB should be able to replicate with them. They include:

  • CouchDB – CouchDB is our primary reference database and is used for automated testing.
  • Cloudant – A cluster-aware fork of CouchDB.
  • PouchDB Server – An HTTP API written on top of PouchDB. Additionally, it supports alternate backends like in-memory, Redis, Riak and MySQL via the LevelUP ecosystem. Note that your application must use the PouchDB API rather than directly modifying the database, however.

{% include anchor.html class="h3" title="The web is nice, but I want to build a native app?" hash="native_support" %}

PouchDB is one of multiple projects that implement the CouchDB protocol, and these can all be used to sync the same set of data.

For desktop applications, you may want to look into embedding CouchDB (or rcouch). PouchDB also works great with web-based frameworks like node-webkit, Chrome apps, Electron and WinJS.

For mobile applications, you can use PouchDB within PhoneGap/Cordova (optionally using the SQLite Plugin), or there are several native libraries:

iOS:

Android:

{% include anchor.html class="h3" title="How much data can PouchDB store?" hash="data_limits" %}

In Firefox, PouchDB uses IndexedDB. Though Firefox has no upper limit besides disk space, if your application wishes to store more than 50MB locally, Firefox will ask the user using a non-modal dialog to confirm that this is okay.

Chrome also uses IndexedDB, and it determines the amount of storage available on the user’s hard drive and uses that to calculate a limit.

Opera 15+ shares a codebase with Chromium/Blink, and behaves similarly.

Internet Explorer 10+ has a hard 250MB limit, and will prompt the user with a non-modal dialog at 10MB.

Mobile Safari on iOS has a hard 50MB limit for WebSQL, whereas desktop Safari has no limit. Both will prompt the user with a modal dialog if an application requests more than 5MB of data, at increments of 5MB, 10MB, 50MB, 100MB, etc. Some versions of Safari have a bug where they only let you request additional storage once, so you'll need to request the desired space up-front. PouchDB allows you to do this using [the size option]({{ site.baseurl }}/api.html#create_database).

iOS Web Application, a page saved on the homescreen behaves differently than apps in Mobile Safari (at least from iOS 9.3.2+). No specifics are published online by Apple, but WebSQL storage seems not limited to 50mb and there will not be any prompts when requesting data storage. Use <meta name="apple-mobile-web-app-capable" content="yes"> in your html header and use Add to Home Screen in the share menu of Safari. Please note, IndexedDB is now available as of iOS 10.3. It seems there is different behavior for different models of iPad and iPhone. You can check your mileage using the storage abuser, which you can Add to Home Screen on your device. Caveat: when iOS is running low on storage space, the OS might decide to delete all data without any notice or warning to the end user. Be sure to use devices with plenty of spare space, or your users will lose unsynced data.

Android works the same as Chrome as of 4.4+ (IndexedDB), while older versions can store up to 200MB (WebSQL).

In PhoneGap/Cordova, you can have unlimited data on both iOS and Android by using the SQLite Plugin.

For more information, see Working with quota on mobile browsers.

{% include anchor.html class="h3" title="What data types does PouchDB support?" hash="data_types" %}

PouchDB has two types of data: documents and attachments.

Documents

As in CouchDB, the documents you store must be serializable as JSON. Modifying the Object prototype or storing classes is not supported.

IndexedDB will actually support non-JSON data (e.g. Dates aren't stringified), but you should not rely on this, because CouchDB, LevelDB, and Web SQL do not behave the same.

Attachments

PouchDB also supports attachments, which are the most efficient way to store binary data. Attachments may either be supplied as base64-encoded strings or as Blob objects.

Different backends have different strategies for storing binary data, which may affect the overall database size. Attachment stubs have a length property that describes the number of bytes in the Blob object, but under the hood, it may actually take up more space than that.

PouchDB's strategies are:

  • Blob: data is stored in a true binary format. The most efficient method.
  • UTF-16 Blob: blobs are coerced to UTF-16, so they takes up 2x the normal space.
  • Base-64: data is stored as a base-64-encoded string. The least efficient method.

Here are the strategies used by various browsers in PouchDB:

Adapter IE (10+) Firefox Chrome Android Chrome >= 43 Safari iOS < 8 Safari = 7.1,iOS = 8 Safari >= 10.1,iOS >= 10.3
IndexedDB Blob Blob Base-64 Blob Blob
WebSQL Blob Blob UTF-16 Blob Blob

Attachments are deduplicated based on their MD5 sum, so duplicate attachments won't take up extra space.

To truly remove an attachment from the data store, you will need to use [compaction]({{ site.baseurl }}/api.html#compaction) to remove document revisions that reference that attachment.

{% include anchor.html class="h3" title="Is it safe to upgrade PouchDB?" hash="safe_to_upgrade" %}

Since v1.0.0, PouchDB has supported automatic schema migrations. This means that if you open a database that was built with an older version of PouchDB, the newer version will run all the steps necessary to get the old database up to date. We have extensive tests in place to guarantee that this feature works correctly.

Even in the case of a major version release, PouchDB still performs the schema migrations. So for instance, you can create a database with PouchDB 1.0.0 and it will still work after you open it in 3.0.0.

Once a database is migrated, however, you can no longer open it with an older version of PouchDB. So if an update contains a migration, it will be clearly marked in the release notes.

{% include anchor.html class="h3" title="How is PouchDB different from CouchDB?" hash="couchdb_differences" %}

PouchDB is also a CouchDB client, and you should be able to switch between a local database or an online CouchDB instance without changing any of your application's code.

However, there are some minor differences to note:

View Collation - CouchDB uses ICU to order keys in a view query; in PouchDB they are ASCII ordered.

View Offset - CouchDB returns an offset property in the view results. In PouchDB, offset just mirrors the skip parameter rather than returning a true offset.


Learn (`docs/learn.md`)


layout: 2ColLeft.html title: About PouchDB sidebar: nav.html

PouchDB is an in-browser database that allows applications to save data locally, so that users can enjoy all the features of an app even when they're offline. Plus, the data is synchronized between clients, so users can stay up-to-date wherever they go.

PouchDB also runs in Node.js and can be used as a direct interface to CouchDB-compatible servers. The API works the same in every environment, so you can spend less time worrying about browser differences, and more time writing clean, consistent code.

PouchDB is a free open-source project, written in JavaScript and driven by our wonderful community. If you want to get involved, then check out the contributing guide.

{% include anchor.html class="h3" title="Browser Support" hash="browser_support" %}

PouchDB supports all modern browsers, using IndexedDB under the hood and falling back to WebSQL where IndexedDB isn't supported. It is fully tested and supported in:

  • Firefox 29+ (Including Firefox OS and Firefox for Android)
  • Chrome 30+
  • Safari 5+
  • Internet Explorer 10+
  • Opera 21+
  • Android 4.0+
  • iOS 7.1+
  • Windows Phone 8+

PouchDB also runs in Cordova/PhoneGap, NW.js, Electron, and Chrome apps. It is framework-agnostic, and you can use it with Angular, React, Ember, Backbone, or your framework of choice. There are [many adapters]({{ site.baseurl }}/external.html#framework_adapters), or you can just use PouchDB as-is.

PouchDB requires a modern ES5 environment, so if you need to support older browsers (IE <10, Android <4.0, Opera Mini), then you should include the es5-shim library. You can also use the LocalStorage and in-memory adapters, or fall back to a live CouchDB.

{% include anchor.html class="h3" title="Node.js" hash="node_js" %}

In Node.js, PouchDB uses LevelDB under the hood, and also supports many other backends via the LevelUP ecosystem.

PouchDB can also run as its own CouchDB-compatible web server, using PouchDB Server.


Offline (`docs/offline.md`)


layout: default.html title: Looks like you're offline... edit: false

You need to be online to see this page, once you've seen it once it'll be available offline in the future!

Users (`docs/users.md`)


layout: 2ColLeft.html title: Who's using PouchDB? sidebar: nav.html

A list of known products and services that are using PouchDB.

Newt

{% include img.html width=100 src="newt.png" alt="Newt" %}

Newt lets you read, listen and write thousands of books across devices natively. It runs on Windows, Linux, macOS, Android, iOS & web. Its backed is powered by CouchDB and it uses PouchDB on the web client, desktop client and mobile native app for offline access, live sync, revisions and much more.

BikeCommute

BikeCommute is a FirefoxOS app that registers an NFC tag to track bike commuters in the Mozilla Portland office. Built using Famo.us, PouchDB, and CouchDB. See a video of it in action or read about it on Mozilla Hacks.

Cloudwall

{% include img.html width=200 src="cloudwall.png" alt="Cloudwall" %}

Cloudwall is an operating system for noBackend webapps, based on CouchDB and PouchDB.

Cozy Cloud

{% include img.html width=125 src="cozy.png" alt="Cozy Cloud" %}

Cozy is a personal cloud that you can host, customize, and fully control. It syncs contacts, calendars, and files between your personal devices and server. Under the hood, it leverages CouchDB and PouchDB.

Delta

Delta is a command-line utility for text diffs. View split diffs in the browser with syntax highlighting, or in the command-line using the --cli flag.

DevITJobs

DevITJobs is a job board for the tech industry with mandatory salary ranges & tech stacks.

eHealth Africa

{% include img.html width=200 src="ehealth_africa.png" alt="eHealth Africa" %}

eHealth Africa is an American-Nigerian NGO specialising in the development and deployment of tech for health. To tackle the Ebola outbreak, they built mobile apps and dashboards to help track the spread of infection in the field. The combination of CouchDB and PouchDB enabled these apps to work consistently despite the extreme network unreliability of sub-saharan Africa.

Financier

Financier is a freemium personal budgeting app that uses PouchDB to store your budget data. The paid version syncs data with CouchDB 2 for data persistence and sharing across devices. The app is fully integrated with the PouchDB changes feed for instant updates. More about the stack is available in humans.txt.

GRADEpro GDT

{% include img.html width=150 src="gradepro.png" alt="GRADEpro GDT" href="http://gradepro.org/" %}

GRADEpro GDT is an easy to use, all‐in‐one web solution to summarize and present information for healthcare decision making. PouchDB is instrumental for both the collaboration features of GRADEpro (so many users can work on the same data simultaneously) and implementation of the offline mode. The tool is used in more than 135 countries, some of which have poor Internet connectivity, as well as by scientists, who are frequent travelers and need to use the application e.g. on a plane.

Hoodie

{% include img.html width=150 src="HoodieLogo.png" alt="Hoodie" href="http://hood.ie/" %}

Hoodie provides a complete backend solution for your frontend code. It helps you develop your web application fast and easy. Hoodie-based apps are offline-first so they are usable anytime. Just plug Hoodie’s API into your frontend code, and your app is ready.

HospitalRun

{% include img.html width=150 src="hospitalrun-logo.svg" alt="HospitalRun" href="http://hospitalrun.io/" %}

HospitalRun is an open source software product designed specifically for developing world hospitals, making usability the key requirement. Using PouchDB and offline-first design, HospitalRun allows records to be carried to remote clinics, functioning when there is no Internet, and syncing when there is.

Local NPM

Local NPM allows you to easily set up a local NPM server that caches data from the real NPM and updates in realtime. The goal is to cut down network request time by moving data closer to the client. Built using Node.js, LevelDB, and PouchDB.

Lullabot

Lullabot is an interactive strategy, design, and development company. We create delightful experiences using Drupal and open source technologies. Our main website is using Drupal as the back-end for content management, CouchDB with PouchDB for the API, and ReactJS for the front-end.

MBTA Alerts

{% include img.html width=100 src="mbta_alerts.jpeg" alt="MBTA Alerts" %}

MBTA Alerts is a Twitter bot that thousands of Bostonians depend upon to be notified of delays in the MBTA transit system. Uses Node.js, PouchDB, and CouchDB.

Medic Mobile

{% include img.html src="medic-mobile.png" alt="Medic Mobile" %}

Medic Mobile is a software toolkit that combines smart messaging, decision support, easy data gathering and management, and health system analytics. Thanks to PouchDB our tools are offline first so workers can file reports wherever they are and sync when back online.

MoneyTracker.cc

MoneyTracker is an open-source personal finances tracking web app. This app can work offline on desktop, tablet and mobile.
Data is stored locally on device in PouchDB database and can be synced to the cloud.

NPM Browser

NPM Browser is a fully offline cache of NPM packages, which runs in your browser, using Angular.js, PouchDB, and the pouchdb-load plugin.

Pokedex.org

{% include img.html width=100 src="pokedexorg-logo.png" alt="Pokedex.org logo" %}

Pokedex.org is a progressive web app, powered by ServiceWorker, PouchDB, virtual-dom, and web workers.

Quizster

{% include img.html width=100 src="quizster.svg" alt="Quizster" href="https://quizster.co" %}

Quizster is a photo-based submission and feedback system. Quizster uses PouchDB to allow real-time communication between students and teachers.

Squarespace Blog

{% include img.html width=100 src="squarespace_blog.png" alt="Squarespace Blog" %}

Squarespace Blog is an Android and iOS app that gives you the tools you need to write and edit posts on multiple Squarespace websites. It uses PouchDB attachments inside a WebView for fast offline images.

Story-writer

Story-writer is a free Markdown editor, with a web version and NW.js client version, built using Node.js, NW.js, LevelDB, and PouchDB.

Thali project

Thali is a Microsoft-sponsored open-source platform for creating apps that exploit the power of personal devices and put people in control of their data. It uses Cordova, PouchDB, OpenSSL, and Tor.

StudyMD

{% include img.html width=100 src="studymd.png" alt="StudyMD" %}

StudyMD transforms your markdown files into flashcards. It's an electronjs app based on reactjs. PouchDB is used in Node.js for storing the flashcards in a LevelDB database.

YLD

YLD is a Node.js software engineering, consulting and training company. We partner with enterprises to strengthen their software engineering culture and create the agility necessary to compete in today’s market, and are responsible for some of the largest Node.js solutions in production today. PouchDB allows us to create responsive, resilient and sync-enabled web applications.

FactoryTalk TeamONE

{% include img.html src="TeamONE.png" alt="FactoryTalk TeamONE" %}
FactoryTalk® TeamONE™ delivers one new experience and helps industrial teams improve productivity. Created by Rockwell Automation. We're using PouchDB for offline access and team synchronization. Looking to use the Thali project (also a PouchDB client listed here) to enable true peer-to-peer connectivity. Connect with us on twitter (@ROKTeamONE.)


Data Authors.Json (`docs/_data/authors.json`)

[
{
"name": "Nick Colley",
"twitter": "nickcolley",
"github": "nickcolley",
"www": "http://nickcolley.co.uk",
"avatar": "nick_colley.jpeg"
},
{
"name": "Nolan Lawson",
"twitter": "nolanlawson",
"github": "nolanlawson",
"www": "http://nolanlawson.com",
"avatar": "nolan_lawson.png"
},
{
"name": "Dale Harvey",
"twitter": "daleharvey",
"github": "daleharvey",
"www": "http://arandomurl.com",
"avatar": "dale_harvey.jpeg"
},
{
"name": "Calvin Metcalf",
"twitter": "CWMma",
"github": "calvinmetcalf",
"www": "http://calvinmetcalf.com/",
"avatar": "calvin_metcalf.jpeg"
},
{
"name": "Giovanni Ornaghi",
"twitter": "sphaso",
"github": "sphaso",
"www": "https://it.linkedin.com/in/giovanniornaghi",
"avatar": "giovanni_ornaghi.jpeg"
},
{
"name": "Garren Smith",
"twitter": "garrensmith",
"github": "garrensmith",
"www": "http://www.redcometlabs.com",
"avatar": "garren_smith.png"
},
{
"name": "Will Holley",
"twitter": "wilhol",
"github": "willholley",
"www": "https://github.com/willholley/"
},
{
"name": "Gareth Bowen",
"twitter": "garethjtbowen",
"github": "garethbowen",
"www": "https://bowenwebdesign.co.nz",
"avatar": "gareth_bowen.png"
},
{
"name": "Alba Herrerías",
"twitter": "alba_dev",
"github": "AlbaHerrerias",
"www": "https://www.albaherrerias.dev",
"avatar": "alba_herreiras.png"
}
]


Guides Async Code (`docs/guides/async-code.md`)


index: 6 layout: guide.html title: Asynchronous code sidebar: guides_nav.html

PouchDB provides a fully asynchronous API. This ensures that when you talk to PouchDB, the UI doesn't stutter, because the DOM is not being blocked by database operations.

However, working with asynchronous code can be very complex, especially if you're only accustomed to synchronous APIs. So it's worth going over some of the basics.

{% include anchor.html title="I promise to call you back..." hash="i-promise-to-call-you-back" %}

To make things as flexible as possible for PouchDB users, the API is provided in both callback format and promise format.

The callback format looks like this:

js
db.get('mittens', function (error, doc) {
  if (error) {
    // oh noes! we got an error
  } else {
    // okay, doc contains our document
  }
});

The promise format looks like this:

js
db.get('mittens').then(function (doc) {
  // okay, doc contains our document
}).catch(function (err) {
  // oh noes! we got an error
});

Basically, if you include a callback as the last argument in a function, then PouchDB assumes you want the callback style. Otherwise it assumes you want the promise style.

{% include anchor.html title="Let's talk about promises" hash="lets-talk-about-promises" %}

For this guide, we will use the promise format for a few reasons:

  1. Callbacks easily lead to spaghetti code, or to the pyramid of doom.
  2. Promises generally lead to better code organization, although they do have a steep learning curve.

If you already understand promises, you can skip to the next section.

{% include alert/start.html variant="info"%}
{% markdown %}

What about async/await? Async functions are an experimental ES7 syntax that enhances promise-based APIs by adding
the async and await keywords. For more information about async/await, read [our introductory blog post]({{ site.baseurl }}/2015/03/05/taming-the-async-beast-with-es7.html).

{% endmarkdown %}
{% include alert/end.html%}

{% include anchor.html title="Understanding promises" hash="understanding-promises" %}

If you have the time, you are strongly encouraged to watch this 50-minute video: "Redemption from Callback Hell". The rest of this chapter basically summarizes that video.

The best way to think of promises is that they bring keywords like return and try/catch to asynchronous code.

Synchronous code:

js
function returnSomething() {
  try {
    doSomething();
    doSomethingElse();
    return true;
  } catch (err) {
    console.log(err);
  }
}

Asynchronous code:

js
function returnSomething() {
  return doSomething().then(function () {
    return doSomethingElse();
  }).then(function () {
    return true;
  }).catch(function (err) {
    console.log(err);
  });
}

{% include anchor.html title="Use catch() to catch errors" hash="use-catch-to catch errors" %}

The big advantage of working with Promises in asynchronous code is that you can always attach a catch function to the end of a big promise chain, and any errors that occur along the way will show up at the end.

This avoids endless if (err) {} checking in the callback world:

js
doSomething(function (err, result) {
  if (err) {
    // handle error
  }
  doSomethingElse(function (err, result) {
    if (err) {
      // handle error again...
    }
    doSomethingYetAgain(function (err, result) {
      if (err) {
        // seriously? okay, handle error again...
      }
    });
  });
});

Instead, in the promise world, you can have a long chain of asynchronous operations with a single catch at the end. To use PouchDB as an example:

js
db.put({_id: 'charlie', age: 21}).then(function () {
  return db.get('charlie');
}).then(function (charlie) {
  // increment Charlie's age
  charlie.age++;
  return db.put(charlie);
}).then(function () {
  return db.get('charlie');
}).then(function (charlie) {
  // increment Charlie's age again
  charlie.age++;
  return db.put(charlie);
}).then(function () {
  return db.get('charlie');
}).then(function (charlie) {
  console.log(charlie);
}).catch(function (err) {
  console.log(err);
});

You should see:

js
{"age":23,"_id":"charlie","_rev":"3-e794618b4e39ed566cc68b56f5426e8e"}

You can see a live example of this code.

In this example, we put/get a document 3 times in a row. At the very end, there is a catch() statement to catch any errors along the way.

What kind of errors might we run into? Well, let's imagine that we accidentally misspell the id 'charlie' at some point. In this case, we will gracefully catch the error. Here's another live example.

You should see:

js
{"status":404,"name":"not_found","message":"missing"}

This is really nice! No matter where the misspelling is, the error can be handled within a single function. That's much nicer than having to do if (err){} an endless number of times!

{% include anchor.html title="An alternate way of catching errors" hash="an-alternate-way-of-catching-errors" %}

If you've been doing promises for awhile, you might have seen this instead:

js
db.get('charlie').then(function (charlie) {
  // we got the charlie doc
}, function (err) {
  // we got an error
})

This is equivalent to:

js
db.get('charlie').then(function (charlie) {
  // we got the charlie doc
}).catch(function (err) {
  // we got an error
})

The catch() method is just syntactic sugar. You can use either format.

{% include anchor.html title="Promises 101" hash="promises-101" %}

The then() method takes a function. What can you do within this function? Three things:

  • Return another promise
  • Throw an error
  • Return a non-promise object (or undefined)

Another way to think of it is this:

js
db.get('charlie').then(function (charlie) {
  // Within this function, you can do
  // try/catch/return like you normally would,
  // and it will be handled asynchronously!
}).then(function (result) {
  // If the previous function returned something
  // (or returned undefined), it will show up here
  // as "result".
}).catch(function (err) {
  // If the previous function threw an error,
  // it will show up here as "err".
});

{% include anchor.html title="Next" hash="next" %}

Now that you have a grasp on promises, let's learn about updating and deleting documents.


Guides Attachments (`docs/guides/attachments.md`)


index: 9 layout: guide.html title: Working with attachments sidebar: guides_nav.html

Attachments are where PouchDB can get really fun.

The big difference between storage engines like WebSQL/IndexedDB and the older localStorage API is that you can stuff a lot more data in it.

PouchDB attachments allow you to use that to full advantage to store images, MP3s, zip files, or whatever you want.

{% include anchor.html title="How attachments are stored" hash="how-attachments-are-stored" %}

As their name implies, attachments are attached to documents. You can work with attachments either in base64-encoded format, or as a Blob.

For example, here is a very simple document with a plain text attachment, stored as base64.

js
db.put({
  _id: 'mydoc',
  _attachments: {
    'myattachment.txt': {
      content_type: 'text/plain',
      data: 'aGVsbG8gd29ybGQ='
    }
  }
});

Our document has the usual _id field, but it also has a special _attachments field that holds the attachments. Documents can have as many attachments as you want.

{% include alert/start.html variant="info" %}

When you create an attachment, you need to specify its content_type, otherwise known as the MIME type. Common MIME types include 'text/plain' for plain text, 'image/png' for PNG images, and 'image/jpeg' for JPG images.

{% include alert/end.html %}

As it turns out, 'aGVsbG8gd29ybGQ=' is just the string 'hello world' encoded in base64. You can use the atob() and btoa() methods in your browser to verify.

js
btoa('hello world')      // "aGVsbG8gd29ybGQ="
atob('aGVsbG8gd29ybGQ=') // "hello world"

Let's see what happens after we store this document. If you try to get() it normally, you may be surprised to see that the attachment data itself isn't returned:

js
db.get('mydoc').then(function (doc) {
  console.log(doc);
});

The returned document will look like this:

js
{
  "_attachments": {
    "myattachment.txt": {
      "content_type": "text/plain",
      "digest": "md5-XrY7u+Ae7tCTyyK7j1rNww==",
      "stub": true
    }
  },
  "_id": "mydoc",
  "_rev": "1-e8a84187bb4e671f27ec11bdf7320aaa"
}

You can see a live example of this code.

By default, PouchDB will only give you an attachment stub, which contains a digest, i.e. the md5sum of the binary attachment.

To get the full attachments when using get() or allDocs(), you need to specify {attachments: true}:

js
db.get('mydoc', {attachments: true}).then(function (doc) {
  console.log(doc);
});

Then you'll get back the full attachment, base64-encoded:

js
{
  "_attachments": {
    "myattachment.txt": {
      "content_type": "text/plain",
      "digest": "md5-XrY7u+Ae7tCTyyK7j1rNww==",
      "data": "aGVsbG8gd29ybGQ="
    }
  },
  "_id": "mydoc",
  "_rev": "1-e8a84187bb4e671f27ec11bdf7320aaa"
}

You can see a live example of this code.

{% include anchor.html title="Image attachments" hash="image-attachments" %}

Plaintext is cool and all, but you know what would be really awesome? Storing images.

So let's do it! In this example, we'll put a document with a small icon attachment, represented as a base64-encoded string. Then we'll fetch it and display the icon as a normal `` tag:

text
/* Detailed source-code truncated for AI context efficiency. */

You can see a live example of this code.

You should be unsurprised to see a cat smiling back at you. If the kitten theme bothers you, then you haven't been on the Internet very long.

How does this code work? First off, we are making use of the URL.createObjectURL() method, which is a standard HTML5 method that converts a Blob to a URL that we can easily use as the src of an img.

Second off, we are using the getAttachment() API, which returns a Blob rather than a base64-encoded string. To be clear: we can always convert between base64 and Blobs, but in this case, getAttachment() is just more convenient.

{% include anchor.html title="Directly storing binary data" hash="directly-storing-binary-data" %}

Up to now, we've been supplying our attachments as base64-encoded strings. But we can also create the Blobs ourselves and store those directly in PouchDB.

Another shortcut we can use is the putAttachment() API, which simply modifies the existing document to hold a new attachment. Or, if the document does not exist, it will create an empty one.

{% include alert/start.html variant="info" %}

In Node.js, PouchDB uses Buffers instead of Blobs. Otherwise, the same rules apply.

{% include alert/end.html %}

For instance, we can read the image data from an `` tag using a canvas element, and then directly write that Blob to PouchDB:

js
function convertImgToBlob(img, callback) {
   const canvas = document.createElement('canvas');
   const context = canvas.getContext('2d');
   context.drawImage(img, 0, 0);

    // Warning: toBlob() isn't supported by every browser.
    // You may want to use blob-util.
   canvas.toBlob(callback, 'image/png');
}

const catImage = document.getElementById('cat');
convertImgToBlob(catImage, function (blob) {
  db.putAttachment('meowth', 'meowth.png', blob, 'image/png').then(function () {
    return db.get('meowth', {attachments: true});
  }).then(function (doc) {
    console.log(doc);
  });
});

You can see a live example of this code.

This stores exactly the same image content as in the other example, which you can confirm by checking the base64-encoded output.

{% include alert/start.html variant="warning" %}

Blobs can be tricky to work with, especially when it comes to cross-browser support.
You may find blob-util to be a useful
addition to the attachment API. For instance, it has an
imgSrcToBlob() method that will work cross-browser.

{% include alert/end.html %}

{% include anchor.html title="Allow the user to store an attachment" hash="allow-the-user-to-store-an-attachment" %}

You can also upload a file with the HTML5 File API and store it directly in the database, because the data you get from the `` element is already a Blob.
(See: Blob API and File API, which inherits properties from the Blob Interface.)

Here is an example of allowing a user to choose a file from their filesystem:

html
<input type="file">

And then "uploading" that file directly into PouchDB:

js
const input = document.querySelector('input');
input.addEventListener('change', function () {
  const file = input.files[0]; // file is a Blob

  db.put({
    _id: 'mydoc',
    _attachments: {
      filename: {
        content_type: file.type,
        data: file
      }
    }
  }).catch(function (err) {
    console.log(err);
  });
});

You can see a live example of this code.

Select a file and you will see the stored file, size, and type, which are valid Blob properties. If you choose an image, it will also show the image!

{% include anchor.html title="Base64 vs Blobs/Buffers" hash="base64-vs-blobs-buffers" %}

Whether you supply attachments as base64-encoded strings or as Blobs/Buffers, PouchDB will try to store them in the most efficient way.

So when you insert your attachments, either format is acceptable. For instance, you can put Blobs/Buffers using put():

js
db.put({
  _id: 'mydoc',
  _attachments: {
    'myattachment.txt': {
      content_type: 'text/plain',
      data: myBlob
    }
  }
});

And you can also pass base64-encoded strings to putAttachment():

js
db.putAttachment('mydoc', 'myattachment.png', myBase64String, 'image/png');

You can also insert multiple attachments at once using put():

js
db.put({
  _id: 'mydoc',
  _attachments: {
    'myattachment1.txt': {
      content_type: 'text/plain',
      data: myBlob1
    },
    'myattachment2.txt': {
      content_type: 'text/plain',
      data: myBlob2
    },
    'myattachment3.txt': {
      content_type: 'text/plain',
      data: myBlob3
    },
    // etc.
  }
});

The bulkDocs() and post() APIs also accept attachments in either format.

When you fetch attachments, however, getAttachment() will always return Blobs/Buffers.

The other "read" APIs, such as get(), allDocs(), changes(), and query() have an {attachments: true} option that returns the attachments base64-encoded strings. If you add {binary: true}, though, they will return Blobs/Buffers.

{% include alert/start.html variant="info" %}
{% markdown %}

Performance tip: If you can insert and retrieve your attachments using only Blobs/Buffers, then you will typically get better performance, especially when it comes to memory usage. The base64 string format is mostly provided for developer convenience and debugging.

{% endmarkdown %}
{% include alert/end.html %}

{% include anchor.html title="Blob types" hash="blob-types" %}

Blobs have their own type, but there is also a content_type that you specify when you store it in PouchDB:

js
const myBlob = new Blob(['I am plain text!'], {type: 'text/plain'});
console.log(myBlob.type); // 'text/plain'

db.put({
  _id: 'mydoc',
  _attachments: {
    'myattachment.txt': {
      content_type: 'text/plain',
      data: myBlob
    }
  }
});

The reason for this redundancy is 1) Buffers in Node do not have a type, and 2) the CouchDB attachment format requires it.

So for best results, you should ensure that your Blobs have the same type as the one reported to PouchDB. Otherwise you may see inconsistent behavior (e.g. in IndexedDB, where the Blob is stored as-is on compatible browsers).

{% include anchor.html title="Related API documentation" hash="related-api-documentation" %}

{% include anchor.html title="Next" hash="next" %}

Now that you can attach cat pictures to all your documents (and why wouldn't you?), let's talk about replication.


Guides Bulk Operations (`docs/guides/bulk-operations.md`)


index: 8 layout: guide.html title: Bulk operations sidebar: guides_nav.html

You can get(), put(), and remove() single documents to your heart's content, but a database isn't a database unless it can handle many operations at once!

PouchDB provides two methods for bulk operations - bulkDocs() for bulk writes, and allDocs() for bulk reads.

{% include anchor.html title="Use bulkDocs() to write many docs" hash="use-bulkdocs-to-write-many-docs" %}

The bulkDocs() API is very simple. It just takes a list of documents that you want to put() into the database:

js
db.bulkDocs([
  {
    _id: 'mittens',
    occupation: 'kitten',
    cuteness: 9.0
  },
  {
    _id: 'katie',
    occupation: 'kitten',
    cuteness: 7.0
  },
  {
    _id: 'felix',
    occupation: 'kitten',
    cuteness: 8.0
  }
]);

You can see a live example of this code.

This code is equivalent to put()ing each document separately:

js
db.put({
    _id: 'mittens',
    occupation: 'kitten',
    cuteness: 9.0
}).then(function () {
  return db.put({
    _id: 'katie',
    occupation: 'kitten',
    cuteness: 7.0
  });
}).then(function () {
  return db.put({
    _id: 'felix',
    occupation: 'kitten',
    cuteness: 8.0
  });
});

{% include anchor.html title="Why bulk up with bulkDocs()?" hash="why-bulk-up-with-bulkdocs" %}

Bulk operations tend to be faster than individual operations, because they can be combined into a single transaction (in a local IndexedDB/WebSQL) or a single HTTP request (in a remote CouchDB).

You can also update or delete multiple documents this way. You just need to include the _rev and _deleted values as previously discussed. The same rules as for put() apply to each individual document.

{% include alert/start.html variant="warning" %}

Neither bulkDocs() nor allDocs() constitutes a transaction in the traditional sense. That means that, if a single put() fails, you should not assume that the others will fail.

By design, CouchDB and PouchDB do not support transactions. A document is the smallest unit of operations.
{% include alert/end.html %}

{% include anchor.html title="Use allDocs() to read many docs" hash="use-alldocs-to-read-many-docs" %}

Likewise, allDocs() is a method that allows you to read many documents at once.

Most crucially, when you read from allDocs(), the documents are returned sorted by order of _id. This makes the _id a very powerful field that you can use for more than just uniquely identifying your documents.

For instance, if you refer back to the live example above, you'll notice that the kittens are sorted by their name, because their names are used as their _ids.

Another common way to take advantage of this is to use new Date().toJSON() as your document _ids. In this way, all your documents will be sorted by date.

For instance, let's save three kittens with three different dates, and then fetch them sorted by date:

js
db.put({
    _id: new Date().toJSON(),
    name: 'Mittens',
    occupation: 'kitten',
    cuteness: 9.0
}).then(function () {
  return db.put({
    _id: new Date().toJSON(),
    name: 'Katie',
    occupation: 'kitten',
    cuteness: 7.0
  });
}).then(function () {
  return db.put({
    _id: new Date().toJSON(),
    name: 'Felix',
    occupation: 'kitten',
    cuteness: 8.0
  });
}).then(function () {
  return db.allDocs({include_docs: true});
}).then(function (response) {
  console.log(response);
}).catch(function (err) {
  console.log(err);
});

You can see a live example to confirm that the kittens are sorted by the order they were put into the database.

{% include anchor.html title="Please use allDocs(). Seriously." hash="please-use-alldocs" %}

allDocs() is the unsung star of the PouchDB world. It not only returns documents in order – it also allows you to reverse the order, filter by _id, slice and dice using "greater than" and "less than" operations on the _id, and much more.

Far too many developers overlook this valuable API, because they misunderstand it. When a developer says "my PouchDB app is slow!", it is usually because they are using the slow query() API when they should be using the fast allDocs() API.

For details on how to effectively use allDocs(), you are strongly recommended to read ["Pagination strategies with PouchDB"]({{ site.baseurl }}/2014/04/14/pagination-strategies-with-pouchdb.html). For 99% of your applications, you should be able to use allDocs() for all the pagination/sorting/searching functionality that you need.

{% include anchor.html title="Related API documentation" hash="related-api-documentation" %}

{% include anchor.html title="Next" hash="next" %}

Now that you've fallen helplessly in love with bulkDocs() and allDocs(), let's turn our wandering gaze to attachments.


Guides Changes (`docs/guides/changes.md`)


index: 12 layout: guide.html title: Changes feed sidebar: guides_nav.html

One of the brilliant things about CouchDB replication is that it makes it easy to learn about changes made to the database over time. CouchDB allows you to easily answer questions like:

  • What changes occurred to the database since a given time?
  • What changes occurred to this document?
  • What did this database look like a few days ago?

For all of these and related questions, there's the changes() API.

{% include anchor.html title="Basic changes usage" hash="basic-changes-usage" %}

If you want to simply fetch all changes since the beginning of time, you can do:

js
db.changes({
  since: 0,
  include_docs: true
}).then(function (changes) {

}).catch(function (err) {
  // handle errors
});

You can see a live example of this code.

Then you will have a list of all changes made to the database, in the order that they were made.

One thing you will notice about the changes feed is that it actually omits non-leaf revisions to documents. For instance, in the live example, we skip from seq 1 immediately to seq 3.

This is by design – the changes feed only tells us about leaf revisions. However, the order of those leaf revisions is determined by the order they were put in the database. So you may notice that 'firstDoc' still appears before 'secondDoc', which appears before 'thirdDoc'.

Also notice the option {include_docs: true}. By default, the documents themselves are not included in the changes feed; only the ids, revs, and whether or not they were deleted. With {include_docs: true}, however, each non-deleted change will have a doc property containing the new or modified document.

{% include anchor.html title="Changes pagination" hash="changes-pagination" %}

If you expect this to be a very large number of changes, you can also use the limit option to do pagination:

js
const pageSize = 10;
let lastSeq = 0;
function fetchNextPage() {
  return db.changes({
    since: lastSeq,
    limit: pageSize
  }).then(function (changes) {
    if (changes.results.length < pageSize) {
      // done!
    } else {
      lastSeq = changes.last_seq;
      return fetchNextPage();
    }
  });
}

fetchNextPage().catch(function (err) {
  // handle errors
});

You can see a live example of this code.

{% include anchor.html title="seq versus _rev" hash="seq-versus-rev" %}

The changes feed lists each change with a corresponding seq integer. seq always starts with 0, and beyond that it increases monotonically. (In Cloudant, these are strings rather than integers.)

seq can be thought of as a version number for the entire database. Basically it answers the question of "How many total changes have been made to all documents in this database?" This sets it apart from the revision hash _rev, which marks the changes made to a single document.

However, the seq between two databases is not guaranteed to be kept in sync. CouchDB and PouchDB have slightly different ways of increasing their seq values, so really seq is only meaningful within a single database.

{% include anchor.html title="Live changes feed" hash="live-changes-feed" %}

Just like replication, you can also listen to a live changes feed. The way this works is very similar to the replication API:

js
db.changes({
  since: 'now'
}).on('change', function (change) {
  // received a change
}).on('error', function (err) {
  // handle errors
});

In the above example, we've also taken advantage of the quasi-magical 'now' option for since, which will give us all changes from the moment we start listening.

This can be very useful for scenarios where you want to update the UI whenever something in the database changes, such as for a real-time chat application.

{% include anchor.html title="Understanding changes" hash="understanding-changes" %}

There are two types of changes:

  • Added or modified documents
  • Deleted documents

To distinguish between the two types in a live changes() listener,
you can use the following code:

js
db.changes({
  since: 'now',
  live: true,
  include_docs: true
}).on('change', function (change) {
  // change.id contains the doc id, change.doc contains the doc
  if (change.deleted) {
    // document was deleted
  } else {
    // document was added/modified
  }
}).on('error', function (err) {
  // handle errors
});

You can see a live example of this code.

Notice that change.doc contains the document (unless it's deleted), because we used {include_docs: true}.

Also notice that new documents always have revisions starting with the string '1-'. Subsequent revisions start with '2-', '3-', '4-', etc.

{% include alert/start.html variant="info" %}

How can I distinguish between added and modified documents? Checking if the revision starts with '1-' is a pretty good trick. However, this will not work for databases that are replication targets, because replication only sends the latest versions of documents. This means that the '1-' revision may get skipped entirely, and the local database will only receive the 2nd, 3rd or 4th (etc.) revision. Conflicting revisions will also appear in the changes feed.

So the short answer is that you cannot. If you are trying to mirror changes in a non-Pouch structure (e.g. a list of DOM elements), then the best solution is to search all the DOM elements to see if the document already exists, or to re-run allDocs() for every change.

{% include alert/end.html %}

{% include anchor.html title="Related API documentation" hash="related-api-documentation" %}

{% include anchor.html title="Next" hash="next" %}

Now that we know how to hook our data spigot to the changes feed, let's look into using the very powerful find() API.


Guides Compact And Destroy (`docs/guides/compact-and-destroy.md`)


index: 15 layout: guide.html title: Compacting and destroying sidebar: guides_nav.html

By default, PouchDB and CouchDB are designed to store all document revisions forever. This is very similar to how Git works, and it helps ensure that two databases can consistently replicate with each other.

However, if you allow your database to grow without bounds, it can end up taking up much more space than you need. This can especially be a problem in browsers with storage quotas.

To mitigate this problem, PouchDB offers two recourses: compaction and destruction.

{% include anchor.html title="Compacting a database" hash="compacting-a-database" %}

When you compact a database, you tell PouchDB to optimize its current storage usage. CouchDB will do the same thing:

js
return db.compact().then(function (info) {
  // compaction complete
}).catch(function (err) {
  // handle errors
});

From the API perspective, nothing should be different about the database after compaction, except that non-leaf revisions will no longer be available.

js
db.put({_id: 'foo', version: 1}).then(function () {
  return db.get('foo');
}).then(function (doc) {
  doc.version = 2;
  return db.put(doc);
}).then(function () { )
  return db.compact();
}).then(function () {
  // DANGER!
  // From now on, revision 1 is no longer available.
}).catch(function (err) {
  // handle errors
});

You can see a live example of this code.

Compaction is a great feature, but it may not be what you desire if you want to retain a document's history from the beginning of time.

However, if that's not a concern, then compaction is a harmless operation. In fact, since leaf revisions are retained, this means that you can still do conflict resolution after compaction!

{% include anchor.html title="Auto-compaction" hash="auto-compaction" %}

If you really want to go all-in on compaction, then you can even put your database in auto_compaction mode. This means that it will automatically perform a compact() operation after every write.

js
const db = new PouchDB('mydb', {auto_compaction: true});
db.put({_id: 'foo', version: 1}).then(function () {
  return db.get('foo');
}).then(function (doc) {
  doc.version = 2;
  return db.put(doc);
}).then(function () {
  // Revision 1 is already unavailable!
}).catch(function (err) {
  // handle errors
});

You can see a live example of this code.

This feature is only available in local databases, not remote ones. On remote databases, the auto_compaction option will do nothing.

{% include anchor.html title="Destroying a database" hash="destroying-a–database" %}

We all love our databases, but sometimes good things must come to an end, and you need to snub out a database completely.

So if you want to give your database to a nice farm family upstate, then the destroy() API is for you. It's very simple:

js
new PouchDB('mydb').destroy().then(function () {
  // database destroyed
}).catch(function (err) {
  // error occurred
})

Note that destroying a database does not mean that replicated databases will also be destroyed. Destruction has nothing to do with the normal put()/remove() operations on documents, so it has no impact on replication.

Also note that in Web SQL, the database will not really be destroyed – it will just have its tables dropped. This is because Web SQL does not support true database deletion.

{% include anchor.html title="Related API documentation" hash="related-api-documentation" %}

{% include anchor.html title="Next" hash="next" %}

To wrap up, let's look at a special class of documents in PouchDB – local docs.


Guides Conflicts (`docs/guides/conflicts.md`)


index: 11 layout: guide.html title: Conflicts sidebar: guides_nav.html

Conflicts are an unavoidable reality when dealing with distributed systems. And make no mistake: client-server is a distributed system.

CouchDB and PouchDB differ from many other sync solutions, because they bring the issue of conflicts front-and-center. With PouchDB, conflict resolution is entirely under your control.

{% include alert/start.html variant="info" %}

PouchDB exactly implements CouchDB's replication algorithm, so conflict resolution works the same in both. For the purposes of this article, "CouchDB" and "PouchDB" may be used interchangeably.

{% include alert/end.html %}

{% include anchor.html title="Two types of conflicts" hash="two-types-of-conflicts" %}

In CouchDB, conflicts can occur in two places: immediately, when you try to commit a new revision, or later, when two peers have committed changes to the same document. Let's call these immediate conflicts and eventual conflicts.

Immediate conflicts

Immediate conflicts can occur with any API that takes a rev or a document with _rev as input – put(), post(), remove(), bulkDocs(), and putAttachment(). They manifest as a 409 (conflict) error:

js
const myDoc = {
  _id: 'someid',
  _rev: '1-somerev'
};
db.put(myDoc).then(function () {
  // success
}).catch(function (err) {
  if (err.name === 'conflict') {
    // conflict!
  } else {
    // some other error
  }
});

In your code, you should always be handling conflicts. No matter how unlikely it may seem, 409s can and do occur.

For instance, if you are doing live replication, a document may be modified by somebody else while the user is working on it. If the remote changes are replicated to the local database before the user tries to commit their changes, then they will receive the above 409 error.

Upsert

In many cases, the most practical solution to the 409 problem is to retry the put() until it succeeds. If the user's intended change can be expressed as a delta (i.e. a change that doesn't depend on the current revision), then this is very easy to achieve.

Borrowing a phrase from traditional databases, let's call this an upsert ("update or insert"), and use the pouchdb-upsert plugin to implement it:

js
function myDeltaFunction(doc) {
  doc.counter = doc.counter || 0;
  doc.counter++;
  return doc;
}

db.upsert('my_id', myDeltaFunction).then(function () {
  // success!
}).catch(function (err) {
  // error (not a 404 or 409)
});

This upsert() function takes a docId and deltaFunction, where the deltaFunction is just a function that takes a document and outputs a new document. (If the document does not exist, then an empty document is provided.)

pouchdb-upsert also offers a putIfNotExists() function, which will create a document if it doesn't exist already. For more details, see the plugin's documentation.

Eventual conflicts

Now, let's move on to the second type: eventual conflicts.

Imagine two PouchDB databases have both gone offline. The two separate users each make modifications to the same document, and then they come back online at a later time.

Both users committed changes to the same version of the document, and their local databases did not throw 409 errors. What happens then?

This is the classic "conflict" scenario, and CouchDB handles it very elegantly. By default, CouchDB will choose an arbitrary winner based on a deterministic algorithm, which means both users will see the same winner once they're back online. However, since the replication history is stored, you can always go back in time to resolve the conflict.

To detect if a document is in conflict, you use the {conflicts: true} option when you get() it.

js
db.get('docid', {conflicts: true}).then(function (doc) {
  // do something with the doc
}).catch(function (err) {
  // handle any errors
});

If the document has conflicts, then the doc will be returned with a _conflicts attribute, which may contain the revision IDs of conflicting revisions.

For instance, imagine the doc returned is the following:

js
{
  "_id": "docid",
  "_rev": "2-x",
  "_conflicts": ["2-y"]
}

Here we have two separate revisions (2-x and 2-y) written by two separate databases, and one database's revision (2-x) has arbitrarily won.

{% include alert/start.html variant="warning" %}
{% markdown %}

Normally, _revs look more like 2-c1592ce7b31cc26e91d2f2029c57e621, i.e. a digit followed by a very long hash. In these examples, x and y are used in place of the hash, for simplicity's sake.

{% endmarkdown %}
{% include alert/end.html %}

Notice that the document's current revision starts with 2-, and the conflicting version also starts with 2-, indicating that they're both at the same level of the revision tree. (Revision hashes start with 1-, 2-, 3-, etc., which indicates their distance from the first, "root" revision. The root always starts with 1-.)

Both databases will see the same conflict, assuming replication has completed. In fact, all databases in the network will see the exact same revision history – much like Git.

To fetch the losing revision, you simply get() it using the rev option:

js
db.get('docid', {rev: '2-y'}).then(function (doc) {
  // do something with the doc
}).catch(function (err) {
  // handle any errors
});

At this point, you can present both versions to the user, or resolve the conflict automatically using your preferred conflict resolution strategy: last write wins, first write wins, RCS, etc.

To mark a conflict as resolved, all you need to do is remove() the unwanted revisions. So for instance, to remove '2-y', you would do:

js
db.remove('docid', '2-y').then(function (doc) {
  // yay, we're done
}).catch(function (err) {
  // handle any errors
});

If you want to resolve the conflict by creating a new revision, you simply put() a new document on top of the current winner, and make sure that the losing revision is deleted.

{% include anchor.html title="Accountants don't use erasers" hash="accountants-dont-use-erasers" %}

Another conflict resolution strategy is to design your database so that conflicts are impossible. In practice, this means that you never update or remove existing documents – you only create new documents.

This strategy has been called the "every doc is a delta" strategy. A classic use-case for this would be a checkbook app, where every document is simply an operation that increases or decreases the account balance:

js
{_id: new Date().toJSON(), change: 100} // balance increased by $100
{_id: new Date().toJSON(), change: -50} // balance decreased by $50
{_id: new Date().toJSON(), change: 200} // balance increased by $200

In this system, it is impossible for two documents to conflict, because the document _ids are just timestamps. Ledger transactions are recorded in the order they were made, and at the end of the day, you only need to do an allDocs() or query() operation to sum the result.

The wisdom of this strategy can be expressed by the maxim: "Accountants don't use erasers". Like a diligent accountant, your app can just add new documents when you want to make a change, rather than going back and scrubbing out previous changes.

There is also a PouchDB plugin that implements this strategy: delta-pouch.

{% include anchor.html title="Next" hash="next" %}

Now that we've settled our conflicts, let's take a look at the changes feed.


Guides Databases (`docs/guides/databases.md`)


index: 4 layout: guide.html title: Working with databases sidebar: guides_nav.html

PouchDB databases come in two flavors: local and remote.

{% include anchor.html title="Local databases" hash="local–databases" %}

To create a local database, you simply call new PouchDB and give it a name:

js
const db = new PouchDB('kittens');

You can see a live example of this code.

{% include alert/start.html variant="info" %}

Protip: whenever you see a live example in this guide, you can download it to follow along at home! For example, to run this example, just enter the following commands in your command prompt:

git clone https://gist.github.com/bddac54b92c2d8d39241.git kittens cd kittens python -m SimpleHTTPServer # for Python 2 python -m http.server # for Python 3

Now the site is up and running at http://localhost:8000. To find the correct gist.github.com URL, just click the "block" number at the top of the page.

{% include alert/end.html %}

{% include anchor.html title="Remote databases" hash="remote-databases" %}

To create a remote database, you call new PouchDB and give it a path to a database in CouchDB.

js
const db = new PouchDB('http://localhost:5984/kittens');

{% include alert/start.html variant="info" %}
note: The remote database will not be created until you do an API call, e.g.: db.info(). The reason behind that is that the PouchDB constructor is completely
synchronous, for ease of error handling (i.e. no asynchronous errors).
{% include alert/end.html %}

The structure of a CouchDB URL is very simple:

text
http://     localhost:5984     /kittens
⌞_____⌟     ⌞____________⌟      ⌞_____⌟
   |              |                |
Protocol     Where CouchDB       database
(https if    itself is           name
Cloudant)    hosted

If the remote database doesn't exist, then PouchDB will create it for you.

You can verify that your database is working by visiting the URL http://localhost:5984/kittens. You should see something like this:

js
{"db_name":"kittens","doc_count":0,"doc_del_count":0,"update_seq":0,"purge_seq":0,"compact_running":false,"disk_size":79,"data_size":0,"instance_start_time":"1410722558431975","disk_format_version":6,"committed_update_seq":0}

If instead you see:

js
{"error":"not_found","reason":"no_db_file"}

Then check to make sure that your remote CouchDB has started up correctly. Common errors (such as CORS) are listed here.

{% include anchor.html title="Get basic info about the database" hash="get-basic-info-about-the–database" %}

You can see basic information about the database by using the info() method.

js
db.info().then(function (info) {
  console.log(info);
})

The local database should show something like:

js
{"doc_count":0,"update_seq":0,"db_name":"kittens"}

The remote database may have a bit more information:

js
{"db_name":"kittens","doc_count":0,"doc_del_count":0,"update_seq":0,"purge_seq":0,"compact_running":false,"disk_size":79,"data_size":0,"instance_start_time":"1410722558431975","disk_format_version":6,"committed_update_seq":0}

The most important bits of information are:

  • doc_count: the number of undeleted documents in the database
  • db_name: the name of the database

{% include anchor.html title="Debugging" hash="debugging" %}

IndexedDB/WebSQL inspectors

You can use the normal developer tools to see what your database looks like under the hood.

In Chrome, just choose Overflow icon ☰ → Tools → Developer Tools. Then click the Resources tab, then IndexedDB, and you should see the following:

{% include img.html src="dev_tools.png" alt="Chrome Developer Tools" %}

This is the raw IndexedDB representation of your PouchDB, so it is very fine-grained. However, you may find it useful.

In Safari, your database will be under Develop → Show Web Inspector → Resources → Databases.

{% include img.html src="safari_inspector.png" alt="Web Inspector in Safari" %}

{% include anchor.html title="Deleting your local database" hash="deleting-your-local-database" %}

During development, it's often useful to destroy the local database, so you can see what your users will experience when they visit your site for the first time. A page refresh is not enough, because the data will still be there!

In Chrome, you can use the Clear Cache extension, which will add a trashcan icon to your toolbar, which you can click to delete all local data (IndexedDB, WebSQL, LocalStorage, cookies, etc.).

In Firefox, you can use the Clear Browsing Data add-on, which adds a toolbar button to delete your IndexedDB with one click.

In Safari, you can simply click Safari → Clear History and Website Data.

{% include anchor.html title="Differences between the local and remote databases" hash="differences-between-the-local-and-remote-databases" %}

When you create a local PouchDB database, it uses whatever underlying datastore is available - IndexedDB in most browsers, WebSQL in older browsers, and LevelDB in Node.js.

When you create a remote PouchDB database, it communicates directly with the remote database – CouchDB, Cloudant, Couchbase, etc.

The goal of PouchDB is to allow you to seamlessly communicate with one or the other. You should not notice many differences between the two, except that of course the local one is much faster!

{% include anchor.html title="Related API documentation" hash="related-api-documentation" %}

{% include anchor.html title="Next" hash="next" %}

Now that you've created some databases, let's put some documents in 'em!


Guides Documents (`docs/guides/documents.md`)


index: 5 layout: guide.html title: Working with documents sidebar: guides_nav.html

{% include anchor.html title="What's a document?" hash="whats-a–document" %}

PouchDB is a NoSQL database, meaning that you store unstructured documents rather than explicitly specifying a schema with rows, tables, and all that jazz.

A document might look like this:

js
{
  "_id": "mittens",
  "name": "Mittens",
  "occupation": "kitten",
  "age": 3,
  "hobbies": [
    "playing with balls of yarn",
    "chasing laser pointers",
    "lookin' hella cute"
  ]
}

If you come from a SQL background, this handy conversion chart may help:

SQL concept PouchDB concept
table no equivalent
row document
column field
primary key primary key (_id)
index view

We'll discuss these concepts later on.

{% include anchor.html title="Storing a document" hash="storing-a–document" %}

To store a document, you simply put it:

js
const doc = {
  "_id": "mittens",
  "name": "Mittens",
  "occupation": "kitten",
  "age": 3,
  "hobbies": [
    "playing with balls of yarn",
    "chasing laser pointers",
    "lookin' hella cute"
  ]
};
db.put(doc);

Whenever you put() a document, it must have an _id field so that you can retrieve it later.

So now let's get() the document by using its _id:

js
db.get('mittens').then(function (doc) {
  console.log(doc);
});

You should see:

js
{
  "name": "Mittens",
  "occupation": "kitten",
  "age": 3,
  "hobbies": [
    "playing with balls of yarn",
    "chasing laser pointers",
    "lookin' hella cute"
  ],
  "_id": "mittens",
  "_rev": "1-bea5fa18e06522d12026f4aee6b15ee4"
}

You can see a live example of this code.

The document looks exactly the same as when we put it, except... aha! What is this? There is a new field, _rev, that contains what looks like garbage. PouchDB gots some 'splainin' to do.

{% include anchor.html title="Understanding revisions (_rev)" hash="understanding-revisions-rev" %}

The new field, _rev is the revision marker. It is a randomly-generated ID that changes whenever a document is created or updated.

Unlike most other databases, whenever you update a document in PouchDB or CouchDB, you must present the entire document along with its current revision marker.

For instance, to increment Mittens' age to 4, we would do:

js
doc.age = 4;
doc._rev = "1-bea5fa18e06522d12026f4aee6b15ee4";
db.put(doc);

If you fail to include the correct _rev, you will get the following sad error:

js
{
  "status": 409,
  "name": "conflict",
  "message": "Document update conflict"
}

HTTP 409 is a standard HTTP error message that indicates a conflict.

{% include anchor.html title="Updating documents correctly" hash="updating-documents–correctly" %}

So to update Mittens' age, we will first need to fetch Mittens from the database, to ensure that we have the correct _rev before we put them back. We don't need to manually assign the _rev value here (like we did above), as it is already in the doc we're fetching.

js
// fetch mittens
db.get('mittens').then(function (doc) {
  // update their age
  doc.age = 4;
  // put them back
  return db.put(doc);
}).then(function () {
  // fetch mittens again
  return db.get('mittens');
}).then(function (doc) {
  console.log(doc);
});

You can see a live example of this code.

{% include alert/start.html variant="info" %}

Don't worry if the structure of this code seems strange! It's using promises, which will be discussed in the next chapter.

{% include alert/end.html %}

Now you should see the following:

js
{
  "name": "Mittens",
  "occupation": "kitten",
  "age": 4,
  "hobbies": [
    "playing with balls of yarn",
    "chasing laser pointers",
    "lookin' hella cute"
  ],
  "_id": "mittens",
  "_rev": "2-3e3fd988b331193beeeea2d4221b57e7"
}

As you can see, we have successfully updated Mittens' age to 4 (they grow up so fast!), and their revision marker has also changed to "2-3e3fd988b331193beeeea2d4221b57e7". If we wanted to increment their age to 5, we would need to supply this new revision marker.

{% include anchor.html title="Related API documentation" hash="related-api-documentation" %}

{% include anchor.html title="Next" hash="next" %}

Now that you understand a bit about how to create and update documents, let's take a small detour to talk about asynchronous code.


Guides Local Documents (`docs/guides/local-documents.md`)


index: 16 layout: guide.html title: Local documents sidebar: guides_nav.html

"Local" documents are a special class of documents in PouchDB and CouchDB, which are used for storing local metadata about a database. You might never need them in your own app, but sometimes they can come in handy for advanced use cases.

{% include anchor.html title="Local docs in a nutshell" hash="local-docs-in-a-nutshell" %}

Local docs have the following characteristics:

  • They don't replicate.
  • They can't contain attachments.
  • They don't appear in allDocs(), changes(), or query().
  • However, you can modify them with put()/remove()/bulkDocs(), and you can fetch them with get().

So basically, local docs only exist for that database, and they don't mix with the "normal" documents.

To create a local doc, you simply use '_local/' as the prefix of the _id. This is supported in both CouchDB and PouchDB:

js
db.put({
  _id: '_local/foobar',
  someText: 'yo, this is my local doc!'
}).then(function () {
  return db.get('_local/foobar');
});

{% include anchor.html title="Advantages of local docs" hash="advantages-of-local–docs" %}

Local docs are useful for small bits of configuration or metadata, which you don't necessarily want to replicate, but which you want to keep in the database anyway. Many PouchDB plugins and core components use local docs. For instance, the replication algorithm uses them to store checkpoints, and map/reduce uses them to keep track of what's been emitted.

Local docs also have some good performance characteristics compared to regular docs. They don't have a version history, so only the most recent revision is ever stored in the database. This means that put()s and get()s are faster for local docs than for regular docs, and that local docs tend to take up less space on disk. In a sense, they are auto-compacted, although they take up even less space on disk than documents in a compacted database.

Regardless, you need to provide the current _rev when you update local docs, just like with regular docs.

{% include anchor.html title="Related API documentation" hash="related-api-documentation" %}


Guides Mango Queries (`docs/guides/mango-queries.md`)


index: 13 layout: guide.html title: Mango queries sidebar: guides_nav.html

Mango queries, also known as pouchdb-find or the find() API, are a structured query API that allows you to build secondary indexes beyond the built-in allDocs() and changes() indexes.

This API is useful for answering questions like:

  • find all documents where the type is 'user'
  • find all users whose age is greater than 21
  • find all Pokémon whose name starts with 'pika'
  • etc.

{% include anchor.html title="Installation" hash="installation" %}

The find() API is currently offered as a separate plugin, meaning that you must install it on top of pouchdb.js. Here's how to do so:

Script tags

html
<script src="pouchdb.js"></script>
<script src="pouchdb.find.js"></script>

The pouchdb.find.js file is available in the pouchdb package in npm/Bower, on unpkg, or [as a GitHub download](https://github.com/apache/pouchdb/releases/download/{{ site.version }}/pouchdb.find.js). Note it must be placed after pouchdb.js.

npm

If you are using Node, Browserify, Webpack, Rollup, etc., then you can install it like so:

bash
npm install --save pouchdb-find

Then in code:

js
const PouchDB = require('pouchdb');
PouchDB.plugin(require('pouchdb-find'));

{% include anchor.html title="Query language" hash="query-language" %}

The Mango query language is a DSL inspired by MongoDB, which allows you to define an index that is then used for querying. One quick way to understand how this works is to use the live query demo.

At a basic level, there are two steps to running a query: createIndex() (to define which fields to index) and find() (to query the index).

For instance, let's imagine a simple index to look up all documents whose name is "mario". First we'll create it:

js
db.createIndex({
  index: {fields: ['name']}
});

This returns a Promise that resolves once the index is created. At this point, we have an index based on the "name" field, so we can use it for lookup:

js
db.find({
  selector: {
    name: 'mario'
  }
});

This returns a Promise containing an array of all documents that match this selector. Note that this is equivalent to using the $eq (equals) operator:

js
db.find({
  selector: {
    name: {$eq: 'mario'}
  }
});

The important thing to understand is that, for a typical database, createIndex() is the expensive operation, because it is looping through all documents in the database and building a B-tree based on the name value.

Once the B-tree is built up, though, the find() is relatively cheap. (If this were not the case, then we would be better off just using allDocs() to iterate through the database ourselves!)

Once we have an index on name, we can also sort all documents by name:

js
db.find({
  selector: {
    name: {$gte: null}
  },
  sort: ['name']
});

Note that we are specifying that the name must be greater than or equal to null, which is a workaround for the fact that the Mango query language requires us to have a selector. In CouchDB collation order, null is the "lowest" value, and so this will return all documents regardless of their name value.

{% include anchor.html title="Pagination" hash="pagination" %}

Reading all documents in the database and sorting them by a particular value is neat, but we could do this ourselves with allDocs(), and it would have the same performance impact. Where it gets more interesting is when we use limit:

js
db.find({
  selector: {
    name: {$gte: null}
  },
  sort: ['name'],
  limit: 10
});

In this case, we only get 10 documents back, but they are the first 10 documents, sorted by name. This means that we have only read 10 documents out of the database into memory, which can be used for [efficient pagination]({{ site.baseurl }}/2014/04/14/pagination-strategies-with-pouchdb.html).

For instance, if we are displaying the first 10 results on a single page, and the user clicks "next" to see the next page, we can restructure our query based on the last result, to continue the pagination. Let's imagine the first 10 documents' names are:

js
[
  'abby', 'bertrand', 'clarice', 'don', 'emily',
  'fumiko', 'gunther', 'horatio', 'ike', 'joy'
]

For our next 10 pages of results, the query becomes:

js
db.find({
  selector: {
    name: {$gt: 'joy'}
  },
  sort: ['name'],
  limit: 10
});

Because we are now specifying that the name must be greater than 'joy', we are guaranteed to get the next-highest result after 'joy', which may (for instance) look like this:

js
[
  'kim', 'lin', 'maria', 'nell', 'oliver',
  'pat', 'quincy', 'roy', 'sam', 'tanya'
]

In this way, we can continue paginating by using the last value as our next starting point. At any given point in time, there are only 10 documents stored in memory at once, which is great for performance.

{% include anchor.html title="Indexing on more than one field" hash="more-than-one-field" %}

Sometimes an index is not as simple as "find all documents whose name is "mario". Sometimes you want to do something fancy, such as "find all documents whose name is "mario" and whose age is greater than 21". In those cases, you can index on more than one field:

js
db.createIndex({
  index: {
    fields: ['name', 'age']
  }
}).then(function () {
  return db.find({
    selector: {
      name: 'mario',
      age: {$gt: 21}
    }
  });
});

One thing to note is that the order of these fields matters when creating your index. For instance, the following would not work:

js
/* THIS WON'T WORK! */
db.createIndex({
  index: {
    fields: ['age', 'name']
  }
}).then(function () {
  return db.find({
    selector: {
      name: 'mario',
      age: {$gt: 21}
    }
  });
});

The reason for this is easy to understand if we imagine how this index would sort a hypothetical database:

name age
Luigi 17
Luigi 28
Mario 18
Mario 21
Mario 22
Mario 26
Peach 17
Peach 21
Peach 25

In the above table, the documents are sorted by ['name', 'age'], and our "Marios above the age of 21" are very clearly grouped together.

However, if we were to change the order, and sort them by ['age', 'name'], it would look instead like this:

age name
17 Luigi
17 Peach
18 Mario
21 Mario
21 Peach
22 Mario
25 Peach
26 Mario
28 Luigi

If we imagine our find() query as a "slice" of the data, it's obvious that there's no slice that corresponds to "all Marios whose age is greater than 21." Instead, our documents are sorted by age, and then documents with the same age are sorted by name.

This index may be good for answering questions like "find all 17-year-olds whose name starts with letters N-Z", but it's not very good for answering questions like "find all people with a certain name, older than a certain age."

This shows that it's important to carefully design an index before creating a query to use that index. Otherwise, the query planner may fall back to in-memory querying, which can be expensive.

{% include anchor.html title="Performance notes" hash="performance-notes" %}

The Mango query language is generally very permissive, and allows you to write queries that may not perform very well, but will run regardless. For instance, you may create an index with createIndex(), but then write a find() query that doesn't actually use that index. In general, the query planner tries to find the most appropriate index, but it may fall back to in-memory querying.

As a straightforward example, if you query using the _id field, then the query planner will automatically map that directly to an allDocs() query. However, if you query for a field that isn't yet indexed, then it will simply use allDocs() to read in all documents from the database (!) and then filter in-memory. This can lead to poor performance, especially if your database is large.

If you're ever wondering how the query planner is interpreting your query, you can use the explain endpoint:

js
db.explain({
  selector: {
    name: 'mario',
    age: {$gt: 21}
  }
})
.then(function (explained) {
  // detailed explained info can be viewed
});

In the console, the query planner will show a detailed explanation of how it has interpreted the query, whether it uses any indexes, and whether any parts of the query need to be executed in-memory.

You may also want to pay attention to the "warning" value included in your results set, indicating that there was no index that matched the given query. For instance, the warning may look like this:

js
{
  "docs": [ /* ... */ ],
  "warning": "No matching index found, create an index to optimize query time."
}

{% include anchor.html title="Set which index to use" hash="use_index" %}

When creating a query, by settings the use_index field, it is possible to tell pouchdb-find which index to use.
The below example shows how to do that.

js
db.createIndex({
  index: {
    fields: ['age', 'name'],
    ddoc: "my-index-design-doc"
  }
}).then(function () {
  return db.find({
    selector: {
      name: 'mario',
      age: {$gt: 21},
    },
    use_index: 'my-index-design-doc'
  });
});

{% include anchor.html title="Further reading" hash="further-reading" %}

The Mango query language is quite large and supports many options. Some of the more common ones include:

  • $eq: equals
  • $gt: greater than
  • $gte: greater than or equal to
  • $lt: less than
  • $lte: less than or equal to

There are many more options besides these, although note that not all of them can take advantage of indexes. For instance, $regex, $ne, and $not cannot use on-disk indexes, and must use in-memory filtering instead.

The most complete documentation for selector options can be found in the CouchDB _find documentation. You might also look at the Cloudant Query Language documentation (which is nearly identical to Mango, other than text and other Cloudant-specific features). PouchDB uses CouchDB as the reference implementation; they ought to be functionally identical.

It should be noted that, over HTTP, this API currently works with CouchDB 2.0+, Cloudant, and PouchDB Server.
CouchDB 2.0 is the reference implementation, so the API should be the same. CouchDB 1.6.1 and below is not supported.

{% include anchor.html title="Related API documentation" hash="related-api-documentation" %}

{% include anchor.html title="Next" hash="next" %}

Now that we've learned how to do structured Mango queries, let's try some more advanced queries, using map/reduce.


Guides Queries (`docs/guides/queries.md`)


index: 14 layout: guide.html title: Map/reduce queries sidebar: guides_nav.html

Map/reduce queries, also known as the query() API, are one of the most powerful features in PouchDB. However, they can be quite tricky to use, and so this guide is designed to dispell some of the mysteries around them.

The first thing to understand is that you don't need map/reduce queries if you merely want to look up documents by _id or sort them by _id. The allDocs() API already does this, using an efficient built-in index (see "bulk operations" for details).

The second thing to know is that map/reduce is also unnecessary if you want to sort documents by their update time – this is exactly what the changes feed does! Again, this is a built-in index that you get for free.

Finally, it's important to understand that Mango queries are much easier to use than map/reduce queries, and they can usually satisfy 99% of use cases. The point of map/reduce is to provide an extremely advanced API for building secondary indexes, suitable for those with specific querying needs.

So now that you've read the fine print, let's talk about how map/reduce queries actually work!

{% include anchor.html title="Mappin' and reducin'" hash="mappin-and-reducin" %}

The PouchDB query() API (which corresponds to the _view API in CouchDB) has two modes: temporary queries and persistent queries.

Temporary queries

Temporary queries are very slow, and we only recommend them for quick debugging during development. To use a temporary query, you simply pass in a map function:

js
db.query(function (doc, emit) {
  emit(doc.name);
}, {key: 'foo'}).then(function (result) {
  // found docs with name === 'foo'
}).catch(function (err) {
  // handle any errors
});

In the above example, the result object will contain stubs of documents where the name attribute is equal to 'foo'. To include the document in each row of results, use the include_docs option.

{% include alert/start.html variant="info" %}

The emit pattern is part of the standard CouchDB map/reduce API. What the function basically says is, "for each document, emit doc.name as a key."

{% include alert/end.html %}

Persistent queries

Persistent queries are much faster, and are the intended way to use the query() API in your production apps. To use persistent queries, there are two steps.

First, you create a design document, which describes the map function you would like to use:

js
// document that tells PouchDB/CouchDB
// to build up an index on doc.name
const ddoc = {
  _id: '_design/my_index',
  views: {
    by_name: {
      map: function (doc) { emit(doc.name); }.toString()
    }
  }
};
// save it
pouch.put(ddoc).then(function () {
  // success!
}).catch(function (err) {
  // some error (maybe a 409, because it already exists?)
});

{% include alert/start.html variant="info" %}

The .toString() at the end of the map function is necessary to prep the
object for becoming valid JSON.

{% include alert/end.html %}

{% include alert/start.html variant="info" %}

The emit function will be available in scope when the map function is
run, so don't pass it in as a parameter.

{% include alert/end.html %}

Then you actually query it, by using the name you gave the design document when you saved it:

js
db.query('my_index/by_name').then(function (res) {
  // got the query results
}).catch(function (err) {
  // some error
});

Note that, the first time you query, it will be quite slow because the index isn't
built until you query it. To get around this, you can do an empty query to kick
off a new build:

js
db.query('my_index/by_name', {
  limit: 0 // don't return any results
}).then(function (res) {
  // index was built!
}).catch(function (err) {
  // some error
});

After this, your queries will be much faster.

{% include alert/start.html variant="info"%}

CouchDB builds indexes in exactly the same way as PouchDB. So you may want to familiarize yourself with the "stale" option in order to get the best possible performance for your app.

{% include alert/end.html %}

{% include anchor.html title="More about map/reduce" hash="more-about-map-reduce" %}

That was a fairly whirlwind tour of the query() API, so let's get into more detail about how to write your map/reduce functions.

Indexes in SQL databases

Quick refresher on how indexes work: in relational databases like MySQL and PostgreSQL, you can usually query whatever field you want:

sql
SELECT * FROM pokemon WHERE name = 'Pikachu';

But if you don't want your performance to be terrible, you first add an index:

sql
ALTER TABLE pokemon ADD INDEX myIndex ON (name);

The job of the index is to ensure the field is stored in a B-tree within the database, so your queries run in O(log(n)) time instead of O(n) time.

Indexes in NoSQL databases

All of the above is also true in document stores like CouchDB and MongoDB, but conceptually it's a little different. By default, documents are assumed to be schemaless blobs with one primary key (called _id in both Mongo and Couch), and any other keys need to be specified separately. The concepts are largely the same; it's mostly just the vocabulary that's different.

In CouchDB, queries are called map/reduce functions. This is because, like most NoSQL databases, CouchDB is designed to scale well across multiple computers, and to perform efficient query operations in parallel. Basically, the idea is that you divide your query into a map function and a reduce function, each of which may be executed in parallel in a multi-node cluster.

Map functions

It may sound daunting at first, but in the simplest (and most common) case, you only need the map function. A basic map function might look like this:

js
function myMapFunction(doc) {
  emit(doc.name);
}

This is functionally equivalent to the SQL index given above. What it essentially says is: "for each document in the database, emit its name as a key."

And since it's just JavaScript, you're allowed to get as fancy as you want here:

js
function myMapFunction(doc) {
  if (doc.type === 'pokemon') {
    if (doc.name === 'Pikachu') {
      emit('Pika pi!');
    } else {
      emit(doc.name);
    }
  }
}

Then you can query it:

js
// find pokemon with name === 'Pika pi!'
pouch.query(myMapFunction, {
  key          : 'Pika pi!',
  include_docs : true
}).then(function (result) {
  // handle result
}).catch(function (err) {
  // handle errors
});

// find the first 5 pokemon whose name starts with 'P'
pouch.query(myMapFunction, {
  startkey     : 'P',
  endkey       : 'P\ufff0',
  limit        : 5,
  include_docs : true
}).then(function (result) {
  // handle result
}).catch(function (err) {
  // handle errors
});

{% include alert/start.html variant="info"%}

The pagination options for query() – i.e., startkey/endkey/key/keys/skip/limit/descending – are exactly the same as with allDocs(). For a guide to pagination, read the [Bulk operations guide]({{ site.baseurl }}/guides/bulk-operations.html) or [Pagination strategies with PouchDB]({{ site.baseurl }}/2014/04/14/pagination-strategies-with-pouchdb.html).

{% include alert/end.html %}

Reduce functions

As for reduce functions, there are a few handy built-ins that do aggregate operations ('_sum', '_count', and '_stats'), and you can typically steer clear of trying to write your own:

js
// emit the first letter of each pokemon's name
const myMapReduceFun = {
  map: function (doc) {
    emit(doc.name.charAt(0));
  },
  reduce: '_count'
};
// count the pokemon whose names start with 'P'
pouch.query(myMapReduceFun, {
  key: 'P', reduce: true, group: true
}).then(function (result) {
  // handle result
}).catch(function (err) {
  // handle errors
});

If you're adventurous, though, you should check out the CouchDB documentation or the [PouchDB documentation]({{ site.baseurl }}/api.html#query_database) for details on reduce functions.

{% include anchor.html title="Avoiding map/reduce" hash="avoiding-map-reduce" %}

The map/reduce API is complex, and it can be computationally expensive because it requires building up an entirely new index. Therefore, it's good to know some tricks for avoiding the map/reduce API when you don't need it:

  1. If you can use allDocs() or changes() instead of the query() API, do it!
  2. If your query is simple enough that you can use find(), use that instead.
  3. Read the 12 tips for better code with PouchDB, especially the tip to "use and abuse your doc _ids."
  4. If your data is highly relational, try the relational-pouch plugin, which follows this advice, and only uses _id and allDocs() under the hood.

{% include anchor.html title="Related API documentation" hash="related-api-documentation" %}

{% include anchor.html title="Next" hash="next" %}

Now that we've learned how to map reduce, map reuse, and map recycle, let's move on to destroy() and compact().


Guides Replication (`docs/guides/replication.md`)


index: 10 layout: guide.html title: Replication sidebar: guides_nav.html

PouchDB and CouchDB were designed for one main purpose: sync. Jason Smith has a great quote about this:

The way I like to think about CouchDB is this: CouchDB is bad at everything, except syncing. And it turns out that's the most important feature you could ever ask for, for many types of software."

When you first start using CouchDB, you may become frustrated because it doesn't work quite like other databases. Unlike most databases, CouchDB requires you to manage revisions (_rev), which can be tedious.

However, CouchDB was designed with sync in mind, and this is exactly what it excels at. Many of the rough edges of the API serve this larger purpose. For instance, managing your document revisions pays off in the future, when you eventually need to start dealing with conflicts.

{% include anchor.html title="CouchDB sync" hash="couchdb-sync" %}

CouchDB sync has a unique design. Rather than relying on a master/follower architecture, CouchDB
supports a multi-master architecture. You can think of this as a system where any node can be written to or read from, and where you don't have to care which one is the "master" and which one is the "follower." In CouchDB's egalitarian world, every citizen is as worthy as another.

{% include img.html src="offline_replication.gif" alt="Offline replication with CouchDB." %}
(Thanks to IBM for the image: http://www.ibm.com/developerworks/library/wa-couchdb/)

When you use PouchDB, CouchDB, and other members of the Couch family, you
don't have to worry which database is the "single source of truth." They all are. According to the CAP theorem, a database can only have at most 2 of 3 properties: Consistency, Availability, or Partition-Tolerance. Typical relational databases such as MySQL are CP, which means they are consistent and tolerant to node partitions, at the expense of availability. CouchDB is an AP database, meaning that it's Partition-Tolerant,
every node is Available at all times, but it's only eventually Consistent.

To illustrate, imagine a multi-node architecture with CouchDB servers spread across several continents. As long as you're willing to wait, the data will eventually flow
from Australia to Europe to North America to wherever. Users around the world running PouchDB in their browsers or Couchbase Lite/Cloudant Sync in their smartphones experience the
same privileges. The data won't show up instantaneously, but depending on the Internet connection speed, it's usually close enough to real-time.

In cases of conflict, CouchDB will choose an arbitrary winner that every node can agree upon deterministically. However, conflicts are still stored in the revision tree (similar to a Git history tree), which means that app developers can either surface the conflicts to the user, or just ignore them.

In this way, CouchDB replication "just works."

{% include anchor.html title="Setting up sync" hash="setting-up-sync" %}

As you already know, you can create either local PouchDBs:

js
const localDB = new PouchDB('mylocaldb')

or remote PouchDBs:

js
const remoteDB = new PouchDB('http://localhost:5984/myremotedb')

This pattern comes in handy when you want to share data between the two.

The simplest case is unidirectional replication, meaning you just want one database to mirror its changes to a second one. Writes to the second database, however, will not propagate back to the master database.

To perform unidirectional replication, you simply do:

js
localDB.replicate.to(remoteDB).on('complete', function () {
  // yay, we're done!
}).on('error', function (err) {
  // boo, something went wrong!
});

Congratulations, all changes from the localDB have been replicated to the remoteDB.

However, what if you want bidirectional replication? You could do:

js
localDB.replicate.to(remoteDB);
localDB.replicate.from(remoteDB);

However, to make things easier for your poor tired fingers, PouchDB has a shortcut API:

js
localDB.sync(remoteDB);

These two code blocks above are equivalent. And the sync API supports all the same events as the replicate API:

js
localDB.sync(remoteDB).on('complete', function () {
  // yay, we're in sync!
}).on('error', function (err) {
  // boo, we hit an error!
});

{% include anchor.html title="Live replication" hash="live–replication" %}

Live replication (or "continuous" replication) is a separate mode where changes are propagated between the two databases as the changes occur. In other words, normal replication happens once, whereas live replication happens in real time.

To enable live replication, you simply specify {live: true}:

js
localDB.sync(remoteDB, {
  live: true
}).on('change', function (change) {
  // yo, something changed!
}).on('error', function (err) {
  // yo, we got an error! (maybe the user went offline?)
});

However, there is one gotcha with live replication: what if the user goes offline? In those cases, an error will be thrown and replication will stop.

You can allow PouchDB to automatically handle this error, and retry until the connection is re-established, by using the retry option:

js
localDB.sync(remoteDB, {
  live: true,
  retry: true
}).on('change', function (change) {
  // yo, something changed!
}).on('paused', function (info) {
  // replication was paused, usually because of a lost connection
}).on('active', function () {
  // replication was resumed
}).on('error', function (err) {
  // totally unhandled error (shouldn't happen)
});

This is ideal for scenarios where the user may be flitting in and out of connectivity, such as on mobile devices.

{% include anchor.html title="Canceling replication" hash="canceling—replication" %}

Sometimes, you may want to manually cancel replication – for instance, because the user logged out. You can do so by calling cancel() and then waiting for the 'complete' event:

js
const syncHandler = localDB.sync(remoteDB, {
  live: true,
  retry: true
});

syncHandler.on('complete', function (info) {
  // replication was canceled!
});

syncHandler.cancel(); // <-- this cancels it

The replicate API also supports canceling:

js
const replicationHandler = localDB.replicate.to(remoteDB, {
  live: true,
  retry: true
});

replicationHandler.on('complete', function (info) {
  // replication was canceled!
});

replicationHandler.cancel(); // <-- this cancels it

{% include anchor.html title="Deleting replicated databases" hash="delete-during-replication" %}

One thing to note about replication is that it tracks the data within a database, not the database itself. If you destroy() a database that is being replicated to, the next time the replication starts it will transfer all of the data again, recreating the database to the state it was before it was destroyed. If you want the data within the database to be deleted you will need to delete via remove() or bulkDocs(). The pouchdb-erase plugin can help you remove the entire contents of a database.

{% include anchor.html title="Fancy replication" hash="fancy-replication" %}

Any PouchDB object can replicate to any other PouchDB object. So for instance, you can replicate two remote databases, or two local databases. You can also replicate from multiple databases into a single one, or from a single database into many others.

This can be very powerful, because it enables lots of fancy scenarios. For example:

  1. You have an [in-memory PouchDB]({{ site.baseurl }}/adapters.html#pouchdb_in_the_browser) that replicates with a local PouchDB, acting as a cache.
  2. You have many remote CouchDB databases that the user may access, and they are all replicated to the same local PouchDB.
  3. You have many local PouchDB databases, which are mirrored to a single remote CouchDB as a backup store.

The only limits are your imagination and your disk space.

{% include alert/start.html variant="warning" %}

When you replicate between two remote databases, the changes flow through PouchDB. If this is not what you want, then you should POST directly to the CouchDB _replicate endpoint, as described in the CouchDB replication guide.

{% include alert/end.html %}

{% include anchor.html title="Related API documentation" hash="related-api-documentation" %}

{% include anchor.html title="Next" hash="next" %}

Now that we have a grasp on replication, let's talk about an inconvenient fact of life: conflicts.


Guides Setup Couchdb (`docs/guides/setup-couchdb.md`)


index: 2 layout: guide.html title: Setting up CouchDB sidebar: guides_nav.html

{% include anchor.html title="CouchDB: PouchDB's older sibling" hash="couchdb-pouchdbs-older-sibling" %}

One of the main benefits of learning PouchDB is that it's exactly the same as CouchDB. In fact, PouchDB is a shameless plagiarist: all of the API methods are the same, with only slight modifications to make it more JavaScript-y.

For instance, in CouchDB you would fetch all documents using:

bash
/db/_all_docs?include_docs=true

In PouchDB this becomes:

js
db.allDocs({include_docs: true})

The APIs are the same, and the semantics are the same.

In the following examples, we will set up CouchDB and talk to it using a tool you're already familiar with: your browser.

{% include anchor.html title="Installing CouchDB" hash="installing-couchdb" %}

If you are on a Debian flavor of Linux (Ubuntu, Mint, etc.), you can install CouchDB as follows.

First, enable the CouchDB package repository on your machine:

bash
$ sudo apt update && sudo apt install -y curl apt-transport-https gnupg
$ curl https://couchdb.apache.org/repo/keys.asc | gpg --dearmor | sudo tee /usr/share/keyrings/couchdb-archive-keyring.gpg >/dev/null 2>&1
source /etc/os-release
$ echo "deb [signed-by=/usr/share/keyrings/couchdb-archive-keyring.gpg] https://apache.jfrog.io/artifactory/couchdb-deb/ ${VERSION_CODENAME} main" \
    | sudo tee /etc/apt/sources.list.d/couchdb.list >/dev/null

Next, update your package lists and install CouchDB:

bash
$ sudo apt-get update
$ sudo apt-get install -y couchdb

If you are on a Mac or Windows you should install the official binaries from the CouchDB web site.

A CouchDB alternative: PouchDB Server

If you have trouble installing CouchDB, you can also install PouchDB Server, which is a drop-in replacement for CouchDB that uses PouchDB under the hood:

bash
$ npm install pouchdb-server
$ pouchdb-server --port 5984

PouchDB Server is currently experimental, and we do not recommend it for production environments.

{% include anchor.html title="Verify your installation" hash="verify-your–installation" %}

Once CouchDB is installed, it should be running at localhost:5984. To verify, you can open up your terminal and type

bash
$ curl localhost:5984

You should see something like:

js
{"couchdb":"Welcome","version":"2.2.0",...}

Next, open up http://localhost:5984/_utils/ in your browser.

If you see a screen like the following, then you are ready to rock and roll with CouchDB:

{% include img.html src="fauxton.png" alt="Fauxton interface" %}

{% include anchor.html title="Set up CORS" hash="set-up-cors" %}

CORS is a web technology that allows web sites to use resources from another domain. You will want to enable this in your CouchDB before continuing, because otherwise PouchDB will not work unless it's served from exactly the same domain as CouchDB.

Enabling CORS is easy. Just install this handy script:

bash
$ npm install add-cors-to-couchdb

And run it:

bash
$ add-cors-to-couchdb

If you installed PouchDB Server, CORS is enabled by default, and this step is not necessary.

{% include anchor.html title="Next" hash="next" %}

Now that you have CouchDB installed, let's install PouchDB.


Guides Setup Pouchdb (`docs/guides/setup-pouchdb.md`)


index: 3 layout: guide.html title: Setting up PouchDB sidebar: guides_nav.html

Installing PouchDB is easy. There are a few different ways to do it:

{% include anchor.html title="Direct download" hash="direct-download" %}

Download the latest pouchdb-{{site.version}}.min.js from the big green button above. Then in your index.html:

html
<script src="pouchdb-{{site.version}}.min.js"></script>

{% include anchor.html title="Bower" hash="bower" %}

Run this on the command line:

bash
$ bower install pouchdb

Then in your index.html:

html
<script src="bower_components/pouchdb/dist/pouchdb.min.js"></script>

{% include anchor.html title="npm" hash="npm" %}

Run this on the command line:

bash
$ npm install pouchdb

Then in your index.html:

html
<script src="node_modules/pouchdb/dist/pouchdb.min.js"></script>

{% include anchor.html title="jsdelivr CDN" hash="jsdelivr-cdn" %}

Add this to your index.html:

html
<script src="//cdn.jsdelivr.net/npm/pouchdb@{{site.version}}/dist/pouchdb.min.js"></script>

{% include anchor.html title="Node.js" hash="nodejs" %}

Run this on the command line:

bash
$ npm install pouchdb

Then in your JavaScript:

js
const PouchDB = require('pouchdb');

{% include anchor.html title="With TypeScript" hash="typescript" %}

Run this on the command line:

bash
$ npm install pouchdb @types/pouchdb

In your tsconfig.json activate allowSyntheticDefaultImports:

json
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true
  }
}

Then in your TypeScript:

typescript
import PouchDB from 'pouchdb';

You can install a plugin (provided there is a type definition for it in npm), import it in the same way and then pass the imported name to PouchDB.plugin() method just as you would do in JavaScript.

{% include anchor.html title="Next" hash="next" %}

Now that you have PouchDB installed, let's start working with databases.


Guides Updating Deleting (`docs/guides/updating-deleting.md`)


index: 7 layout: guide.html nav: Updating/deleting documents title: Updating and deleting documents sidebar: guides_nav.html

As we saw in the past two chapters, working with PouchDB documents can be tricky, because you have to manage the revision identifier _rev.

Now that we understand promises, though, there are few techniques we can use to make our code more elegant and readable.

{% include anchor.html title="Creating a default document" hash="creating-a–default-document" %}

Often in our code, we'll want to get() a document, and if it doesn't exist, we want to create some default.

For instance, let's say we have a configuration object. We want to provide some reasonable defaults for our config:

js
{
  _id: 'config',
  background: 'blue',
  foreground: 'white',
  sparkly: 'false'
}

This is a pretty good default setting! So let's write the code to set it as our default.

Thankfully, promises make this rather easy:

js
db.get('config').catch(function (err) {
  if (err.name === 'not_found') {
    return {
      _id: 'config',
      background: 'blue',
      foreground: 'white',
      sparkly: 'false'
    };
  } else { // hm, some other error
  	throw err;
  }
}).then(function (configDoc) {
  // sweet, here is our configDoc
}).catch(function (err) {
  // handle any errors
});

This code is doing the following:

  • Try to get() a doc with _id equal to 'config'
  • If it doesn't find it, return the default doc
  • Otherwise, you'll just get back the existing document

You can see a live example of this code.

{% include anchor.html title="Why must we dance this dance?" hash="why-must-we-dance-this-dance" %}

A common question from new PouchDB/CouchDB users is: why do we have to deal with _rev at all? Why can't I just put() the document without providing a _rev?

The answer is: because _revs are what makes sync work so well. PouchDB asks for a little upfront effort with managing document revisions, so that later on, sync is a breeze.

In fact, you are probably already familiar with a system that forces you to go through a similar dance. This system is called Git.

PouchDB and CouchDB's document revision structure is very similar to Git's. In fact, each document's revision history is stored as a tree (exactly like Git), which allows you to handle conflicts when any two databases get out of sync.

text
rev 3-a  rev 3-b
      \___/
        |
      rev 2
        |
      rev 1

Conflicts will be discussed later in this guide. For now, you can think of revisions as being a single lineage:

text
rev 4
        |
      rev 3
        |
      rev 2
        |
      rev 1

{% include anchor.html title="Deleting documents" hash="deleting-documents" %}

When you remove() a document, it's not really deleted; it just gets a _deleted attribute added to it.

That is, the database saves a tombstone at the end of the revision tree.

text
{_id: 'foo', _rev: '4-z', _deleted: true}
            |
{_id: 'foo', _rev: '3-y'}
            |
{_id: 'foo', _rev: '2-x'}
            |
{_id: 'foo', _rev: '1-w'}

There are three ways of deleting a document, which are all equivalent:

  1. You can call db.remove(doc):
js
db.get('mydoc').then(function (doc) {
  return db.remove(doc);
});
  1. You can call db.remove(doc._id, doc._rev):
js
db.get('mydoc').then(function (doc) {
  return db.remove(doc._id, doc._rev);
});
  1. You can call db.put(doc) with _deleted set to true:
js
db.get('mydoc').then(function (doc) {
  doc._deleted = true;
  return db.put(doc);
});

Of course, you will want to add catch() to the end of all these, unless you like to live dangerously.

You can see a live example of this code.

{% include anchor.html title="Related API documentation" hash="related-api-documentation" %}

{% include anchor.html title="Next" hash="next" %}

Now that we understand how to update and delete documents, let's do it in bulk.


Posts 2014 04 01 Pouchdb 2.1.0 (`docs/posts/2014-04-01-pouchdb-2.1.0.md`)


layout: post.html

title: PouchDB 2.1.0

author: Dale Harvey


PouchDB 2.1.0 is now released! Please file issues or tell us what you think. We would also like to give a huge thanks to our new and existing contributors.

{% include alert/start.html variant="warning"%}
This release contains a database migration, which means that once you upgrade you will no longer be able to use previous versions of PouchDB on the same databases.
{% include alert/end.html %}

Major Changes:

  • Website redesign and a new blog (#1539, #893)
  • Support optional LevelDOWN builds (#44)
  • Replication performance improvements (#1732)
  • Performance improvements for skip / limit (#1649)
  • Cache WebSQL databases, fully passing tests in Safari (#1068)
  • Fix support for unicode in doc ids (WebSQL) (#1733)
  • Fix for localStorage detection in Chrome Apps (#1727)
  • Allow overriding of http functions (#1602)
  • Improve error reporting from replicator (#1714)
  • Switch to readAsArrayBuffer for IE binary support (#1667)
  • Add npm run shell for quick shell experimenting (#1610)
  • Ensure we don't lose data when browsers upgrade from WebSQL to IDB (#1289)

[Complete list of changes](https://github.com/daleharvey/pouchdb/compare/2.0.1...2.1.0)


Posts 2014 04 01 Welcome To The Pouchdb Blog (`docs/posts/2014-04-01-welcome-to-the-pouchdb-blog.md`)


layout: post.html

title: Welcome to the PouchDB blog

author: Dale Harvey


Hello World! Welcome to the newly formed PouchDB blog. PouchDB has been an active community for over 2 years now, with almost everything being run via IRC and Github. As the project grows, we wanted a way to communicate with users about the current state of the PouchDB world.

We will obviously be posting updates and releases notes for PouchDB releases, but as well as that, we will also be blogging about related projects and technologies within the PouchDB world, such as Service Workers, CouchDB, etc.

As with the PouchDB source, we would love to invite everyone to contribute. We have documented the instructions, so please get involved.

Over the last 4 years PouchDB has had 89 awesome contributors. I would like to give a shout-out to the people who worked together to get this site up and running in such a short time: nickcolley, misosoup, zaccolley, nolanlawson and virgie in particular.

Happy Pouching.


Posts 2014 04 14 Pagination Strategies With Pouchdb (`docs/posts/2014-04-14-pagination-strategies-with-pouchdb.md`)


layout: post.html

title: Pagination strategies with PouchDB

excerpt: PouchDB, like CouchDB, was designed to store large amounts of data. However, once you're dealing with hundreds, thousands, or even millions of documents in a single database…

author: Nolan Lawson


PouchDB, like CouchDB, was designed to store large amounts of data. However, once you're dealing with hundreds, thousands, or even millions of documents in a single database, proper pagination becomes crucial if you want to offer anything manageable to your users. Nobody likes scrolling through a mile-high web page to try to find what they want.

A basic example

Let's pretend we're storing 20 documents in PouchDB:

javascript
var pouch = new PouchDB('numbers');
var docs = [
  {_id : 'doc01', name : 'uno'},        {_id : 'doc02', name : 'dos'},
  {_id : 'doc03', name : 'tres'},       {_id : 'doc04', name : 'cuatro'},
  {_id : 'doc05', name : 'cinco'},      {_id : 'doc06', name : 'seis'},
  {_id : 'doc07', name : 'siete'},      {_id : 'doc08', name : 'ocho'}, 
  {_id : 'doc09', name : 'nueve'},      {_id : 'doc10', name : 'diez'},  
  {_id : 'doc11', name : 'once'},       {_id : 'doc12', name : 'doce'},
  {_id : 'doc13', name : 'trece'},      {_id : 'doc14', name : 'catorce'},
  {_id : 'doc15', name : 'quince'},     {_id : 'doc16', name : 'dieciseis'},
  {_id : 'doc17', name : 'diecisiete'}, {_id : 'doc18', name : 'dieciocho'},
  {_id : 'doc19', name : 'diecinueve'}, {_id : 'doc20', name : 'veinte'},
];
pouch.bulkDocs({docs : docs}, function (err, response) {
  // handle err or response
});

Now, the simplest kind of Pouch query, called [allDocs()]({{ site.baseurl }}/api.html#batch_fetch), doesn't do any pagination by default. As the name implies, it just returns all the docs:

javascript
pouch.allDocs(function (err, response) {
  // handle err or response
});

Here's the response:

javascript
{
  "total_rows": 20,
  "offset": 0,
  "rows": [
    {
      "id": "doc01",
      "key": "doc01",
      "value": { "rev": "1-8e483b9e178a8e58e3f1a4690b3d6e9a"}
    },
    {
      "id": "doc02",
      "key": "doc02",
      "value": {"rev": "1-f6fee051188e84ac3d22c645c819a5d2"}
    },
    /* etc. */
    {
      "id": "doc20",
      "key": "doc20",
      "value": {"rev": "1-f6fee051188e84ac3d22c645c819a5d2"}
    }
  ]
}

{% include alert/start.html variant="warning"%}

Potential gotcha! Somewhat unintuitively, allDocs() doesn't return the full document data by default. It only returns the document id and revision hash rev, unless you pass in the option {include_docs : true}.

{% include alert/end.html %}

All 20 documents are returned, no pagination involved. We can visualize it graphically like this:

text
First doc returned                                    Last doc returned
 ↓                                                                   ↓ 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

Now obviously, returning all 20 docs is nice, but we're here to paginate! So let's see what we can pass to the allDocs() method to slice and dice our data.

Learning the pagination lingo

allDocs() is a pretty flexible API, and we have a lot of parameters to choose from. Since PouchDB is modeled after CouchDB, we can learn about these parameters by directly consulting the Couch docs, but here's a basic rundown:

  • startkey: the _id of the first doc we'd like to fetch
  • endkey: the _id of the last doc we'd like to fetch
  • limit: maximum number of docs to return
  • skip: number of docs to skip
  • descending: true if we want reverse ordering

These parameters are simply passed as the first argument to allDocs(), e.g.

javascript
pouch.allDocs({startkey : 'doc05', endkey : 'doc06'}, function (err, response) {
  // handle err or response
});

Here's the response:

javascript
{
  "total_rows": 20,
  "offset": 0,
  "rows": [
    {
      "id": "doc05",
      "key": "doc05",
      "value": {"rev": "1-15397f81e784ee1a05d3ab6da07afe8d"}
    },
    {
      "id": "doc06",
      "key": "doc06",
      "value": {"rev": "1-5b35abcf7cc48e2cea63e64281f271ca"}
    }
  ]
}

Now, let's go through each of these parameters in detail.

startkey and endkey

startkey simply tells PouchDB where you'd like to start in the stream. For instance, the options {startkey : 'doc05'} would return these docs:

text
First doc returned                              Last doc returned
             ↓                                                       ↓ 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

Similarly, endkey tells PouchDB where to stop reading. {endkey : 'doc15'} would give us:

text
First doc returned                         Last doc returned
 ↓                                               ↓ 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

These two options can be used together! {startkey : 'doc05', endkey: 'doc15'} gives us:

text
First doc returned                   Last doc returned
             ↓                                   ↓ 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

Notice that both startkey and endkey are inclusive — i.e., the matching value itself is included in the results.

However, the value doesn't actually need to be present. So for example, if you have the documents ['A', 'B', 'X', 'Y'], calling {startkey : 'C', endkey : 'Z'} would return ['X', 'Y'].

skip and limit

skip tells PouchDB how many documents to skip from its normal starting point. E.g. {skip : 5} gives us:

text
First doc returned                              Last doc returned
                ↓                                                    ↓ 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

Whereas limit does the opposite: it cuts off documents from the end. {limit : 15} only gives us the first 15 docs:

text
First doc returned                         Last doc returned
 ↓                                               ↓ 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

When skip and limit are used together, limit applies after skip. So e.g. {skip : 5, limit : 10} would slice your documents like this:

text
First doc returned                Last doc returned
                ↓                                ↓ 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

Skipping into a trap

If you come from a SQL background, you might recognize skip and limit as our old friends from SQL: OFFSET and LIMIT. You might also imagine that these parameters are the only ones you need for proper pagination. But this is a trap!

Dumb method (do not use!)

javascript
var pageSize = 5;
var offset = 0;
function fetchNextPage() {
  pouch.allDocs({limit : pageSize, skip : offset}, function (err, response) {
    offset += pageSize;
    // handle err or response
  });
}

This method is intuitive, but once skip grows to a large number, your performance will start to degrade pretty drastically. That's because those documents must literally be skipped over with each query, and the database will still read into memory every document that it's skipping. This can be really bad for performance, especially on mobile devices with constrained memory.

Just repeat to yourself: skip really means "skip"!

Smart method (please use!)

javascript
var options = {limit : 5};
function fetchNextPage() {
  pouch.allDocs(options, function (err, response) {
    if (response && response.rows.length > 0) {
      options.startkey = response.rows[response.rows.length - 1].id;
      options.skip = 1;
    }
    // handle err or response
  });
}

This method takes a bit more time to grok, but what it's essentially doing is leveraging startkey, instead of relying on the performance-killer skip.

In our initial query, we simply tell PouchDB to give us the first 5 documents (or 10, or whatever your page size is). In our subsequent queries, we tell it to start with the last doc from the previous page, and to skip that one doc.

Of course, you could also fetch pageSize + 1 docs each time, and simply omit the last document when you display the results to the user. The choice is yours, but either method is preferable to using skip and limit alone.

{% include alert/start.html variant="info"%}

Aside: To be fair, WebSQL and CouchDB (since [version 1.1.1](https://issues.apache
.org/jira/browse/COUCHDB-977)) do not
suffer from this problem, due to their ability to efficiently count SQLite rows/B-tree offsets.
However, since IndexedDB and LevelDB (and other backends modeled on LevelDOWN(https://github
.com/rvagg/node-leveldown/)) are
traditional key-value stores, they don't have a good way to count offsets. Also, some experimental data suggests that CouchDB 1.5 is still faster with the startkey pattern. So you're better off just using startkey everywhere.

{% include alert/end.html %}

Paginating in backwards land

When descending is set to true, the normal document order is reversed. Simply passing {descending : true} would give us:

text
First doc returned                                    Last doc returned
 ↓                                                                   ↓ 
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Additionally, all the other parameters suddenly work backwards from their normal behavior! For instance, if we want to fetch the documents from 'doc01' to 'doc10', we'd normally do:

javascript
pouch.allDocs({startkey : 'doc01', endkey : 'doc10'}, function (err, response) {
  // handle err or response
});

This would give us 10 results, as expected:

text
First doc returned     Last doc returned
 ↓                           ↓ 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

If we set descending to true, though, we're in backwards land! So that same query will give us zero results:

text
Last doc???         First doc???
                                          ↓                          ↓ 
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Here's the actual JavaScript response object:

javascript
{
  "total_rows": 20,
  "offset": 0,
  "rows": []
}

Whenever descending is set to true, we need to switch the startkey and endkey to get the results we want:

javascript
pouch.allDocs({startkey : 'doc10', endkey : 'doc01', descending : true}, function (err, response) {
  // handle err or response
});

This gives us what we expect:

text
First doc returned     Last doc returned
                                          ↓                          ↓ 
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Skip and limit in backwards land

Similarly, for skip and limit, the operations are performed on the reversed list. Setting skip to 5 will skip the "first" 5 documents, so {skip : 5, descending : true} returns:

text
First doc returned                     Last doc returned
                      ↓                                              ↓ 
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

And setting limit to 15 will give us the "first" 15 documents, so {limit : 15, descending : true} gives us:

text
First doc returned                              Last doc returned
  ↓                                                   ↓ 
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Of course, you're free to go nuts and combine these parameters together. For example, the somewhat bloated options:

javascript
{
  skip : 1,
  limit : 5,
  startkey : 'doc10',
  descending : true
}

would give us:

text
First doc returned    Last doc returned
                                             ↓           ↓ 
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Keeping your strings in order

In the examples up to now, I've been careful to name my documents 'doc01', 'doc02', etc. Astute readers might wonder if that's because PouchDB uses standard JavaScript string ordering. And those smarty-pants readers would be correct! Since doc2 comes after doc10 in lexicographical ASCII order, I pad the single digits with zeroes.

This can be a very useful trick for document sorting. Normally, PouchDB will generate a random doc ID if you don't specify one (e.g. '36483B8A-DF4A-4AEB-B946-096BA0FA8813'), but if you provide your own IDs, you can make sorting incredibly easy for a variety of applications.

Example: sort by date

Let's imagine we want to sort our documents by date. For the doc IDs, we'll use timestamps in standard ISO-8601 format, given by new Date().toJSON(). This guarantees proper lexicographical ordering:

javascript
var partyLikeIts = new Date(915148800000); // 1999
var now = new Date(1394841840000);         // 2014

partyLikeIts.toJSON() // "1999-01-01T00:00:00.000Z"
now.toJSON()          // "2014-03-15T00:04:00.000Z"

// Party on!
partyLikeIts.toJSON() < now.toJSON(); // true

{% include alert/start.html variant="warning" %}

Note: If you're worried about ID collisions, you could also use new Date().toJSON() + Math.random().

{% include alert/end.html %}

Now we can fetch the 10 most recent docs:

javascript
{
  limit : 10,
  descending : true
}

Or the first 10 docs published after January 1st, 2000:

javascript
{
  limit : 10,
  startkey : '2000-01-01T00:00:00.000Z'
}

And if you need fancier sorting, or more than one index, try the Map/Reduce-powered [query() API][query-api], which boasts complex key collation.

total_rows is totally awesome

When we call allDocs(), a typical response object looks like this:

javascript
{
  "total_rows": 20,
  "offset": 0,
  "rows": [/* results go here */]
}

offset simply tells us how many documents were skipped, but total_rows tells us the total number of docs in our database. This can be very useful if you want to calculate how many documents are left to be read in, or if you just want to quickly fetch the database size.

For instance, calling allDocs() with {limit : 0} will simply return an empty list:

javascript
{
  "total_rows": 20,
  "offset": 0,
  "rows": []
}

If you're a SQL jockey, you can think of this as your COUNT(*).

Putting it all together

PouchDB and CouchDB handle pagination like it ain't no thing. There are more advanced topics, like how to page through Map/Reduce views with the query() API, but if you understand allDocs(), you're already 75% of the way there.

So congratulations, you're now a [Page Master]! Try to use your newfound powers for good.

[query-api]: {{ site.baseurl }}/api.html#query_database
[page master]: https://en.wikipedia.org/wiki/Pagemaster
[remove]: {{ site.baseurl }}/api.html#delete_document


Posts 2014 05 01 Pouchdb 2.2.0 (`docs/posts/2014-05-01-pouchdb-2.2.0.md`)


layout: post.html

title: PouchDB 2.2.0

author: Dale Harvey


PouchDB 2.2.0 is now released! Please file issues or tell us what you think. We would also like to give a huge thanks to our new and existing contributors.

{% include alert/start.html variant="warning"%}
We have removed the ES5 shims from PouchDB. If you require support for older browsers, it can be included from https://github.com/es-shims/es5-shim.
{% include alert/end.html %}

Major Changes:

  • Fix replication for large databases (#1954)
  • Persistent map/reduce (#1658)
  • Fix experimental .sync() API (#1696)
  • .replication() API switched to an EventEmitter (#1916)
  • .changes() API switched to an EventEmitter (#1768)
  • Experimental LevelDB-based LocalStorage adapter (#44)
  • Performance improvements (#1869, #1871, #1889, #1897, #1990)
  • Remove ES5 shims (#1753)
  • Performance tests (#113)

[Complete list of changes](https://github.com/daleharvey/pouchdb/compare/2.1.0...2.2.0)


Posts 2014 05 01 Secondary Indexes Have Landed In Pouchdb (`docs/posts/2014-05-01-secondary-indexes-have-landed-in-pouchdb.md`)


layout: post.html

title: Secondary indexes have landed in PouchDB

author: Nolan Lawson


With the release of PouchDB 2.2.0, we're happy to introduce a feature that's been cooking on the slow simmer for some time: secondary indexes, a.k.a. persistent map/reduce.

This is a powerful new tool for developers, since it allows you to index anything in your JSON documents – not just the doc IDs. Starting in 2.2.0, your data is sortable and searchable in ways that just weren't feasible before, thanks to a new cross-platform indexing engine we've built to work with every supported backend - IndexedDB, WebSQL, and LevelDB (and soon: LocalStorage!).

As usual with PouchDB, the new API is modeled after CouchDB's. So developers who are already familiar with the CouchDB map/reduce API will be up and running in no time.

And did I mention? It's fast. Our performance tests show that the new persistent map/reduce API could give you orders of magnitude improvements over the old on-the-fly query() method. In a database containing 1,000 documents, we found queries in PouchDB 2.2.0 to be between 10 and 100 times faster than in PouchDB 2.1.2.

Show me the code!

Here's the old way to query:

js
var pouch = new PouchDB('mydb');

// temporary view, each doc is processed in-memory (slow)
pouch.query(function (doc) {
  emit(doc.name);
}, {key: 'foo'}).then(function (result) {
  // found docs with name === 'foo'
});

{% include alert/start.html variant="info" %}

The emit pattern is part of the standard CouchDB map/reduce API. What the function basically says is, "for each document, emit doc.name as a key."

{% include alert/end.html %}

And here's the new way:

js
// document that tells PouchDB/CouchDB
// to build up an index on doc.name
var myIndex = {
  _id: '_design/my_index',
  views: {
    'my_index': {
      map: function (doc) { emit(doc.name); }.toString()
    }
  }
};
// save it
pouch.put(myIndex).then(function () {
  // kick off an initial build, return immediately
  return pouch.query('my_index', {stale: 'update_after'});
}).then(function () {
  // query the index (much faster now!)
  return pouch.query('my_index', {key: 'foo'});
}).then(function (result) {
  // found docs with name === 'foo'
});

Ew, you put map/reduce in my JavaScript?

First, let's define what map/reduce is, so you can understand why you might want it.

Indexes in SQL databases

Quick refresher on how indexes work: in relational databases like MySQL and PostgreSQL, you can usually query whatever field you want:

sql
SELECT * FROM pokemon WHERE name = 'Pikachu';

But if you don't want your performance to be terrible, you first add an index:

sql
ALTER TABLE pokemon ADD INDEX myIndex ON (name);

The job of the index is to ensure the field is stored in a B-tree within the database, so your queries run in O(log(n)) time instead of O(n) time.

Indexes in NoSQL databases

All of the above is also true in document stores like CouchDB and MongoDB, but conceptually it's a little different. By default, documents are assumed to be schemaless blobs with one primary key (called _id in both Mongo and Couch), and any other keys need to be specified separately. The concepts are largely the same; it's mostly just the vocabulary that's different.

In CouchDB, queries are called map/reduce functions. This is because, like most NoSQL databases, CouchDB is designed to scale well across multiple computers, and to perform efficient query operations in parallel. Basically, the idea is that you divide your query into a map function and a reduce function, each of which may be executed in parallel in a multi-node cluster.

Map functions

It may sound daunting at first, but in the simplest (and most common) case, you only need the map function. A basic map function might look like this:

js
function myMapFunction(doc) {
  emit(doc.name);
}

This is functionally equivalent to the SQL index given above. What it essentially says is: "for each document in the database, emit its name as a key."

And since it's just JavaScript, you're allowed to get as fancy as you want here:

js
function myMapFunction(doc) {
  if (doc.type === 'pokemon') {
    if (doc.name === 'Pikachu') {
      emit('Pika pi!');
    } else {
      emit(name);
    }
  }
}

Then you can query it:

js
// find pokemon with name === 'Pika pi!'
pouch.query(myMapFunction, {key: 'Pika pi!', include_docs: true}).then(function (result) {
  // handle result
});

// find the first 5 pokemon whose name starts with 'P'
pouch.query(myMapFunction, {
  startkey: 'P', endkey: 'P\uffff', limit: 5, include_docs: true
}).then(function (result) {
  // handle result
});

{% include alert/start.html variant="info"%}

The pagination options for query() – i.e., startkey/endkey/key/keys/skip/limit/descending – are exactly the same as with allDocs(). For a beginner's guide to pagination, read [Pagination strategies with PouchDB]({{ site.baseurl }}/2014/04/14/pagination-strategies-with-pouchdb.html).

{% include alert/end.html %}

Reduce functions

As for reduce functions, there are a few handy built-ins that do aggregate operations ('_sum', '_count', and '_stats'), and you can typically steer clear of trying to write your own:

js
// emit the first letter of each pokemon's name
var myMapReduceFun = {
  map: function (doc) {
    emit(doc.name.charAt(0));
  },
  reduce: '_count'
};
// count the pokemon whose names start with 'P'
pouch.query(myMapReduceFun, {
  key: 'P', reduce: true, group: true
}).then(function (result) {
  // handle result
});

If you're adventurous, though, you should check out the CouchDB documentation or the [PouchDB documentation]({{ site.baseurl }}/api.html#query_database) for details on reduce functions.

Map/reduce, reuse, recycle

PouchDB has actually had map/reduce queries since way before version 2.2.0, via the query() API. It had a big flaw, though: all queries were performed in-memory, reading in every document in the entire database just to perform a single operation.

This is fine when your data set is small, but it could be murder on large databases. On mobile devices especially, memory is a precious commodity, so you want to be as frugal as possible with the resources given to you by the OS.

The new persistent query() method is much more memory-efficient, and it won't read in the entire database unless you tell it to. It has two modes:

  • Temporary views (like the old system)
  • Persistent views (new system)

Both of these concepts exist in CouchDB, and they're faithfully emulated in PouchDB.

A room with a view

First off, some vocabulary: CouchDB calls indexes views, and these views are stored in a special document called a design document. Basically, a design document describes a view, and a view describes a map/reduce query, which tells the database that you plan to use that query later, so it better start indexing it now. In other words, creating a view is the same as saying CREATE INDEX in a SQL database.

Temporary views are exactly that – temporary. Instead of using a design document, you just call pouch.query(), so the view is created, written to disk, queried, and then poof, it's deleted. That's why, in older version of PouchDB, we could get away with doing it all in-memory.

Crucially, though, temporary views have to do a full scan of all documents every time you execute them, along with all the reading and writing to disk that that entails, only to throw it away at the end. That may be fine for quick testing during development, but in production it can be a big drag on performance.

Persistent views, on the other hand, are a much better solution if you want your queries to be fast. Persistent views need to be saved in a design document (hence "persistent"), so that the emitted fields are already indexed by the time you look them up. Subsequent lookups don't need to do any additional writes to disk, unless documents have been added or modified, in which case only the updated documents need to be processed.

{% include alert/start.html variant="info"%}

Why design docs? Design documents are special meta-documents that CouchDB uses for things like indexes, security, and schema validation (think: "designing" your database). They are stored, updated, and deleted exactly like normal documents, but their _ids are prefixed with the reserved string '_design/'. They also show up in allDocs() queries, but you can filter them out by using {startkey: '_design0'}.

{% include alert/end.html %}

Writing your first view

A design document with a view looks just like a regular document. The simplest one might look like this:

js
var designDoc = {
  _id: '_design/my_index',
  views: {
    'my_index': {
      map: function(doc) {
        emit(doc.name);
      }.toString()
    }
  }
};

All you need to do is put() this document into your database:

js
pouch.put(designDoc).then(function (info) {
 // design doc created
}).catch(function (err) {
  // if err.name === 'conflict', then
  // design doc already exists
});

… and then it will be available for querying using the name 'my_index':

js
pouch.query('my_index').then(function(result) {
 // do something with result
});

Whatever name you give, be sure to use the same one in both the _id (after '_design/') and in the views; otherwise you will need to use the awkward format 'my_design_doc_name/my_view_name' when you query.

{% include alert/start.html variant="info"%}

Technically, a design doc can contain multiple views, but there's really no advantage to this. Plus, it can even cause performance problems in CouchDB, since all the indexes are written to a single file. So we recommend that you create one view per design doc, and use the same name for both, in order to make things simpler.

{% include alert/end.html %}

Since there's a lot of boilerplate involved in creating views, you can use the following helper function to create a simple design doc based on a name and a map function:

js
function createDesignDoc(name, mapFunction) {
  var ddoc = {
    _id: '_design/' + name,
    views: {
    }
  };
  ddoc.views[name] = { map: mapFunction.toString() };
  return ddoc;
}

Then you just need to put() it:

js
var designDoc = createDesignDoc('my_index', function (doc) {
  emit(doc.name);
});
pouch.put(designDoc).then(function (doc) {
  // design doc created!
}).catch(function (err) {
  // if err.name === 'conflict', then
  // design doc already exists
});

From now on, you can call pouch.query('my_index') and it will summon this design document for querying.

Just like regular documents, design docs can always be deleted or changed later. The index will be updated automatically the next time you query() it.

If you do this a lot, though, then old indexes will build up on disk. You can call pouch.viewCleanup() to clean up any orphaned indexes.

{% include alert/start.html variant="warning"%}

Performance tip: Technically, the view will not be built up on disk until the first time you query() it. So a good trick is to always call query(viewName, {stale: 'update_after'}) after creating a view, to ensure that it starts building in the background. You can also use {stale: 'ok'} to avoid waiting for the most up-to-date results. In the future, we may add an API for auto-updating.

{% include alert/end.html %}

For those who get nostalgic

Of course, some of our loyal Pouchinistas may have been perfectly happy with the old way of doing things. The fact that the old query() function slurped the entire database into memory might have served them just fine, thank you.

To be sure, there are some legitimate use cases for this. If you don't store a lot of data, or if all your users are on desktop computers, or if you require closures (not supported by persistent views), then doing map/reduce in memory may have been fine for you. In fact, an upgrade to 2.2.0 might actually make your app slower, because now it has to read and write to disk.

Luckily we have a great solution for you: instead of using the query() API, you can use the much more straightforward allDocs() API. This will read all documents into memory, just like before, but best of all, you can apply whatever functions you want to the data, without having to bother with learning a new API or even what the heck "map/reduce" is.

But in general, we strongly recommend upgrading your apps to the new API instead.

When *not* to use map/reduce

Now that I've sold you on how awesome map/reduce is, let's talk about the situations where you might want to avoid it.

First off, you may have noticed that the CouchDB map/reduce API is pretty daunting. As a newbie Couch user who very recently struggled with the API, I can empathize: the vocabulary is new, the concepts are (probably) new, and there's no easy way to learn it, except to clack at your keyboard for awhile and try it out.

Second off, views can take awhile to build up, both in CouchDB and PouchDB. Each document has to be fetched from the database, passed through the map function, and indexed, which is a costly procedure for large databases. And if your database is constantly changing, you will also incur the penalty at query() time of running the map function over the changed documents.

Luckily, it turns out that the primary index, _id, is often good enough for a variety of querying and sorting operations. So if you're clever about how you name your document _ids, you can avoid using map/reduce altogether.

For instance, say your documents are emails that you want to sort by recency: boom, just set the _id to new Date().toJSON(). Or say they're web pages that you want to look up by URL: use the URL itself as the _id! Neither CouchDB nor PouchDB have a limit on how long your _ids can be, so you can get as fancy as you want with this.

Use and abuse your doc IDs

For a more elaborate example, let's imagine you're writing a music app. Your database might contain artists:

js
[ { _id: 'artist_bowie',
    type: 'artist',
    name: 'David Bowie',
    age: 67 },
  { _id: 'artist_dylan',
    type: 'artist',
    name: 'Bob Dylan',
    age: 72 },
  { _id: 'artist_joni',
    type: 'artist',
    name: 'Joni Mitchell',
    age: 70 } ]

… as well as albums:

js
[ { _id: 'album_bowie_1971_hunky_dory',
    artist: 'artist_bowie',
    title: 'Hunky Dory',
    type: 'album',
    year: 1971 },
  { _id: 'album_bowie_1972_ziggy_stardust',
    artist: 'artist_bowie',
    title: 'The Rise and Fall of Ziggy Stardust and the Spiders from Mars',
    type: 'album',
    year: 1972 },
  { _id: 'album_dylan_1964_times_they_are_changin',
    artist: 'artist_dylan',
    title: 'The Times They Are a-Changin\'',
    type: 'album',
    year: 1964 },
  { _id: 'album_dylan_1965_highway_61',
    artist: 'artist_dylan',
    title: 'Highway 61 Revisited',
    type: 'album',
    year: 1965 },
  { _id: 'album_dylan_1969_nashville_skyline',
    artist: 'artist_dylan',
    title: 'Nashville Skyline',
    type: 'album',
    year: 1969 },
  { _id: 'album_joni_1974_court_and_spark',
    artist: 'artist_joni',
    title: 'Court and Spark',
    type: 'album',
    year: 1974 } ]

See what I did there? Artist-type documents are prefixed with 'artist_', and album-type documents are prefixed with 'album_'. This naming scheme is clever enough that we can already do lots of complex queries using allDocs(), even though we're storing two different types of documents.

Want to find all artists? It's just:

js
allDocs({startkey: 'artist_', endkey: 'artist_\uffff'});

Want to list all the albums? Try:

js
allDocs({startkey: 'album_', endkey: 'album_\uffff'});

How about all albums by David Bowie? Wham bam, thank you ma'am:

js
allDocs({startkey: 'album_bowie_', endkey: 'album_bowie_\uffff'});

Let's go even fancier. Can we find all of Bob Dylan's albums released between 1964 and 1965, in reverse order? Gather 'round people, and try this:

js
allDocs({startkey: 'album_dylan_1965_', endkey: 'album_dylan_1964_\uffff', descending: true});

In this example, you're getting all those "indexes" for free, each time a document is added to the database. It doesn't take up any additional space on disk compared to the randomly-generated UUIDs, and you don't have to wait for a view to get built up, nor do you have to understand the map/reduce API at all.

Of course, this system starts to get shaky when you need to search by a variety of criteria: e.g. all albums sorted by year, artists sorted by age, etc. And you can only sort strings – not numbers, booleans, arrays, or arbitrary JSON objects, like the map/reduce API supports. But for a lot of simple applications, you can get by without using the query() API at all.

{% include alert/start.html variant="warning"%}

Performance tip: if you're just using the randomly-generated doc IDs, then you're not only missing out on an opportunity to get a free index – you're also incurring the overhead of building an index you're never going to use. So use and abuse your doc IDs!

{% include alert/end.html %}

Tips for writing views

When you do need the full map/reduce API, though, it pays to know the tips and tricks. Below is a list of advanced techniques and common pitfalls, which you may find useful when you're trying to squeeze that last bit of performance out of your views.

1. Don't index it if you don't need it

If your database has several document types, but only the "person" type has a lastName field, do this:

js
function (doc) {
  if (doc.lastName) {
    emit(doc.lastName);
  }
}

If you took out the if statement, you'd emit a null key for every non-person in your database, which needlessly bloats the size of your index.

{% include alert/start.html variant="info"%}

If the null field is meaningful for some reason, though, you can emit it and look it up later using query(viewName, {key: null}). Any undefined fields are treated as null.

{% include alert/end.html %}

2. Move logic to the query params

Don't do:

js
function (doc) {
  if (doc.highScore >= 1000) {
    emit(doc.highScore);
  }
}

Instead, do:

js
function (doc) {
  emit(doc.highScore);
}

And then query with {startkey: 1000}.

3. Use high characters for prefix search

To find every lastName that starts with an 'L', you can use the high Unicode character '\uffff':

js
pouch.query(viewName, {startkey: 'L', endkey: 'L\uffff'});

Or you can set inclusive_end to false:

js
pouch.query(viewName, {startkey: 'L', endkey: 'M', inclusive_end: false});

Both queries will do the same thing.

4. Use {} for complex key ranges

If your keys are [lastName, firstName], and you need to find everybody with the last name 'Harvey', you can do:

js
pouch.query(viewName, {startkey: ['Harvey'], endkey: ['Harvey', {}]});

In CouchDB collation ordering, {} is higher than everything except other objects.

For the database geeks: implementation details

If you want to know how map/reduce works in PouchDB, it's actually exceedingly easy: just open up your browser's developer tools, head over to "Resources," and see where we've created a bonus database to store the index.

One of the neat things about how we implemented map/reduce is that we managed to do it entirely within PouchDB itself. Yep, that's right: your map/reduce view is just a regular old PouchDB database, and map/reduce itself is a plugin with no special privileges, creating databases in exactly the same way a regular user would. Since the implementation sits on top of the PouchDB API, it's exactly the same for all three backends: LevelDB, IndexedDB, and WebSQL.

How did we pull off this trick? To be clear: it's not like we set out to make PouchDB some kind of map/reduce engine. In fact, as we were debating how to implement this feature, we originally ran on the assumption that we'd need to build additional functionality on top of PouchDB. It was only after a few months of discussions and experiments that we realized the whole thing could be done in PouchDB proper.

In the end, the only additional functionality we had to add to the main PouchDB code was a hook to tell PouchDB to destroy the map/reduce database when its parent database was destroyed. That's it.

This technique also has a nice parallel in CouchDB: it turns out that they, too, reused the core _view code in order to write _all_docs. In CouchDB, _all_Docs is just a special kind of _view, whereas in PouchDB, we built map/reduce views on top of allDocs(). We did it backwards, but the spirit of the idea was the same.

As for the index itself, what it basically boils down to is clever serialization of arbitrary JSON values into CouchDB collation ordering – i.e. nulls before booleans, booleans before numbers, numbers before strings, strings before arrays, arrays before objects, and so on recursively. Every JSON object maps to an indexable string that guarantees the same ordering in every database backend, and we only deviate from CouchDB by using ASCII string ordering instead of ICU ordering (since that's what our backends use).

Then, we take this value and concatenate it with whatever else needs to be indexed (usually just the doc _id), meaning the entire lookup key is a single string. We decided on this implementation because:

  1. LevelDB does not support complex indexes.
  2. IndexedDB does support complex indexes, but not in IE.
  3. WebSQL supports multi-field indexes, but it's also a dead spec, so we're not going to prioritize it above the others.

The next part of the design was to divide the data we needed to store into two kinds of documents: 1) emitted key/values with doc IDs, which are used for querying and pagination, and 2) mappings from doc IDs to those key/values, so that we can delete or update them when their parent documents are deleted or updated. Since by design, PouchDB databases do not have multi-document transaction semantics, we implemented a task queue on top of the database to serialize writes.

Aside from that, the only other neat trick was a liberal use of _local documents, which are a special class of documents that aren't counted in the total_rows count, don't show up in allDocs, but can still be accessed using the normal put() and get() methods. This is all it takes to fully reimplement CouchDB map/reduce in a tiny JavaScript database!

Anyone interested in more implementation details can read the months-long discussions in these Github issues. The map/reduce code itself is also pretty succinct.


Posts 2014 06 01 Pouchdb 2.2.3 (`docs/posts/2014-06-01-pouchdb-2.2.3.md`)


layout: post.html

title: PouchDB 2.2.3

author: Dale Harvey


PouchDB 2.2.3 is now released! Please file issues or tell us what you think. We would also like to give a huge thanks to our new and existing contributors.

Major Changes:

  • Experimental browser adapter plugins (#2190)
  • Fix Ember.js integration (#2158)
  • Fix Browserify integration (#2249, pouchdb/mapreduce#170)
  • Fixes for conflict resolution (#2072)
  • Better error handling in replication (#2122)
  • Fix Internet Explorer 10-11 (#1661, #2198, #2132)
  • Fix attachments in Android 4.x (#1992)
  • Fix LevelDB memory leak, race condition, error messages (#2250, #2251, #2144)
  • Perf improvements for replication in WebSQL & IndexedDB (#2180, #2178)
  • Bugfixes for IndexedDB inclusive_end (#2129)
  • Fix changes() memory leak (#2311)

[Complete list of changes](https://github.com/daleharvey/pouchdb/compare/2.2.0...2.2.3)


Posts 2014 06 17 12 Pro Tips For Better Code With Pouchdb (`docs/posts/2014-06-17-12-pro-tips-for-better-code-with-pouchdb.md`)


layout: post.html

title: 12 pro tips for better code with PouchDB

author: Nolan Lawson


This is not a blog post per se, but more of a list of tips and tricks to get the most out of your PouchDB experience.

There are a lot of PouchDB anti-patterns floating around out there on the Internet, and also some common pitfalls that can trip you up when you're new to the CouchDB API. I see this stuff all the time on Stack Overflow, Twitter, and IRC, and it never ceases to amaze and horrify me. We need to do better.

So consider this your 12-step program to becoming a better PouchDB coder. Let's put these bad habits to bed.

1. Use put(), not post()

Never use post(). You don't need it, so just stop.

Recall that put() requires you to supply your own doc ID, whereas post() generates a random one for you. However, this leads to inefficiencies, since you pay the cost of indexing a long ID like 'CB89021E-8439-392F-899C-054963FD0B9B' without ever using it.

Plus, you'll have to use put() anyway whenever you update or delete a document, so if you post(), you've got two APIs to learn instead of one. So remember: always put(), never post().

2. Don't emit(doc.foo, doc)

Don't do:

js
function (doc) {
  emit(doc.foo, doc);
}

Do:

js
function (doc) {
  emit(doc.foo);
}

This anti-pattern is everywhere. It seems to date back to pre-PouchDB days, since it shows up in a lot of blog posts and articles about CouchDB.

Folks, you never need to emit() the full document in your map/reduce functions. You can always just use {include_docs: true} when you query, and the freshest version of that document will be delivered right to your door. Free of charge.

Plus, if you emit the full doc, then it will actually serialize and write out that entire document to disk. This is true in both PouchDB and CouchDB, and it's pure waste.

So if you don't want big, inefficient map/reduce functions, then remember: only emit() what you need.

3. Don't emit(doc.foo, 1) and then _sum

Similar to the above, don't do:

js
{
  map: 
    function (doc) {
      emit(doc.foo, 1);
    }.toString(),
  reduce: '_sum'
}

Do:

js
{
  map: 
    function (doc) {
      emit(doc.foo);
    }.toString(),
  reduce: '_count'
}

You don't need to use _sum when a simple _count will do.

4. Attachments are overrated

{% include alert/start.html variant="warning" %}

Update: since this post was written, the stability and performance of attachments in PouchDB has greatly improved. Replicating large attachments is still not recommended, but attachments can be handy if used correctly. blob-util can help.

{% include alert/end.html %}

NPM has moved away from storing attachments in CouchDB. Nowadays they use a CDN for the binaries, and CouchDB just stores the metadata. In PouchDB, attachments have been one of the biggest sources of bugs, since every browser seems to handle them differently. Plus, the attachment API is hard to understand, you need a Blob shim for older browsers, and let's not even talk about ArrayBuffers, ArrayBufferViews, Uint8Arrays, and browsers that don't even support any of the above.

In general, both CouchDB and PouchDB are just poor fits for storing binary data. (Databases rarely are.) Instead of attachments, try using a CDN or a simple fileserver, and store the URLs or checksums in the database if you need to.

"One of the big things that everybody who's spent a lot of time with databases knows is that you should never put your binaries in the database. It's a terrible idea. It always goes wrong. I have never met a database in 15 years of which it is not true, and it's definitely not true of CouchDB.

You are taking this thing which is meant to sort and organize data, and you're giving it binary data, which it can neither sort nor organize. It can't do anything with that data, other than get really fat."

Laurie Voss, on [JavaScript Jabber](http://javascriptjabber.com/099-jsj-npm-inc-with-isaac-schlueter-laurie-voss-and-rod-boothby/)

5. Use plugins

[PouchDB has plugins]({{ site.baseurl }}/external.html). Use them, and if inspiration ever strikes, write your own!

6. Don't just update docs for the hell of it

Every time you modify a document, another revision is added to its revision history – think Git. Except unlike Git, these revisions contain the full document data (not just the diffs), which can take up a lot of space on disk. So if nothing changed in a document, don't bother put()ing it again.

7. Use and abuse your doc IDs

I already wrote [a blog post about this]({{ site.baseurl }}/2014/05/01/secondary-indexes-have-landed-in-pouchdb.html), but basically, if you don't want bad performance from your secondary indexes, the best strategy is to avoid secondary indexes altogether. The primary index should be sufficient for sorting and searching in nearly all of your applications, or at least for the hot-path code.

Also, if you really want to get fancy with your doc IDs, you can use PouchDB Collate to serialize arbitrary data into strings that are sorted according to CouchDB collation ordering. This allows you to index on arrays, objects, numbers – whatever you want:

js
var pouchCollate = require('pouchdb-collate');
var myDoc = {
  firstName: 'Scrooge',
  lastName: 'McDuck',
  age: 67,
  male: true
};
// sort by age, then gender, then last name, then first name
myDoc._id = pouchCollate.toIndexableString(
  [myDoc.age, myDoc.male, mydoc.lastName, mydoc.firstName]);

In the above example, the doc ID will be a strange string, which will sort correctly in both CouchDB and PouchDB:

js
'5323256.70000000000000017764\u000021\u00004McDuck\u00004Scrooge\u0000\u0000'

This is actually what persistent map/reduce uses under the hood!

And for cases where you only need to build complex IDs out of strings, there is also the fantastic DocURI project, which can build a more human-readable ID like this:

js
'movie/blade-runner/gallery-image/12/medium'

Choose whichever one fits your app better, or just concatenate the strings yourself.

8. Use Web SQL for better performance

{% include alert/start.html variant="warning" %}

Update: since this post was written, IndexedDB performance has improved, and is often better than WebSQL in Chrome. Your mileage may vary, so try them both out on your target platform(s).

{% include alert/end.html %}

Our performance tests have shown it again and again: Web SQL is faster than IndexedDB. It's hard to tell if that's due to our implementation or the browser vendors', but in any case, if you feel the need for speed, then you'll want to prefer Web SQL to IndexedDB.

So now that Android 4.4, iOS 8, and Safari 8 support IndexedDB in addition to Web SQL, it's more important than ever to consider using Web SQL instead of IndexedDB – at least, in apps where performance matters. To do so, the code is simply:

js
var pouch = new PouchDB('mydb', {adapter: 'websql'});
if (!pouch.adapter) { // websql not supported by this browser
  pouch = new PouchDB('mydb');
}

{% include alert/start.html variant="info" %}

If Web SQL is so fast, why does PouchDB fall back to Web SQL from IndexedDB instead of the other way around? Because we're trying to move the web forward, not rely on deprecated technology. If browser vendors can rest on their laurels with Web SQL, then they won't work to make IndexedDB faster.

{% include alert/end.html %}

9. Move logic from the map function to query()

If you only remember one thing from [my blog post about secondary indexes]({{ site.baseurl }}/2014/05/01/secondary-indexes-have-landed-in-pouchdb.html), remember this: every map function you write has to be executed for every single document in your database. No exceptions.

On the other hand, the query() options like startkey, endkey, key, and keys have been optimized to hell, and they leverage the native indexes in the database to deliver the maximum possible performance.

So if you find yourself writing something like this, you're doing it wrong:

js
function getPostsSince(when) {
  return db.query({
    map: function(doc, emit) {
      if (doc.timestamp > when) {
        emit(doc.name, 1);
      }
    },
  // ...
}
function getPostsBefore(when) {
  return db.query({
    map: function(doc, emit) {
      if (doc.timestamp < when) {
        emit(doc.name, 1);
      }
    }, 
  // ...
}
function getPostsBetween(startTime, endTime) {
  return db.query({
    map: function(doc, emit) {
      if (doc.timestamp > startTime && doc.timestamp < endTime) {
        emit(doc.name, 1);
      }
    }, 
  // ...
}

Each of those db.query() calls represents a separate temporary index. I.e., all your docs are read in, run through the map function, spit out to an index, queried, and then the whole thing is thrown away. For every query! This code is based on a real question I got from a developer on Twitter (sorry to pick on ya), and he described it as "crazy slow." No kidding.

Since all of these map/reduce functions key off of doc.timestamp, a better approach (which I recommended to him) would be:

js
var ddoc = createDesignDoc('by_timestamp', function (doc) {
  emit(doc.timestamp, doc.name);
});
db.put(ddoc).then(function() {/* etc. */});

function getPostsSince(when) {
  return db.query('by_timestamp', {endkey: when, descending: true});
}
function getPostsBefore(when) {
  return db.query('by_timestamp', {startkey: when});
}
function getPostsBetween(startTime, endTime) {
  return db.query('by_timestamp', {startkey: startTime, endkey: endTime});
}

(That createDesignDoc() helper function comes from [this blost post]({{ site.baseurl }}/2014/05/01/secondary-indexes-have-landed-in-pouchdb.html).)

Not only is the above code much simpler, but it's also faster and more tweakable. No need to completely rebuild the index when your query changes; just switch around startkey/endkey/descending and friends at query time to get the data you want.

10. You probably don't need reduce

In the previous example, the developer was also doing some fancy operations on the data using '_count' and '_sum'. Now, these are fine shortcuts that can save you a lot of code, but unless you're running your queries against the server, they don't buy you any performance benefits.

PouchDB runs in Node or the browser, meaning it's a single-threaded, single-process environment. There's no massive parallelization of the map/reduce functions like you could get with CouchDB (or at least, BigCouch/Cloudant/Couchbase). So PouchDB takes a shortcut and just runs every reduce function in memory (there's no point in writing it to disk), meaning it's not doing anything you couldn't just do yourself.

So if you find yourself writing three different design documents that all emit the same thing but have different reduce functions: don't bother. You can get better performance and smaller code by just writing a single design document with no reduce, and then doing the reduce yourself.

11. Debug with the CouchDB UI

One common question is: How do I debug my database? Well, that's easy: just replicate to CouchDB! Then you can browse your data in CouchDB's handy Futon interface.

In your code or in the browser console, just run:

js
PouchDB.replicate('mydb', 'http://localhost:5984/mydb', {live: true});

Then open up http://localhost:5984/_utils. Or if you want to try the new Fauxton API in CouchDB 1.5: http://localhost:5984/_utils/fauxton/. You'll get a nice interface that looks like this:

{% include img.html src="fauxton.png" alt="Fauxton UI in PouchDB Server" %}

And if you don't feel like installing CouchDB, you can install PouchDB Server instead:

text
$ npm install pouchdb-server
$ pouchdb-server -p 5984

Then open up http://localhost:5984/_utils.

12. Contribute!

To paraphrase Martin Fowler's famous talk, part of the motivation behind the NoSQL movement was the collective realization by a generation of developers that, you know, this whole "database" thing? It isn't really that hard. Keys map to values, stuff's written to disk: it's a cinch once you learn the basics. And PouchDB occupies an even humbler niche in the NoSQL community, since our NoSQL database is actually built on other databases.

So if you find a bug, or if there's a missing feature you'd like to request, then check out the code and open a pull request! We've got a very handsomely documented contributor's guide, and we're always happy to answer questions on IRC, Twitter, and the mailing list.

And even if you've committed the above errors (I know I have), we won't judge you too harshly. If you've read this far, then you're already on the path to recovery.

More tips

For more CouchDB tips in easily-digestible list format, check out Joan Touzet's awesome presentation: 10 Common Misconceptions about CouchDB. And to see what you can get away with if you bend these rules a bit, watch the talk that Nathan Vander Wilt gave right after.


Posts 2014 07 25 Pouchdb Levels Up (`docs/posts/2014-07-25-pouchdb-levels-up.md`)


layout: post.html

title: PouchDB levels up

author: Nolan Lawson


You might not have noticed it, but PouchDB underwent a quiet revolution over the past few months. The catalyzing moment was the LevelUP proposal posted by Nick Thompson back in January 2014, which slowly led to a rewrite of the PouchDB adapter for LevelDB to support arbitrary LevelUP backends. If that looks like word soup, let me explain why this is such a big deal.

LevelDB

In 2011, Google invented LevelDB as their answer to the IndexedDB spec designed by Nikunj Mehta. Like IndexedDB, it's quite a low-level abstraction that provides only the bare bones of what you'd need to qualify as a database – key-value storage, iteration, batch operations, and little else. Indeed, this was one of the big selling points of IndexedDB: it was low-level and unimposing enough that all the browser vendors could agree on it.

As it turned out, though, Google's implementation was really good. Due to its unique design inspired by BigTable, it scales well for anything from small application stores to huge multi-cluster databases. And thanks to its limited scope, it's been adopted as a core building block for more feature-rich databases, most notably Basho's Riak, Facebook's RocksDB, and Hyperdex.

LevelDB comes to Node

In Node.js, LevelDB took an especially interesting turn when Rod Vagg started the LevelUP project. LevelUP aimed not only to provide a fluent, Node-friendly API for LevelDB, but also to create a new lingua franca for persistent datastores. With LevelUP, the team managed to separate the API from the underlying storage engine (cheekily named LevelDOWN), therefore allowing any database that exposes a LevelUP-compliant API to be swapped in under the hood.

Since the LevelUP project began, the number of compatible datastores has exploded, and today you're spoiled for choice if you write your app to the LevelUP API. As a developer, you can start with LevelDOWN, but if you later decide LevelDB itself isn't to your tastes, you can swap it out for any one of:

What this means in practice is that you could, for instance, use:

  • MemDOWN for your unit tests,
  • Level.js when your app runs in a browser,
  • LevelDOWN on your dev server,
  • and Riak in your production server.

In a sense, LevelUP brings to the NoSQL world the same benefits of interoperability that the SQL world enjoyed for so long. SQL databases share the SQL language, and the LevelUP-compliant databases share the LevelUP API.

Enter PouchDB

This brings us back to Nick Thompson's LevelUP proposal. At the time he wrote it, PouchDB had three supported backends – LevelDB, Web SQL, and IndexedDB. All three were written as separate adapters tailored to PouchDB's API, but he pointed out that if we integrated with LevelUP, we could piggyback on LevelUP's success and potentially get lots of new backends for free.

For instance, there was a longstanding issue to add LocalStorage support for older browsers like IE 8 and 9. With LevelUP, we could avoid writing an entirely new, fourth adapter and simply plug into localstorage-down. And it certainly wouldn't hurt to have Riak, Redis, or an in-memory database as potential backends.

There was some hemming and hawing (mostly from me), but in the end we decided to cruise ahead with the proposal. (And ultimately, I was happily proven wrong.) Calvin Metcalf deserves the primary credit for integrating Sublevel into the LevelDB adapter, while Adam Shih jumped in to get the new adapter browserified with three different backends: localstorage-down, level-js, and MemDOWN. I put the finishing touches to build them as separate plugins, and as of 2.2.3 they're fully passing the test suite and [ready to use]({{ site.baseurl }}/adapters.html#pouchdb_in_the_browser).

Additionally, PouchDB Server has been updated to allow the use of alternate backends via the --level-backend option. This provides an instant CouchDB REST API to any LevelUP-compliant datastore – Redis, Riak, MySQL, you name it. There's also a new --in-memory option, courtesy of MemDOWN, of course.

Community dynamics

Besides getting some new backends, another cool change was that PouchDB started to take a more active role in the LevelUP community. Calvin Metcalf wrote SQLdown, Adam Shih and I became maintainers of localstorage-down, and all three of us submitted patches to level-js, MemDOWN, abstract-leveldown, and even LevelUP itself. The PouchDB test suite has been offered as an additional means of testing for *DOWN authors, and it's already been used to fix bugs in RiakDOWN and RedisDOWN.

So in a sense, we didn't really get these new adapters "for free." PouchDB is complex enough, and our unit tests are thorough enough, that we ended up pushing the LevelUP folks' code to the limit, discovering many new bugs in the process.

But in a more positive sense, we've actually achieved Nick Thompson's goal of more deeply integrating with the LevelUP community, thereby reducing redundancy and putting more effort into a smaller codebase. And in the future, positive changes in PouchDB will spill over into the LevelUP universe, and vice-versa.

The only original aim that we haven't achieved is to consolidate our three adapters (LevelDB, Web SQL, and IndexedDB) into one. We have a few reasons for this: our custom IndexedDB adapter is currently faster than Level.js, SQLdown is still a work in progress, and we're wary of the inevitable schema migration.

However, the fact that we can have dozens of PouchDB backends, while only maintaining three parallel adapters, is still a huge boon and was well worth the effort.

Conclusion

PouchDB has a bright future as part of the LevelUP ecosystem. What would have sounded like a dream half a year ago is now a verifiable reality, with unit tests to boot. Today you can write an app that:

  • Syncs from CouchDB to an in-memory PouchDB Server,
  • Then syncs to LocalStorage on IE 8,
  • Then syncs to Cloudant,
  • Then syncs to a PhoneGap app using the SQLite Plugin,
  • Then syncs to any one of Redis, Riak, SQLite, LevelDB, MySQL, PostgreSQL, and potentially many more.

Or whatever other combination you can cook up!

The marriage of PouchDB and LevelUP has essentially resulted in a marriage of the CouchDB sync protocol to the LevelUP API. Any database that exposes a LevelUP interface is now a full-fledged CouchDB replication target, which is a huge win for application developers.

Which database do you want to sync to? Well, which database do you feel like today?


Posts 2014 08 12 Pouchdb 3.0.0 (`docs/posts/2014-08-12-pouchdb-3.0.0.md`)


layout: post.html

title: PouchDB 3.0.0

author: Calvin Metcalf


PouchDB 3.0.0 is finally released! It's faster, it's more stable, it's easier to debug, it doesn't freeze the DOM and it spontaneously generates kittens.

Please file issues or tell us what you think. We would also like to give a huge thanks to our new and existing contributors.

{% include alert/start.html variant="warning"%}
This release includes a migration. Your existing database will be updated automatically, but you cannot downgrade to a previous version of PouchDB once you've upgraded.
{% include alert/end.html %}

Breaking Changes:

  • The browser build pouchdb-nightly.js has been renamed to pouchdb.js. It was never really a nightly anyway. (#2146)
  • opts.server has been deprecated from the replicate() and sync() APIs. You can still replicate from one HTTP server to another, but the data will flow through PouchDB rather than being server-initiated. (#2313)
  • You can no longer rely on errors to have an identifying name. Instead, rely on CouchDB-centric errors to have a status (i.e. err.status instead of err.name). There are some that still have an identifying name, but these could be removed at any time. The upside of this is that stack traces should be more consistently helpful. (#2545)

Major Changes:

  • Constructor option opts.size to work around Safari/iOS storage quotas (#2347)
  • PouchDB.defaults() for default constructor options, which permits many new features in pouchdb-server. (#2054)
  • _local documents are now unversioned in the underlying backend for better map/reduce performance and to better align the update_seq with CouchDB (#2023). Also fixes an issue with replication and changes(). (#2342)
  • Performance improvements. (#2391, #2392, #2393)
  • MD5 hashes are now calculated incrementally in a way that won't freeze the DOM. (#445, #2497)
  • More efficient replication. (#2466, #2475)

Bugfixes:

  • Fix for replication of multiple attachments. (#2358)
  • Fix for bulkDocs() in Cloudant. (#2365)
  • Fix read-only replication for CORS/40*. (#2484)
  • Fix for reserved words in docs. (#2477)
  • Fixes for change events in .sync(). (#2555)
  • Fixes for race condition in bulk docs with new_edits=false in LevelDB. (#2574)
  • Fixes for opts.stale in query(). (pouchdb/mapreduce#196, pouchdb/mapreduce#198)

[Complete list of changes](https://github.com/pouchdb/pouchdb/compare/2.2.3...3.0.0)


Posts 2014 08 16 Pouchdb 3.0.1 (`docs/posts/2014-08-16-pouchdb-3.0.1.md`)


layout: post.html

title: PouchDB 3.0.1

author: Nolan Lawson


PouchDB 3.0.1 is a patch release to fix a migration bug in PouchDB 3.0.0 (#2456). Please upgrade your existing users directly from 2.2.3 and do not use 3.0.0.

This release also contains a small performance boost for bulkDocs() (#2553).


Posts 2014 08 20 Pouchdb 3.0.2 (`docs/posts/2014-08-20-pouchdb-3.0.2.md`)


layout: post.html

title: PouchDB 3.0.2

author: Nolan Lawson


PouchDB 3.0.2 is another small patch release, this time to fix a bug in replication of deleted documents (#2636).

Other fixes:

  • Ensure PouchDB stops writing after replication is over (#2639)
  • Fix a race condition in LevelDB (#2631)
  • Allow special URI characters in doc IDs (#2651)

Posts 2014 08 29 Pouchdb 3.0.3 (`docs/posts/2014-08-29-pouchdb-3.0.3.md`)


layout: post.html

title: PouchDB 3.0.3

author: Nolan Lawson


Another week, another PouchDB patch release!

This week we present PouchDB 3.0.3, which fixes some ornery issues with replication that were introduced by recent performance optimizations (#2685).

In fact, those performance optimizations have been rolled back. The current implementation appears to be the fastest possible given CouchDB's replication protocol, and the clever tricks introduced in 3.0.0 actually pushed conflicts into CouchDB's revision history, which is bad news for apps that rely on custom conflict resolution. (If you just use the default "pick a random winner," you probably didn't notice anything.)

And although "custom conflict resolution" may only describe a minority of apps, we felt it's better to be correct 100% of the time than to be speedy.

Dealing with slow replication

If you experience slow replications after upgrading to 3.0.3, here are some suggestions:

  1. Avoid CORS if you can, because it uses twice as many HTTP requests. Some tips for doing this:
    • If you are using IrisCouch, you can create a couchapp (i.e. let CouchDB serve your HTML/CSS/JS).
    • If you are running your own CouchDB, you can set up a reverse proxy with Apache or Nginx that allows you to serve your app and CouchDB from the same domain. Or create a couchapp.
  2. Try using the "every doc is a delta" pattern, as described in this blog post and exemplified in the delta-pouch plugin. Your replications will be much faster, because PouchDB is able to use an optimization for generation-1 documents, and in this setup, every document is a generation-1 document.
  3. Otherwise, just minimize the number of documents you create. Or minimize the number of documents you sync to the client. Views can be helpful with this, although the low performance of the query() API relative to the allDocs() API should be balanced with that.

Other fixes in 3.0.3

  • Auto-compaction is no longer experimental! It's fully supported. (#2655)
  • 'change' listeners aren't called after replication is canceled. (#2478)
  • Bower package is smaller. (#2659)

Posts 2014 09 04 Pouchdb 3.0.4 (`docs/posts/2014-09-04-pouchdb-3.0.4.md`)


layout: post.html

title: PouchDB 3.0.4: Night of the Living Attachments

author: Calvin Metcalf


This week we present PouchDB 3.0.4, which fixes a bunch of stuff with attachments. Now the kittens generated by the [3.0.0 release]({{ site.baseurl }}/2014/08/12/pouchdb-3.0.0.html) may be safely stored in Pouch.

{% include img.html src="kittens_small.jpg" alt="Kittens" %}

Attachmentspalooza

  • In Firefox, different attachments now (correctly) have different MD5 hashes. Previously they were just hashes of the string '[Object] object'. With this, we also now check that the hashes we produce are identical to the ones that CouchDB produces (with the exception of plaintext ones due to Erlang being weird and not having ASCII strings) (#2698).
  • Weirdness in Chrome 37-39 with FileReader and empty blobs is now fixed (#2713).
  • Fix for a bug in Chrome 37 relating to blobs in IndexedDB (#2701).
  • HTTP pouches now support buffer/blob data in attachments (#2577).

Other fixes in 3.0.4


Posts 2014 09 07 Pouchdb 3.0.5 (`docs/posts/2014-09-07-pouchdb-3.0.5.md`)


layout: post.html

title: PouchDB 3.0.5: Turtles All the Way Down

author: Nolan Lawson


Tonight our fine dining selection is PouchDB 3.0.5, which fixes some issues with excessive recursion and also PouchDB.sync not always working correctly.

PouchDB.sync un-borked ([#2735](https://github.com/pouchdb/pouchdb/issues/2735))

You can now call PouchDB.sync('mydb', 'http://localhost:5984/mydb') using just the database names.

Less typing === more win.

No more infinite recursion ([#2543](https://github.com/pouchdb/pouchdb/issues/2543))

You can now have as many revisions to your documents as you want, and you'll no longer descend Inception-style into a "too much recursion" or "maximum call stack" error.

We literally wrote a custom JSON parser to fix this. That's how much we love you.

Notes on excessive revision histories

The "infinite recursion" bug shows up when you have many (> 1000) revisions to the same document. For instance, imagine a text editor that put()s a new version of the document for every keystroke. This is fine in traditional databases, but it can be a big problem in PouchDB/CouchDB, because we store the entire history of the database.

Make no mistake: infinitely-large revision histories will now work in PouchDB 3.0.5. However, it's a lot like riding a bicycle without a helmet: you can do it, but you probably shouldn't.

Your mental model for this can be something like Git. Imagine a new git commit for every keystroke, and you'll see why it might be a bit problematic.

js
{ "text": "The quick brown fox jumps over the lazy doge." }
{ "text": "The quick brown fox jumps over the lazy doge" }
{ "text": "The quick brown fox jumps over the lazy dog" }
//
// 40 revisions earlier ...
//
{ "text": "The q" }
{ "text": "The " }
{ "text": "The" }
{ "text": "Th" }
{ "text": "T" }

Of course, you can always compact() to remove old versions, but this just removes the document data. PouchDB still needs to maintain a permanent tree of the _rev hashes in order for the replication algorithm to work properly. If this tree grows excessively, it can bloat your database, whether or not you do compaction.

What this means is that you should be very careful about updating your documents. Try to update your documents in larger batches, or use a pattern like delta-pouch to avoid creating new revisions altogether.


Posts 2014 09 22 3.0.6 (`docs/posts/2014-09-22-3.0.6.md`)


layout: post.html

title: PouchDB 3.0.6: Safari Madness

author: Nolan Lawson


As Cupertino fans snuggle up to their bigger and better iDevices, PouchDB users will appreciate PouchDB 3.0.6. This release contains a workaround for some unforeseen hiccups in Apple's new implementation of IndexedDB for Safari 7.1 and iOS 8.

{% include img.html src="apple-indexeddb.png" alt="Apple introduces IndexedDB support" %}

All joking aside, we are happy to welcome Apple to the IndexedDB party (better late than never!), and we are working with the WebKit devs to resolve the issue. In the meantime, PouchDB 3.0.6 provides a workaround where we fall back to Web SQL on Apple browsers.

Other fixes in this release:

  • db.compact() performance improvements (#2623)
  • Ensure attachments work after Chrome 38 upgrade (#2773)
  • Invalid/unauthorized documents no longer halt replication (#2268)
  • Filtering no longer halts replication (#2689)
  • Update lie dependency to 2.8.0

New features in this release:

  • db.info() shows whether auto_compaction is set (#2744)

An aside on browser compatibility

From the get-go, PouchDB's goal was to work seamlessly on every browser, with one API to rule them all. I like to call it the "jQuery for databases" approach.

In practice, this is pretty tough to achieve. I poke fun at Apple, but there are also bugs in IE's implementation of IndexedDB (here are three), Firefox's implementation (this one), and Chrome's implementation (these two). Additionally, some Android devices ship with an outdated version of IndexedDB in the notoriously non-evergreen "stock" browser.

And that's just IndexedDB! Don't even get me started on Web SQL. Here's just a small taste:

We've written workarounds for all these issues in PouchDB, so that you, dear developer, need never worry your pretty head about them.

So this new Safari/iOS bug doesn't phase us. If we were keeping score, this would probably be workaround #72 by now. We're used to it.

However, that doesn't mean we should get complacent. All of these bugs are opportunities to hold browser vendors' feet to the fire, so that eventually the hacks and workarounds are no longer necessary. You don't want to have to download 10KB of workarounds just to use the native HTML5 APIs. And nobody wants to see HTML5 sink under the weight of browser quirks and non-compliance.

We look forward to removing most of these workarounds from PouchDB (including Web SQL support!), so making some noise in the Microsoft/Mozilla/Chromium/WebKit bug trackers is a great way to get there. Submitting test cases and pull requests is even better.

Thanks to Elmer Bulthuis and Kyaw Tun for finding reproducible test cases to submit to the WebKit devs. Let's move the web forward!

And as always, thanks to our new and existing contributors.


Posts 2014 10 26 10 Things I Learned From Reading And Writing The Pouchdb Source (`docs/posts/2014-10-26-10-things-i-learned-from-reading-and-writing-the-pouchdb-source.md`)


layout: post.html

title: 10 things I learned from reading (and writing) the PouchDB source

author: Nolan Lawson


In the spirit of Paul Irish, here's a list of some surprising things I learned while reading (and writing) the PouchDB source code.

To set the stage: I first joined the project around December of last year, at which point PouchDB was already fairly mature. (The first commit from Mikeal Rodgers is already 4 years old!) My own background was mostly in Android development, but I had some knowledge of Web SQL thanks to a PhoneGap app I had worked on.

The main goals I had with PouchDB were to increase its performance and browser compatibility. And having dealt with that ugly F-word that haunts the Android ecosystem ("fragmentation"), I figured compatibility in the web dev world couldn't be much worse. Could it?

Warning: graphic content ahead. If you've ever been a soldier on the front lines of the browser compatibility war, the following may make you relive some of those past horrors. Be prepared to weep.

Also, if you're not familiar with LocalStorage, Web SQL, or IndexedDB, you may want to read up on those first.

1. Nobody can agree on what the Web SQL "estimated size" means

When you open a Web SQL database, you use openDatabase(), e.g.:

js
var db = openDatabase('documents', '1.0', 'some description', 5000000);

That last parameter is the so-called estimated size. And here's how we set it in PouchDB:

js
function getSize(opts) {
  /* ... */
  var isAndroid = /Android/.test(window.navigator.userAgent);
  return isAndroid ? 5000000 : 1;
}

User agent sniffing! Yes, we should be ashamed of ourselves. But here's why we do it:

  • In modern Chrome and Android 4.4+, the size is simply ignored. The browser calculates the remaining size on disk and sets a limit based on that.
  • Aha, but in Android < 4.4, this is actually a hard limit! So if you ask for 5000000, you'll only ever get 5 MB.
  • Oh, but in Safari/iOS, it gets trickier. If you ask for > 5000000, then it will show an [annoying popup]({{ site.baseurl }}/errors.html#not_enough_space) when the app is first loaded, which is a great way to spook your users. But if you ask for less, then there's another popup when the database reaches 5MB, and beyond that there's a bug in iOS 7.1 where the browser will no longer show any more popups, so you're forever capped at 10MB. To store more than 10MB, you need to ask for more than 10MB up-front.
  • Additionally, if you specify anywhere between 0 and 5000000, Safari and iOS will use that size as a hint for when, precisely, to show the popup. And in the case of PouchDB, we need to avoid the popup in our automated tests, because Selenium doesn't give us a way to press the "OK" button, meaning our tests would just fail if we request too much. So the ideal size to request is 0.
  • However, in PhantomJS and older WebKit (Safari ~5), if you request 0, then it will blow up.

For the record, here's what the dreaded Safari popup looks like:

{% include img.html src="safari_popup.png" alt="annoying Safari popup" %}

So that's why we sniff for Android and only bump the size to 5000000 in those cases. In all other cases, we set it to 1.

Additionally, the W3C has done everyone a disservice by using 5*1024*1024 in their sample code, because this has no relation to the 5MB limit in iOS/Safari. The actual cutoff to avoid the popup is 5000000 (i.e. 5 megabytes, aka 5MB), not 5*1024*1024 (5 mebibytes, aka 5MiB). And yet, if you read blog posts and Stack Overflow comments about Web SQL, you'll see the mistaken 1024*1024 repeated all over the place.

2. IE has race conditions in IndexedDB

Microsoft has a very fast implementation of IndexedDB – it's a bit slower than Chrome's, but much faster than Firefox's (here are some tests). However, to get that speed, they must have taken some shortcuts, because both IE10 and IE11 have some nasty race conditions.

Due to that, you'll often see PouchDB code like this:

js
//Close open request for "name" database to fix ie delay.
if (IdbPouch.openReqList[name] && IdbPouch.openReqList[name].result) {
  IdbPouch.openReqList[name].result.close();
}

Or code like this, where we ensure all "open" and "destroy" operations on the databases are done sequentially:

js
taskQueue.queue.push({
  action: function (thisCallback) {
    destroy(name, opts, thisCallback);
  },
  callback: callback
});

Or code like this, where we keep a cache of all databases by name, since IE will not allow us to open two databases with the same name at the same time:

js
var cached = cachedDBs[name];

if (cached) {
  idb = cached.idb;
  /* ... */
}

To their credit, though, the IE team has been very responsive to our bug reports, so this might be fixed soon.

3. Binary data in Web SQL is a mess

At the time the Web SQL spec was hammered out, nothing like Blobs or ArrayBuffers had been standardized. SQLite of course has support for the binary BLOB type, but in order to store binary data in WebSQL, you need to do it the old-fashioned way, and pass in a JavaScript binary string.

This produces two interesting problems:

  • There is a bug in both WebKit and Chromium, where the \u0000 character is treated as a null terminator, and although you can insert and sort on the full string, when you retrieve it, it will be truncated. And since BLOBs must be inserted as binary strings, this means any binary data containing the 0 byte will be truncated. The only workaround is to use SELECT HEX(columnName), which returns the full binary data in hexadecimal format.
  • However, HEX() presents its own problems, because Safari < 7.1 and iOS < 8 coerce all strings to UTF-16, meaning that the hexadecimal format must be parsed differently in UTF-8 browsers (Chrome/Opera/Android as well as modern Safari/iOS) vs. UTF-16 browsers (early Safari/iOS).

Thus you will see fun code like this:

js
function parseHexString(str, encoding) {
  var result = '';
  var charWidth = encoding === 'UTF-8' ? 2 : 4;
  for (var i = 0, len = str.length; i < len; i += charWidth) {
    var substring = str.substring(i, i + charWidth);
    if (charWidth === 4) { // UTF-16, twiddle the bits
      substring = substring.substring(2, 4) + substring.substring(0, 2);
    }
    result += String.fromCharCode(parseInt(substring, 16));
  }
  result = encoding === 'UTF-8' ? decodeUtf8(result) : result;
  return result;
}

(That "twiddle the bits" comment is a bit inaccurate; the technical term is, of course, nibble-swizzling. In the future we will correct this gross oversight by finding some way to work the word "swizzle" into the source code.)

You'll also see us detect UTF-8 vs. UTF-16 encoding like this:

js
function checkDbEncoding(tx) {
  // check db encoding - utf-8 (chrome, opera) or utf-16 (safari)?
  tx.executeSql('SELECT dbid, hex(dbid) AS hexId FROM ' + META_STORE, [],
    function (tx, result) {
      var id = result.rows.item(0).dbid;
      var hexId = result.rows.item(0).hexId;
      encoding = (hexId.length === id.length * 2) ? 'UTF-8' : 'UTF-16';
    }
  );
}

In this code, we know based on the length of the hexadecimal string returned whether the database is UTF-16 or not. So thankfully, because it's feature-detected, this "just works" in Safari 7.1+ and iOS 8+.

As of PouchDB 3.1.0, we will also avoid hexing entirely for large binary attachments, because it causes performance problems. The new workaround will be to just remove \u0000 characters while preserving enough information to put them back in later.

4. Binary data in IndexedDB is a mess too

IndexedDB, as Web SQL's younger, hipper sibling, is supposed to have native support for Blob objects. In practice, though, Blobs were not supported in Chrome until v37, and assuming Apple fixes the more fundamental problems in IndexedDB, they also apparently will not support them.

In those cases, PouchDB falls back to storing Blobs as base64-encoded strings. And we use feature detection to test it:

js
try {
  var blob = utils.createBlob([''], {type: 'image/png'});
  txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(blob, 'key');
  txn.oncomplete = function () {
    /* ... */
    blobSupport = true;
    /* ... */
  };
} catch (err) {
  blobSupport = false;
  /* ... */
}

Ah, but if only it were so easy! It turns out that Chrome v37 also implemented Blob support incorrectly, returning the wrong MIME type when you fetch it. So in Chrome v37, we need to detect the broken blob support, whereas in v38 we can finally start treating it like the other browsers:

js
var storedBlob = e.target.result;
var url = URL.createObjectURL(storedBlob);
utils.ajax({
  url: url,
  cache: true,
  binary: true
}, function (err, res) {
  if (err && err.status === 405) {
    // firefox won't let us do that. but firefox doesn't
    // have the blob type bug that Chrome does, so that's ok
    blobSupport = true;
  } else {
    blobSupport = !!(res && res.type === 'image/png');
  }
});

And yes, Firefox has a tiny bug here as well! Luckily, it's fixed in the nightly build.

So for those keeping score, PouchDB has three different strategies for each of Chrome v36, v37, and v38. And since Android represents different versions of Chromium frozen in amber, all three variations will be out there in the wild for the near future.

5. IE doesn't support complex keys

CouchDB is one of the grandaddies of NoSQL databases, so unsurprisingly it influenced IndexedDB's design. One of the more useful but tricky features of CouchDB is complex keys, best described by Christopher Lenz:

Obvious to Damien [Katz, creator of CouchDB], but not at all obvious to the rest of us: it's fairly simple to make a view that includes both the content of the blog post document, and the content of all the comments associated with that post. The way you do that is by using complex keys. Until now we've been using simple string values for the view keys, but in fact they can be arbitrary JSON values, so let's make some use of that:

The example given is:

js
function(doc) {
  if (doc.type == "post") {
    map([doc._id, 0], doc);
  } else if (doc.type == "comment") {
    map([doc.post, 1], doc);
  }
}

See that? The key is actually an array containing a string and an integer, meaning it would sort first by the string, then by the integer. Cool, right?

This feature is in the IndexedDB spec, and would be a great fit for PouchDB, since we're basically trying to rewrite CouchDB on top of IndexedDB. However, in the source code you will see pseudo-complex keys like this:

js
docInfo.data._doc_id_rev = docInfo.data._id + "::" + docInfo.data._rev;
var seqStore = txn.objectStore(BY_SEQ_STORE);
var index = seqStore.index('_doc_id_rev');

Leading to gnarly queries like this:

js
var start = docId + "::";
var end = docId + "::~";
var index = seqStore.index('_doc_id_rev');
var range = global.IDBKeyRange.bound(start, end, false, false);
var seqCursor = index.openCursor(range);

Did we mess up? Nope, it's intentional: we concatenate the strings together, because IE does not support complex keys.

This also heavily influenced our design for [persistent map/reduce]({{ site.baseurl }}/2014/05/01/secondary-indexes-have-landed-in-pouchdb.html), because instead of assuming the underlying database can sort by more than one value, we invented a toIndexableString() function that converts any JSON object into a big CouchDB-collation-ordered string. Yeah, we went there.

6. IndexedDB throws an error if you try to iterate backwards with start/end keys

{% include alert/start.html variant="info" %}

{% markdown %}

Update: it turned out I just misunderstood the IndexedDB spec. You can actually swap the start and end in the IDBKeyRange, and that allows you to iterate backwards in all browsers. We updated PouchDB accordingly.

{% endmarkdown %}

{% include alert/end.html %}

This is one of those wonderful "bugs" that is actually part of the IndexedDB spec, so it's faithfully reproduced in all three of Firefox, IE, and Chrome. I guess we should be thankful?

Anyway, here's the code:

js
try {
  if (start && end) {
    keyRange = global.IDBKeyRange.bound(start, end, false, !inclusiveEnd);
  } else if (start) {
    /* ... */
  }
} catch (e) {
  if (e.name === "DataError" && e.code === 0) {
    // data error, start is less than end
    return callback(null, {
      total_rows : totalRows,
      offset : opts.skip,
      rows : []
    });
  } else {
    return callback(errors.error(errors.IDB_ERROR, e.name, e.message));
  }
}

In IndexedDB, an error will be thrown for any IDBKeyRange where the start is greater than the end, even if you're iterating backwards. We work around this by manually checking for the end key:

js
if (manualDescEnd) {
  if (inclusiveEnd && doc.key < manualDescEnd) {
    return;
  } else if (!inclusiveEnd && doc.key <= manualDescEnd) {
    return;
  }
}

Luckily, this doesn't really have a big performance impact, except that we fetch one more key than necessary.

7. IndexedDB and Web SQL are jealous of other callbacks

In both IndexedDB and Web SQL, you can forget about using Promises or even invoking another callback within a transaction. Whenever control is returned to the event loop, the transaction auto-closes.

So although your own PouchDB-using code can be elegant and Promisey (thanks especially to Calvin Metcalf's work on lie), under the hood, PouchDB's own code is pure callback hell.

Here's a taste of our IndexedDB code and our WebSQL code. We party like it's 2009, because you'll see lots of callback patterns like this:

js
verifyAttachments(function (err) {
  if (err) {
    return callback(err);
  }
  /* ... */
});

Or this wannabe Promise.all():

js
function checkDoneWritingDocs() {
  if (++numDocsWritten === docInfos.length) {
    complete();
  }
}

And if we need to use some other callback API, such as FileReader, we need to be careful to do it outside of the transaction. Hence you'll see functions like preprocessAttachments():

js
preprocessAttachments(function () {
  db.transaction(function (txn) {
    /* ... */
  });
});

Suffice it to say, if we didn't have extensive integration tests, we'd barely be sure the code worked at all.

8. Recursion is a double-edged sword

Consider this code:

js
// Unfortunately, the metadata has to be stringified
// when it is put into the database, because otherwise
// IndexedDB can throw errors for deeply-nested objects.
// Originally we just used JSON.parse/JSON.stringify; now
// we use this custom vuvuzela library that avoids recursion.
// If we could do it all over again, we'd probably use a
// format for the revision trees other than JSON.
function encodeMetadata(metadata, winningRev, deleted) {
  var storedObject = {data: vuvuzela.stringify(metadata)};
  storedObject.winningRev = winningRev;
  storedObject.deletedOrLocal = deleted ? '1' : '0';
  storedObject.id = metadata.id;
  return storedObject;
}

As it turns out, this is a very tricky bug that ended up affecting all browsers, and even Node.js.

The gist is that any native JavaScript function that takes an object as input, such as JSON.stringify() or IndexedDB's put(), has a maximum limit on the depth of the object you can pass in. By depth I mean:

js
var object = {
  enhance: {
    enhance: {
      enhance: {
        /* and so on */
      }
    }
  }
};

The limit itself will vary based on the available memory, but in any case, if you hit that limit, you get a "too much recursion" or "maximum call stack" error. As well as an unhappy PouchDB user.

Calvin and I solved this by writing a non-recursive JSON library with a silly name. It's slower than the native methods, but in cases where we can't afford to have a crash, this code turned out to be a lifesaver.

9. In IndexedDB, unique indexes throw constraint errors, but keyPaths don't

This is one of those counter-intuitive things that probably made sense to IndexedDB creator Nikunj Mehta, but surprised the hell out of me.

In SQLite and Web SQL, you can create a table with a primary key:

sql
CREATE TABLE employees (id PRIMARY KEY UNIQUE, name);

And you can also add a unique index:

sql
CREATE TABLE employees (id, name);
CREATE UNIQUE INDEX id_index ON employees (id);

And these two are essentially equivalent, because if you try to INSERT a row with an id that already exists, you'll get a constraint error.

In IndexedDB, however, object stores with primary keys will not throw an error upon insertion, but instead silently overwrite the existing object. In other words, the put() is an upsert. Unique indexes, however, behave totally differently and will indeed throw.

Here's a live example. The big takeaway is that the following two examples are not equivalent:

With a primary key:

js
db.createObjectStore('employees', {keyPath : 'id'});

With a unique index:

js
db.createObjectStore('employees').createIndex('id', 'id', {unique: true});

Better hope you choose the right one the first time! Otherwise you may get a constraint error when you don't expect one, or vice versa.

Edit: As pointed out by Simon Friis Vindum, you can use add() instead of put() to get a constraint error with keyPaths. Here's a live example. Thanks for the tip!

10. CouchDB influenced IndexedDB influenced LevelDB influenced...

Databases are not designed in a vacuum, and the more I learn about this stuff, the more I find that everything is related somehow.

Web SQL was originally inspired by Google Gears, which in 2008 was poised to become the standard for storing data in mobile webapps. And of course neither would have been possible without SQLite, which, according to creator Richard Hipp, was heavily influenced by PostgreSQL. In fact, I find his presentation especially inspiring, because Hipp's process of using Postgres as a gold standard for building SQLite reminds me a lot of how PouchDB has been molded in CouchDB's image.

And although the Web SQL spec was eventually abandoned, it certainly influenced its younger sibling IndexedDB. Among other family traits, they share the same asynchronous structure, auto-closing transactions, and security models. (Compare the "security" sections for both specs, and note how much of the language is simply copied over).

Web SQL also lives on indirectly in IndexedDB, given that both Mozilla's and Apple's implementations are independently (!) implemented on top of SQLite.

What's more intriguing is that you can even find the influence of CouchDB in early discussions of IndexedDB. This might explain features like complex keys, start/end key iteration, and the document-esque data model. Nikunj Mehta even said way back in 2009:

Some people find IndexedDB good for a JavaScript CouchDB.

In a sense, this statement may be the earliest expression of what eventually became PouchDB!

Furthermore, Google went on to create LevelDB as their implementation of the IndexedDB spec, which is currently enjoying enormous popularity in the Node.js ecosystem, especially thanks to the LevelUP project. PouchDB itself [has hopped on the LevelUP bandwagon]({{ site.baseurl }}/2014/07/25/pouchdb-levels-up.html), and today we have PouchDB Server, which is a nearly-complete implementation of CouchDB's HTTP API, but based on Node.js and LevelDB.

So from the earliest discussions of IndexedDB, influenced as it was by CouchDB and Web SQL, through LevelDB and the LevelUP ecosystem, we now have a database that unites them all: PouchDB.

When I look through the PouchDB source code, this enormous accomplishment still gives me chills. It's enough to make you overlook all the odd hacks, workarounds, and inelegancies. The fact that PouchDB works at all is a tiny miracle.


Posts 2014 11 10 3.1.0 (`docs/posts/2014-11-10-3.1.0.md`)


layout: post.html

title: PouchDB 3.1.0: A Guide to Recognizing Your Marsupials (The PouchDB Guide)

author: Dale Harvey


Today marks the dawn of a new PouchDB release, 3.1.0 is released today with a whole bunch of goodness. Here are some of the highlights:

{% include alert/start.html variant="warning"%}
This release includes a migration. Your existing database will be updated automatically, but you cannot downgrade to a previous version of PouchDB once you've upgraded.
{% include alert/end.html %}

PouchDB Guides ([#2766](https://github.com/pouchdb/pouchdb/issues/2766))

Nolan Lawson and Nick Colley worked together to produce some excellent guides to PouchDB which you can see @ [{{ site.baseurl }}/guides/]({{ site.baseurl }}/guides/). They cover a lot of the issues surrounding building PouchDB applications including conflicts and promises vs callbacks. As a bonus the guides as well as the rest of the website now work offline.

Compaction removes orphaned attachments ([#2818](https://github.com/pouchdb/pouchdb/issues/2818))

If you have been using attachments in your PouchDB databases, you may have noticed that it was a bit bigger round the waist than it should have been. We now work harder to ensure there isnt any extra things lying around the database taking up space that you no longer need. This change came along with a lot of compaction and attachment related fixes: #2890, #2905, #2951

Test against CouchDB master ([#2850](https://github.com/pouchdb/pouchdb/issues/2850))

There is a big change brewing in CouchDB world, https://issues.apache.org/jira/browse/COUCHDB-1843 is in progress to merge the clustered 'BigCouch' merge from Cloudant into CouchDB master. This will represent a big change towards CouchDB however there is no need to worry, we now run all of our tests against the current master CouchDB (and they are all green!) so when the big CouchDB 2.0 release comes, your PouchDB applications will continue to work as well as before.

We keep a list of [plugins and related external projects]({{ site.baseurl }}/external.html) to PouchDB, here are a few new ones we added during this month:

  • PouchDB Dump and Load are designed to help you load data into your newly started PouchDB app faster than replication will do currently.
  • If you want to use PouchDB with Angular, you are in luck, there are now 4! adapters to help you integrate them: angular-pouchdb, Factoryng, ngPouch, ng-pouchdb.
  • Ember users aren't left in the wild: ember-pouchdb should get you going with PouchDB and Ember.
  • If you are using Blobs with PouchDB, you probably want to take a look at blob-util.

Bug fixes Galore

Some of the other important fixes include:

  • (#2951) - fix concurrency in leveldb.js
  • (#2935) - new_edits=false works regardless of order
  • (#2867) - do not stack notify change listeners
  • (#2779) - fix undeletion with wrong rev
  • (#2794) - explain normal 404s in console.info
  • (#2841) - respect options when making dependent dbs
  • (#2844) - Encode attachment names
  • (#2807) - auto_compaction properly trims tree
  • (#2708) - implement attachment stub length

Please file issues or tell us what you think. We would also like to give a huge thanks to our new and existing contributors.


Posts 2014 11 27 Testing Pouchdb (`docs/posts/2014-11-27-testing-pouchdb.md`)


layout: post.html

title: How we test PouchDB

author: Dale Harvey


While working on and researching improvements for automated testing in PouchDB, I found a lack of blog posts and general information about how open-source libraries test themselves. With PouchDB, a huge amount of effort has gone into testing, but we haven't talked about it much outside the project. So I wanted to explain a little about our approach.

How do we test PouchDB?

Every pull request and check-in to the PouchDB repository goes through the following test run:

{% include img.html href="https://travis-ci.org/pouchdb/pouchdb/builds/40528881" src="travis-screenshot.png" alt="Travis Passing Screenshot" %}

This combination of tests takes around 6 hours running time and covers the minimal options we have found to be confident in not introducing regressions. We test PouchDB against plain CouchDB 1.6.0 on the most popular modern browsers and Node.js, as well as testing alternative backends, servers and options (LocalStorage, PouchDB Server, auto-compaction, etc.). Ideally we would run a full matrix of configurations, however we are hitting the upper limit on how long our tests take to run, so we have picked the ones most likely to surface regressions.

Why care?

Hopefully I don't need to espouse most of the virtues of testing; things that change and aren't tested are usually broken. PouchDB is fairly complex, so we rely on a comprehensive test suite, primarily to ensure we don't introduce regressions as we work on the code.

With PouchDB, however, I have found an added benefit is enabling contributors. By improving our test infrastructure, we make it easier for contributors to have a clear process for verifying behaviour as bugs, writing a failing test and then confirming a fix.

Being confident in your test suite's ability to catch regressions enables us to accept contributions in a reasonable time frame. If we are ever unsure of whether a contribution may break a particular feature, then we use that as an indication to improve the tests in that area, not as a reason to avoid merging the contribution.

Test library

We use Mocha to write almost all of our tests. Mocha has become pretty much the de-facto library for testing JavaScript, and it has worked really well for us.

If I had one complaint about Mocha, it would be I often find myself seeing does not equal [object Object] as its output too often. Some assertion libraries are better than others, but I rarely remember which. We previously used QUnit, but its Node support was sub-par. I experimented with Tape and it was by far the easiest to get setup to run in the browser and Node, however it is opinionated, and if you deviate from the expected workflow, we found it got in the way more than it helped.

Test platform

Our tests run on every check-in and PR on Travis CI. Travis an amazing platform; it was easy to get up and running and has been incredibly stable. PouchDB would be nowhere near the state it is today without it.

However, we are beginning to outgrow Travis's free open-source capacity, and the paid options are prohibitively expensive. So we are beginning to look around for self-hosted alternatives, albeit without a lot of options.

Travis is open-source, however it is not a well-setup project for self-hosting – there isn't so much as a README to get started with. We previously used Jenkins, however I found it a huge maintenance burden. It's very possible someone with more experience in Jenkins could do a better job, though, and I am also keeping an eye on Strider as a possible alternative.

Test runner

We use Selenium to drive tests in the browser, and Saucelabs to run the browsers that we can't run locally. Saucelabs is great, however due to the nature of our tests, we generate a lot of HTTP traffic that needs to be proxied back to Travis, which has been a common point of failure.

We are also finding some platforms (particularly iPhone and Internet Explorer) will become unstable on Saucelabs. The errors almost always come from our code, but it is hard to maintain a reliable test suite when the underlying platform changes and you have little control over it.

Before settling on Selenium, I had previously tried out Testling, and similarly to Tape it was very easy to get started but opinionated. It was also broken on OSX with pull requests containing fixes that hadn't been touched for months. Selenium had the advantage that it was very widely used, and new frameworks or platforms are likely to have WebDriver support early on.

I have however found Selenium as a project frustrating to use, from the first time visiting http://www.seleniumhq.org/ to understanding what I needed to download and write a first test that started a browser, it was an unclear and confusing process. Even today getting ChromeDriver started correctly gets me confused, also the download cost of 30MB for what is mostly a proxy server is an annoyance.

I would love to see a project wrap up Selenium / ChromeDriver and possibly Cordova / Appium into a nice, well-documented module that installs and boots your browser(ish) platform of choice, ready to be driven by Selenium tests.

Performance tests

Performance testing has been troublesome for PouchDB. We have a performance test suite, but it has been hard to integrate it into our CI process, which means it is not comprehensive and often forgotten.

The issue with running in CI is that Travis is set up to run one test per commit, and every time there is a change to the test suite, its data needs to be backfilled in order to properly compare versions. There is also the fact that Saucelabs and Travis may change their running environment at any time, which makes comparisons over time useless.

I expect that to fix this, we will need to run the tests on dedicated hardware and give performance tests some ability to choose which version of PouchDB to test, along with some helper scripts to backfill the performance data.

And done.

Hopefully that gives a little insight into the process and issues we have with testing. It's a long and ongoing process, and I am hoping that the work we do to improve our testing infrastructure can be reused by other projects.

We would love to hear more about the tools, techniques and issues that other similar projects have come across while working on their tests. If you have anything to share, please send it along to @pouchdb.


Posts 2014 12 04 3.2.0 (`docs/posts/2014-12-04-3.2.0.md`)


layout: post.html

title: PouchDB 3.2.0: To code is human, to debug is divine

author: Nolan Lawson


Hot on the heels of the last release, we present 3.2.0, which has new features to increase developer productivity, as well as a slew of bugfixes.

New features

  • Finally, PouchDB has a debug mode! (#2874, #3084)
  • You can now use {attachments: true} to fetch attachments in allDocs(), changes(), and query(), just like CouchDB 1.6.0+ (#2771, #3074, #190)
  • The doc_ids filter is now supported for changes() (#3056)

Bugfixes

  • Fix conflict during replication of deleted documents (#2970)
  • Only detect blob support once (#2990)
  • Fix tree-merging algorithm, correctly attach docs to deleted winning revs (#2888)
  • Fix compaction-related race condition in LevelDB (#3026)
  • Fix LevelDB bulkDocs() ordering (#3062)
  • Fix WebSQL put() local docs when missing (#3066)
  • Fix encoding of base64-string attachments over HTTP (#3008)
  • Better error for invalid revs (#2994)
  • Upgrade Browserify to v6 (#2884)

Please file issues or tell us what you think. And as always, a big thanks to our new and existing contributors!


Posts 2014 12 11 The Pains Of Being Async At Heart (`docs/posts/2014-12-11-the-pains-of-being-async-at-heart.md`)


layout: post.html

title: The Pains Of Being Async At Heart

author: Dale Harvey


If you are testing JavaScript you will likely have to test against async API's. These bring up some issues that you may not be familiar with if you are used to testing synchronous code. Here are a few things I have learnt working on the PouchDB test suite.

Always wait for operations to complete before testing their result

This is the 101 of testing async code but it is the foundation for almost every other problem:

js
db.put({_id: 'doc', foo: 'bar'});
db.get('doc').then(function(doc) {
  assert.equal(doc.foo, 'bar');
});

While this test may pass (although unlikely) it relies on a broken assumption that the write behind .put completes before the read behind .get. The proper way to test this is to wait until the .put is complete:

js
db.put({_id: 'doc', foo: 'bar'}).then(function() {
  return db.get('doc');
}).then(function(doc) {
  assert.equal(doc.foo, 'bar');
});

As a side note Promises are huge improvement over callbacks for testing async code which by nature often involves a long sequential series of steps.

`setTimeout` is almost always evil

js
// Test that a local write gets synced to remote database
localDB.sync(remoteDB, {live: true});
localDB.put({_id: 'doc', foo: 'bar'}).then(function() {
  setTimeout(function() {
    remoteDB.get('doc').then(function(doc) {
      assert.equal(doc.foo, 'bar');
    });
  }, 1000);
});

The main issue with setTimeout is that nothing is guaranteed to complete before the setTimeout finishes. While testing locally 1000ms may be more than enough time for the sync to do its magic if you start testing against non local database with increased latency that 1000ms will often be too short for the test to pass. The correct way to test this would be to listen for an event or to poll for the value:

js
var changes = remoteDB.changes({live: true, include_docs: true});
changes.on('change', function(change) {
  assert.equal(change.doc.foo, 'bar');
  done();
});

localDB.sync(remoteDB, {live: true});
localDB.put({_id: 'doc', foo: 'bar'});

The added benefit to this is that your test will complete as soon as possible instead of waiting the full 1000ms on every run.

I said "almost"

I have found one use case where setTimeout is useful and that is to "prove a negative". This is a test that will pass if the setTimeout ran immediately, for example:

js
// Test we only receive one change event for a write
var numChanges = 0;
var changes = db.changes({live: true, include_docs: true});
changes.on('change', function(change) {
  numChanges++;
  setTimeout(function() {
    assert.equal(numChanges, 1);
    done();
  }, 500);
});

Try going through your tests and change any setTimeout to setTimeout(fun, 0);. If your test fails then it is likely broken.

You should ensure your test is really really finished

This has been a very tricky issue in PouchDB. You want to make sure that when you call done() on a test that you are absolutely finished with any processing otherwise your test may effect follow on tests, to take the previous example:

js
var changes = db.changes({live: true, include_docs: true});
changes.on('change', function(change) {
  assert.equal(change.doc.foo, 'bar');
  changes.cancel();
  done();
});

db.put({_id: 'doc', foo: 'bar'});

This test looks fine however inside changes.cancel() we may be doing some processing that happens asynchronously, specifically we may be aborting a HTTP request that we haven't processed the reply of yet and as we process the reply the next test may have started. This type of issue is extremely problematic as it can lead to unexpected behaviour in tests that are not the cause of the problem.

In PouchDB we fix this with:

js
var changes = db.changes({live: true, include_docs: true});
changes.on('change', function(change) {
  assert.equal(change.doc.foo, 'bar');
  changes.cancel();
});
changes.on('complete', done);

db.put({_id: 'doc', foo: 'bar'});

The complete event is fired when we are sure we have dealt with all the processing involved in that changes event listener and so we know the test is completed any processing when we call done().

There are lots of issues when testing JavaScript but this hits on a few of the main issues we have had in PouchDB. I hope its helpful and would love to hear some issues you may have had with it.


Posts 2015 01 05 Pouchdb 3.2.1 You Can Always Be Faster (`docs/posts/2015-01-05-pouchdb-3.2.1-you-can-always-be-faster.md`)


layout: post.html

title: PouchDB 3.2.1: You can always be faster

author: Nolan Lawson


A humble release to ring in the new year, PouchDB 3.2.1 is focused on bugfixes and performance improvements. In particular, 3.2.1 boasts some critical fixes for IE11 and node-webkit, as well as a fix for replication of conflicted documents.

{% include alert/start.html variant="warning"%}
This release contains a database migration, which means that once you upgrade you will no longer be able to use previous versions of PouchDB on the same databases.
{% include alert/end.html %}

Gotta go fast

For performance, the biggest gains will be seen in secondary indexes and auto-compaction. Auto-compaction now occurs in the same transaction as bulkDocs, which speeds up the build time for secondary indexes (since they use auto-compaction under the hood). This also means that pouchdb-quick-search will be faster.

In addition, the LevelDB adapter now makes all its bulkDocs writes inside a single batch operation. This means that PouchDB can now provide the same consistency guarantees in both Node.js and the browser, since the LevelDB adapter is using the same transactional model as IndexedDB and WebSQL. Also, using a single batch boosts the write performance of LevelDB.

A performance comparison between 3.2.0 and 3.2.1 can be found in this report. The build times for secondary indexes have been reduced by 37%-57%, depending on the browser and environment.

CouchDB 2.0 is coming

A big shout-out this release goes to Will Holley, who has been tirelessly fixing bugs in the PouchDB test suite so that it can pass against CouchDB master. Not only does this improve PouchDB's test coverage against Cloudant, but it also better prepares us for the (soonish!) release of CouchDB 2.0. Thanks, Will!

PouchDB Server is getting awesome

And if you haven't checked out PouchDB Server or express-pouchdb recently, you'll be pleased to see that Marten de Vries has made a ton of improvements over there, including cleaner configuration via config.json, authentication support, a --proxy option, CouchDB-style logging, "modes" to choose different CouchDB server features, and more. Just $ npm install pouchdb-server && pouchdb-server to try it out!

Full changelog:

Bugfixes

  • leveldown is now an optional dependency in Node.js due to build issues with Windows (#3113)
  • Avoid call stack exceeded errors in the merge algorithm (#2543)
  • Properly destroy MemDOWN-based databases (#3144)
  • Avoid "too many listeners" errors in changes() (#2867)
  • Non-winning conflicts properly replicated (#3136)
  • Fixes for node-webkit (#3229)
  • LevelDB uses a read queue to prevent database corruption (#3251)
  • Can build and test on Windows (#2143)
  • Avoid http header error in IE11 (#3278)
  • Blobs are stored more efficiently in Chrome 38+ (#3326 #3325)
  • Many fixes to the test suite to better support CouchDB master (#136)
  • Fix replication when skipSetup is true (#2915)
  • Fixes for webpack (#3287)
  • auto_compaction is correctly reported in info() (#3345)

Performance improvements

  • Replication doesn't make unnecessary allDocs() calls (#3153)
  • Replication GET requests issued in parallel (#3015)
  • Auto-compaction occurs in same transaction as writes (#3089)
  • Formal one-time IndexedDB migration, cleaner schema (#3211, #3182)
  • IndexedDB doesn't store duplicated id/rev (#3213)
  • Fall back from JSON to vuvuzela to improve parse/stringify (#3188)
  • LevelDB makes changes in a single batch (#1738)

Get in touch

Please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2015 02 03 Fix Up Look Sharp (`docs/posts/2015-02-03-fix-up-look-sharp.md`)


layout: post.html

title: PouchDB 3.3.0: Fix Up, Look Sharp
author: Dale Harvey


A new month means a new PouchDB release. Over the last month we have been looking around the rough edges and cleaning things up or chopping them off along with the usual slew of bugfixes and a lot of great news.

PouchDB-Inspector

Marten de Vries has taken the awesome work done by the CouchDB Fauxton team and wrapped Fauxton up in Firefox and Chrome plugins so you can inspect your local data in each browser.

PouchDB-Find

A few months ago Cloudant released a preview of Mango, a MongoDB-inspired query API for Cloudant and CouchDB. The MapReduce API exposed by CouchDB has long been a source of confusion for developers, and this will hopefully provide a more natural way to query data held within CouchDB / PouchDB.

Nolan Lawson took on the mantle and has developed an implementation of Mango for PouchDB. It is currently in beta, and you can try it out as a plugin, try the live demo or even test it by installing PouchDB-Server.

In other news

The excellent team at CozyCloud have been working on a web-based email application that you can run on a personal server based on PouchDB and ReactJS @ https://www.npmjs.com/package/emails. Yours truly will be speaking about PouchDB and the offline web at Web Rebels in Oslo and the Hood.ie team are working on 1. migrating their data storage to PouchDB and 2. extracting some of PouchDB's test setup to be reusable. Contributions and discussion always welcome.

Release Changes

Sync Events - [#3155](https://github.com/pouchdb/pouchdb/issues/3155)

We have introduced new events to give you more information what is happening during syncing. There are now active and paused events triggered during replication and the uptodate event is now deprecated.

Retry Replication - [#966](https://github.com/pouchdb/pouchdb/issues/966)

Calvin did the base work for this a long time ago, however we now officially support a retry option to replication. If you do a db.replicate(to, {retry: true}) or db.sync(db, {retry: true}) then the replication process will not halt when you go offline, and will automatically resume when you (or your server) comes back online.

Docs property in change events - [#3358](https://github.com/pouchdb/pouchdb/pull/3358)

A lot of developers wanted to know what documents were being referred to when they received a change event. Gregor from hood.ie implemented a new docs property in the change event, so you know exactly what documents have changed.

local_seq now deprecated - [#3367](https://github.com/pouchdb/pouchdb/issues/3367)

local_seq was a rarely used property that will be unsupported in CouchDB 2.0, so we have deprecated it and will be removed in future versions.

Changelog:

  • We now have all tests passing against CouchDB master (#136)
  • Fixed return of .compact (#3350)
  • Fix a typo in the Error constructor (#1167)
  • Disabled blob support due to bugs in Chrome, so we now use base64 for attachments in Chrome (#3369)
  • Fix races in .destroy() (mapreduce/#251)
  • Ensure both replications are cancelled properly during .sync (#3431)
  • Replace ajax calls with request polyfill (#3200)
  • Prefer readAsArrayBuffer to BinaryString (#3379)
  • Get all tests green on Travis (including iPhone and IE) (#3058)
  • Map/reduce views built concurrently (pouchdb/mapreduce#240)
  • Quicker map/reduce view build times (pouchdb/mapreduce#242)

Get in touch

Please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2015 02 14 A Quick One (`docs/posts/2015-02-14-a-quick-one.md`)


layout: post.html

title: PouchDB 3.3.1: A quick one while he's away
author: Nolan Lawson


This is a quick release, ahead of our normal monthly release schedule, to fix some critical bugs and ship them out sooner to our eager PouchDB fanbase.

Bugfixes

New stuff

  • Debugging details added to db.info() (#3398)
  • PouchDB blog as an RSS feed (#3516)
  • Testing Couchbase Sync Gateway in Travis (#3526)

Debugging details

As noted above, db.info() now provides adapter-specific debugging info, such as:

  • idb_attachment_format: (IndexedDB) either 'base64' or 'binary', depending on whether the browser supports binary blobs.
  • sqlite_plugin: (WebSQL) true if the SQLite Plugin is being used.
  • websql_encoding: (WebSQL) either 'UTF-8' or 'UTF-16', depending on the [WebSQL implementation]({{ site.baseurl }}/faq.html#data_types)

This should not be considered a stable API, since it's only for debugging and could change at any time.

RSS feed

Thanks to Nick Colley, the PouchDB blog is now available as an RSS feed! Point your RSS reader over to [pouchdb.com/feed.xml]({{ site.baseurl }}/feed.xml) and get notified whenever a new PouchDB version is released.

Couchbase Sync Gateway

The goal of PouchDB is to work seamlessly with any server that's CouchDB-compliant. And thanks to some help from J. Chris Anderson over at Couchbase, we are slowly improving our support for Couchbase Sync Gateway!

The current status is that CSG is being tested in Travis, but not all the tests are succeeding yet. So the next step is to start whittling away the failing tests. In the meantime, we recommend that CSG users who experience any problems (most likely with attachment support) should replicate from PouchDB to CouchDB, then from CouchDB to CSG.

Get in touch

Please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2015 02 28 Efficiently Managing Ui State In Pouchdb (`docs/posts/2015-02-28-efficiently-managing-ui-state-in-pouchdb.md`)


layout: post.html

title: Efficiently managing UI state with PouchDB

author: Nolan Lawson


A common pattern in data-driven apps is to display the entire database in the UI. If you're using PouchDB, that means you want to mirror the contents of PouchDB to some in-memory representation used by the UI.

This could apply to any JavaScript framework such as React, Ember, or Angular. But whatever framework we choose, we'd like to use PouchDB in an efficient way, so that the UI does as little work as possible. Let's learn how.

First off, let's say you are sorting all your documents by _id. You might have a database:

js
var db = new PouchDB('my_db');

And a function to fetch all the documents (using allDocs()) and then render them:

js
function fetchAndRenderAllDocs() {
  db.allDocs({include_docs: true}).then(function (res) {
    var docs = res.rows.map(function (row) { return row.doc; });  
    renderDocsSomehow(docs);
  }).catch(console.log.bind(console));
}

This is okay, but it would be better if the UI could update automatically whenever a document is changed.

A naïve implementation might look like this:

js
db.changes({live: true, since: 'now'}).on('change', function () {
  fetchAndRenderAllDocs();
}).on('error', console.log.bind(console));

fetchAndRenderAllDocs();

This code works, but now we are re-fetching the documents every time a single document changes. This can get pretty slow, especially if you have a lot of documents, because you are constantly re-reading the entire database into memory.

Wouldn't it be better if we could update only the part of the docs array that actually changed? Yes, and we can!

The PouchDB changes feed has a {include_docs: true} option, which will give us exactly the document that changed. There is also a deleted flag that tells us if a document was deleted. So let's react to that:

js
var docs;

function fetchInitialDocs() {
  return db.allDocs({include_docs: true}).then(function (res) {
    docs = res.rows.map(function (row) { return row.doc; });
    renderDocsSomehow();
  });
}

function reactToChanges() {
  db.changes({live: true, since: 'now', include_docs: true}).on('change', function (change) {
    if (change.deleted) {
      // change.id holds the deleted id
      onDeleted(change.id);
    } else { // updated/inserted
      // change.doc holds the new doc
      onUpdatedOrInserted(change.doc);
    }
    renderDocsSomehow();
  }).on('error', console.log.bind(console));
}

fetchInitialDocs().then(reactToChanges).catch(console.log.bind(console));

Now, we need some way to update the array after the changes come in. Our documents are already sorted by _id, but we want to keep them sorted.

So, let's write a quick binary search implementation:

js
function binarySearch(arr, docId) {
  var low = 0, high = arr.length, mid;
  while (low < high) {
    mid = (low + high) >>> 1; // faster version of Math.floor((low + high) / 2)
    arr[mid]._id < docId ? low = mid + 1 : high = mid
  }
  return low;
}

Now let's implement the onDeleted() function:

js
function onDeleted(id) {
  var index = binarySearch(docs, id);
  var doc = docs[index];
  if (doc && doc._id === id) {
    docs.splice(index, 1);
  }
}

And the onUpdatedOrInserted() function:

js
function onUpdatedOrInserted(newDoc) {
  var index = binarySearch(docs, newDoc._id);
  var doc = docs[index];
  if (doc && doc._id === newDoc._id) { // update
    docs[index] = newDoc;
  } else { // insert
    docs.splice(index, 0, newDoc);
  }
}

There you have it! Now the docs array will be kept perfectly in sync with PouchDB.

And even if you're not displaying the entire database in memory (which may be unfeasible for large databases), you can still use this pattern. You'll just need to change the way you render your initial state (e.g. using a query() instead of allDocs()), and to filter the documents in your on('change') listener. You can either do it directly in code, or you can have PouchDB do it for you with the filter/view/doc_ids options.

{% include alert/start.html variant="info" %}

There's no performance benefit to using filter/view/doc_ids on a local database. With a remote database, however, you may get a performance boost, because it's not sending so much data over the wire.

{% include alert/end.html %}

Another neat thing about this code is that it elegantly handles conflicts. Since {include_docs: true} will only give us the winning revision of a document, we are guaranteed that we'll never show conflicting versions of the same document in the array. We can still handle conflicts in [the usual way]({{ site.baseurl }}/guides/conflicts.html), and the changes feed will simply tell us if the winning revision has changed.

I've written a toy app to demonstrate this code. Feel free to borrow it for your next super-fast PouchDB-powered app.


Posts 2015 03 05 Taming The Async Beast With Es7 (`docs/posts/2015-03-05-taming-the-async-beast-with-es7.md`)


layout: post.html

title: Taming the asynchronous beast with ES7

author: Nolan Lawson


One of the trickiest aspects of PouchDB is that its API is asynchronous. I see no shortage of confused questions on Stack Overflow, Github, and IRC, and most often they stem from a misunderstanding of callbacks and promises.

We can't really help it. PouchDB is an abstraction over IndexedDB, WebSQL, LevelDB (in Node), and CouchDB (via Ajax). All of those APIs are asynchronous; hence PouchDB must be asynchronous.

When I think of elegant database APIs, however, I'm still struck by the simplicity of LocalStorage:

{% highlight js %}
if (!localStorage.foo) {
localStorage.foo = 'bar';
};
console.log(localStorage.foo);
{% endhighlight %}

To work with LocalStorage, you simply treat it like a magical JavaScript object that happens to persist your data. It uses the same synchronous toolset that you're already used to from working with JavaScript itself.

For all of LocalStorage's faults, the ergonomics of this API go a long way to explain its continuing popularity. People keep using LocalStorage, because it's simple and works exactly as expected.

Promises aren't a panacea

For PouchDB, we can try to mitigate the complexity of asynchronous APIs with promises, and that certainly helps us escape the pyramid of doom.

However, promisey code is still hard to read, because promises are basically a bolt-on replacement for language primitives like try, catch, and return:

{% highlight js %}
var db = new PouchDB('mydb');
db.post({}).then(function (result) { // post a new doc
return db.get(result.id); // fetch the doc
}).then(function (doc) {
console.log(doc); // log the doc
}).catch(function (err) {
console.log(err); // log any errors
});
{% endhighlight %}

As JavaScript developers, we now have two parallel systems – sync and async – that we have to keep straight in our heads. And this gets even worse as our control flow becomes more complex, and we need to reach for APIs like Promise.all() and Promise.resolve(). Or maybe we just opt for one of the many helper libraries and pray we can understand the documentation.

Until recently, this was the best we could hope for. But all of that changes with ES7.

Enter ES7

What if I told you that, with ES7, you could rewrite the above code to look like this:

{% highlight js %}
let db = new PouchDB('mydb');
try {
let result = await db.post({});
let doc = await db.get(result.id);
console.log(doc);
} catch (err) {
console.log(err);
}
{% endhighlight %}

And what if I told you that, thanks to tools like Babel.js and Regenerator, you can transpile that down to ES5 and run it in a browser today?

{% include img.html src="orson_welles_clapping.gif" alt="Orson Welles clapping enthusiastically in 'Citizen Kane'" %}

Please ladies and gentlemen, hold your applause until the end of the blog post.

First, let's take a look at how ES7 is accomplishing this amazing feat.

Async functions

ES7 gives us a new kind of function, the async function. Inside of an async function, we have a new keyword, await, which we use to "wait for" a promise:

{% highlight js %}
async function myFunction() {
let result = await somethingThatReturnsAPromise();
console.log(result); // cool, we have a result
}
{% endhighlight %}

If the promise resolves, we can immediately interact with it on the next line. And if it rejects, then an error is thrown. So try/catch actually works again!

{% highlight js %}
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err); // oh noes, we got an error
}
}
{% endhighlight %}

This allows us to write code that looks synchronous on the surface, but is actually asynchronous under the hood. The fact that the API returns a promise instead of blocking the event loop is just an implementation detail.

{% include img.html src="pepperidge_farm_remembers.png" alt="Remember when you could just use 'return' and 'try/catch'? Pepperidge Farm remembers. (Pepperidge Farm meme, old man holding a bag of biscuits and looking at you wistfully.)" %}

And the best part is, we can use this today with any library that returns promises. PouchDB is such a library, so let's use it to test our theory.

Managing errors and return values

First, consider a common idiom in PouchDB: we want to get() a document by _id if it exists, or return a new document if it doesn't.

With promises, you'd have to write something like this:

{% highlight js %}
db.get('docid').catch(function (err) {
if (err.name === 'not_found') {
return {}; // new doc
}
throw err; // some error other than 404
}).then(function (doc) {
console.log(doc);
})
{% endhighlight %}

With async functions, this becomes:

{% highlight js %}
let doc;
try {
doc = await db.get('docid');
} catch (err) {
if (err.name === 'not_found') {
doc = {};
} else {
throw err; // some error other than 404
}
}
console.log(doc);
{% endhighlight %}

Much more readable! This is almost the exact same code we would write if db.get() directly returned a document rather than a promise. The only difference is that we have to add the await keyword when we call any promise-returning function.

Potential gotchas

There are a few subtle issues that I ran into while playing with this, so it's good to be aware of them.

First off, anytime you await something, you need to be inside an async function. So if your code relies heavily on PouchDB, you may find that you write lots of async functions, but very few regular functions.

Another, more insidious problem is that you have to be careful to wrap your code in try/catches, or else a promise might be rejected, in which case the error is silently swallowed. (!)

My advice is to ensure that your async functions are entirely surrounded by try/catches, at least at the top level:

{% highlight js %}
async function createNewDoc() {
let response = await db.post({}); // post a new doc
return await db.get(response.id); // find by id
}

async function printDoc() {
try {
let doc = await createNewDoc();
console.log(doc);
} catch (err) {
console.log(err);
}
}
{% endhighlight %}

Loops

Async functions get really impressive when it comes to iteration. For instance, Let's say that we want to insert some documents into the database, but sequentially. That is, we want the promises to execute one after the other, not concurrently.

Using standard ES6 promises, we'd have to roll our own promise chain:

{% highlight js %}
var promise = Promise.resolve();
var docs = [{}, {}, {}];

docs.forEach(function (doc) {
promise = promise.then(function () {
return db.post(doc);
});
});

promise.then(function () {
// now all our docs have been saved
});
{% endhighlight %}

This works, but it sure is ugly. It's also error-prone, because if you accidentally do:

{% highlight js %}
docs.forEach(function (doc) {
promise = promise.then(db.post(doc));
});
{% endhighlight %}

Then the promises will actually execute concurrently, which can lead to unexpected results.

With ES7, though, we can just use a regular for-loop:

{% highlight js %}
let docs = [{}, {}, {}];

for (let i = 0; i < docs.length; i++) {
let doc = docs[i];
await db.post(doc);
}
{% endhighlight %}

This (very concise) code does the same thing as the promise chain! We can make it even shorter by using for...of:

{% highlight js %}
let docs = [{}, {}, {}];

for (let doc of docs) {
await db.post(doc);
}
{% endhighlight %}

Note that you cannot use a forEach() loop here. If you were to naïvely write:

{% highlight js %}
let docs = [{}, {}, {}];

// WARNING: this won't work
docs.forEach(function (doc) {
await db.post(doc);
});
{% endhighlight %}

Then Babel.js will fail with a somewhat opaque error:

text
Error : /../script.js: Unexpected token (38:23)
> 38 |     await db.post(doc);
     |           ^

This is because you cannot use await from within a normal function. You have to use an async function.

However, if you try to use an async function, then you will get a more subtle bug:

{% highlight js %}
let docs = [{}, {}, {}];

// WARNING: this won't work
docs.forEach(async function (doc, i) {
await db.post(doc);
console.log(i);
});
console.log('main loop done');
{% endhighlight %}

This will compile, but the problem is that this will print out:

text
main loop done
0
1
2

What's happening is that the main function is exiting early, because the await is actually in the sub-function. Furthermore, this will execute each promise concurrently, which is not what we intended.

The lesson is: be careful when you have any function inside your async function. The await will only pause its parent function, so check that it's doing what you actually think it's doing.

Concurrent loops

If we do want to execute multiple promises concurrently, though, then this is pretty easy to accomplish with ES7.

Recall that with ES6 promises, we have Promise.all(). Let's use it to return an array of values from an array of promises:

{% highlight js %}
var docs = [{}, {}, {}];

return Promise.all(docs.map(function (doc) {
return db.post(doc);
})).then(function (results) {
console.log(results);
});
{% endhighlight %}

In ES7, we can do this is a more straightforward way:

{% highlight js %}
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));

let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
{% endhighlight %}

The most important parts are 1) creating the promises array, which starts invoking all the promises immediately, and 2) that we are awaiting those promises within the main function. If we tried to use Array.prototype.map, then it wouldn't work:

{% highlight js %}
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));

// WARNING: this doesn't work
let results = promises.map(async function(promise) {
return await promise;
});

// This will just be a list of promises :(
console.log(results);
{% endhighlight %}

The reason this doesn't work is because we are awaiting inside of the sub-function, and not the main function. So the main function exits before we are really done waiting.

If you don't mind using Promise.all, you can also use it to tidy up the code a bit:

{% highlight js %}
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));

let results = await Promise.all(promises);
console.log(results);
{% endhighlight %}

Presumably this could look even nicer if we used array comprehesions. However, the spec isn't final yet, so it's currently not supported by Regenerator.

Caveats

ES7 is still very bleeding-edge. Async functions aren't supported in either Node.js or io.js, and you have to set some experimental flags to even get Babel to consider it. Officially, the async/await spec is still in the "proposal" stage.

Also, you'll need to include the Regenerator runtime and ES6 shims in your transpiled code for this to work in ES5 browsers. For me, that added up to about 60KB, minified and gzipped. For many developers, that's just way too much to ship down the wire.

However, all of these new tools are very fun to play with, and they paint a bright picture of what working with asynchronous libraries will look like in the sunny ES7 future.

So if you want to play with it yourself, I've put together a small demo library. To get started, just check out the code, run npm install && npm run build, and you're good to go. And for more about ES7, check out this talk by Jafar Husain.

Conclusion

Async functions are an empowering new concept in ES7. They give us back our lost returns and try/catches, and they reward the knowledge we've already gained from writing synchronous code with new idioms that look a lot like the old ones, but are much more performant.

Most importantly, async functions make APIs like PouchDB's a lot easier to work with. So hopefully this will lead to fewer user errors and confusion, as well as more elegant and readable code.

And who knows, maybe folks will finally abandon LocalStorage, and opt for a more modern client-side database.


Posts 2015 04 05 Filtered Replication (`docs/posts/2015-04-05-filtered-replication.md`)


layout: post.html

title: Filtered replication: from Couch to Pouch and back

author: Giovanni Ornaghi


Filtered replication can become a vital feature for many applications, when you realize you don't need the whole dataset to be replicated to each client. At the same time, filtered replication can be the wrong solution to your problem if:

  • You're trying to address security concerns. Replicating only the user's documents via filtering might seem simplest, but filtering isn't a substitute for proper authentication.
  • There's no big security concern, but you'd like to provide a better per-user or per-role experience. There's no shame in creating a DB per user or DB per role! Nolan Lawson discussed such recipes here.

So what is filtered replication good for? One use case is apparent: you want to give your users only a certain amount of documents, which may or may not involve their identity or roles. Sometimes these documents need to be shared by multiple users with real-time feedback, which would make the DB-per-user solution impractical. And often, these groups of users are too volatile to effectively implement a DB-per-role solution.

In these situations, the best solution is filtering.

Filters in PouchDB

In the PouchDB world, there are two shapes of filtered replication:

  • Client-side filtering takes nothing more than a JS function. This will prevent useless documents from being stored locally, but it means the documents will still go over the wire, and the client will waste CPU cycles to handle them properly.
  • Server-side filtering, again, takes nothing more than a JS function, but it's executed by CouchDB. This will prevent documents from going over the wire in the first place! So obviously we prefer this one.

A simple implementation

{% include alert/start.html variant="warning" %}
To reproduce the examples you’ll need PouchDB v3.4.0, which contains some bugfixes for filtered replication.
{% include alert/end.html %}

The first step in implementing your server-side filtering solution is to create the design document. This is an example:

{% highlight js %}
{
"_id": "_design/app",
"filters": {
"by_agent": function(doc, req) {
return doc.agent === req.query.agent;
}.toString()
}
}
{% endhighlight %}

Filters in CouchDB are like filters as higher-order functions: they take some arguments and return true or false. Note that the function needs to be stringified when you store it!

{% include alert/start.html variant="info" %}

{% markdown %}

While in these examples I'm using a filter, the rest of this post will hold if you decide to use a view instead.

At this point you might be wondering about the difference between a view and a filter. My reason for using filters is easy: I want to emit the whole document, and I want to emit documents according to a parameter provided by the client. While you could create a view that emits the whole document, taking parameters becomes a bit too complicated for my taste.

{% endmarkdown %}

{% include alert/end.html %}

We'll come back to this design document later. Now it's time to implement our client-side logic. By reading the awesome [API docs]({{ site.baseurl }}/api.html#replication), we see that sync has various options:

  • filter: can take either the string corresponding to the filter function (see example below), or a JS function (for client-side filtering).
  • query_params: takes a JS object. This object is what we find in the req.query object inside the design document function. Just what we need!

{% highlight js %}
localDB.sync(remoteDB, {
live: true,
retry: true,
filter: 'app/by_agent',
query_params: { "agent": agent }
}).on('change', function(result) {
if (change.deleted){
// remove
} else {
// upsert
}
});
{% endhighlight %}

We're doing a two-way replication (from Couch to Pouch and back), using a filter (in our faithful design document) all in real-time (see live and retry options). Couch will give us a batch of documents from the _changes feed, which we then iterate and manage according to our needs.

Caveats

Now, this looks easy, and it is, but there are a few gotchas:

  • Since we're doing live replication, the complete event will not trigger, so use paused instead.
  • Documents will come in batches, so you might not get the whole _changes feed at once.
  • You cannot really delete documents in the local database. Purging is a feature the PouchDB team is still working on.
  • If you change something on the server side to cause the document to no longer pass the filter, then the document won't pass the filter. Weird, right? CouchDB won't check the last two versions of the document – just the last one. This simply means that those documents will persist on the client and never be present in the _changes feed.
  • Watch how you delete your documents! Simply going into Futon and happily clicking "Delete Document…" won't replicate the deletion. What you want to do is update the document, adding a _deleted: true field. From the CouchDB docs:

When using filtered replication, you should not use the DELETE method to remove documents, but instead use PUT and add a _deleted:true
field to the document, preserving the fields required for the filter. Your Document Update Handler should make sure these fields are always
present. This will ensure that the filter will propagate deletions properly.

In PouchDB, this corresponds to put()ing a document with _deleted: true, rather than remove()ing it.

The next gotcha deserves a bit more space. I find it very counter-intuitive, and my guess is that you'll feel the same. Since you're interested in two-way replication, you want the client to not only read data, but write data as well. What you expect is that saves on the local database will get replicated to the remote database. Let's look at some code:

{% highlight js %}
this.save = function (foobar) {
return localDB.get(foobar._id).then(function (doc) {
doc.someNiceField = foobar.someNiceField;
return localDB.put(doc);
});
};
{% endhighlight %}

In ORM parlance, this is a "connected scenario" update. You retrieve the document from the local database, you change the fields you need to change, then you put it back into the local database.

If you followed this post step-by-step, however, this won't work. Why? To make two-way filtered replication work, the design document needs to be in both the remote database and the local database. To do this, we might decide to simply replicate the design document alongside the other documents. Hence our design document becomes:

{% highlight js %}
{
"_id": "_design/app",
"filters": {
"by_agent": function(doc, req) {
return doc._id === '_design/app' || doc.agent === req.query.agent;
}.toString()
}
}
{% endhighlight %}

It is enough to have it stored locally; Pouch will handle the rest. The downside is that now we need to remember to handle the design document, by not letting it mingle with the documents needed in the UI.

If you feel you'd rather keep the filter function clean and not worry about filtering the design document itself, then you could also have two different design documents by the same id, one in Couch and one in Pouch, not replicating.

Conclusion

With the new trend of offline-first apps and microservices, data replication has become the norm, even for boring CRUD apps. Rolling out your set of webservices, push notifications, or background services might give you more control, but at the same time it will force you to engineer, write, test, and maintain a whole new ecosystem.

And still, two-way replication is as much about a new set of tools as it is a new way of thinking. In offline-first apps, for example, as the number of edge cases regarding the user interaction with data multiplies, so do the possibilities for conflicts between documents.

So is it worth it? CouchDB + PouchDB make an excellent, almost turnkey solution by taming a good number of use cases. My hope is that this short post will make your own transition easier.


Posts 2015 04 07 Better Late Than Never (`docs/posts/2015-04-07-better-late-than-never.md`)


layout: post.html

title: PouchDB 3.4.0: Better late than never
author: Dale Harvey


Everyone needs a break, and for March PouchDB relaxed and enjoyed the arrival of spring. However, regularly scheduled programming has resumed, and today I am happy to announce PouchDB 3.4.0.

Documentation, Documentation, Documentation

Over the last 2 months a huge amount of work has gone into PouchDB's documentation, including #3584, which allows you to switch between a Promises and a callback format for your code examples.

We have hugely expanded the coverage of the documentation, so if there is anything missing please feel free to point it out.

Changelog:

  • Fix incompatibilities with Couchbase Sync Gateway (#3556, #3552, #3555, #3561, #3562, #3562, #3495, #3493)
  • Added a copy of PouchDB to PouchDB.com (check the inspector - #2960)
  • Fix replication using design documents (#3543)
  • Fix immediately cancelling live replication (#3605)
  • Fix replication with a view (#3606)
  • Add support for new SqlitePlugin parameters (#3617)
  • Fix for deleted conflicts (#3646)
  • Fix design documents that contain a slash (#3680)
  • Fix for setting ajax headers in GET requests (#3689)
  • Allow setting auth headers in replication (#3543)

Get in touch

Please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2015 05 07 Pouchdb 3.5.0 Vote For Pouchdb (`docs/posts/2015-05-07-pouchdb-3.5.0-vote-for-pouchdb.md`)


layout: post.html

title: PouchDB 3.5.0: Vote for PouchDB
author: Dale Harvey


Do you support better documentation for PouchDB users? Do you value faster CORS and better Service Worker support? If so, then today you should cast your vote in support of PouchDB. Together, we can make a better future (release of PouchDB).

New features

Bugfixes

  • Fallback to the fetch API where XMLHTTPRequest does not exist (e.g. in Service Workers) (#3678)
  • Stop triggering unnecessary OPTIONS requests during CORS replication (#3573)
  • Multitudes of documentation fixes
  • Fix es3ify behavior with dependencies (#3688)
  • Add source maps to dev environment (#3731)
  • Fix constructor when called with {name: 'foo'} (#3471)
  • Support for Blackberry 10 (#3737)
  • Fix detection for xhr.upload in React Native (#3796)
  • Webpack support (#3319)

Get in touch

Please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2015 05 18 We Have A Problem With Promises (`docs/posts/2015-05-18-we-have-a-problem-with-promises.md`)


layout: post.html

title: We have a problem with promises
author: Nolan Lawson


Fellow JavaScripters, it's time to admit it: we have a problem with promises.

No, not with promises themselves. Promises, as defined by the A+ spec, are awesome.

The big problem, which has revealed itself to me over the course of the past year, as I've watched numerous programmers struggle with the PouchDB API and other promise-heavy APIs, is this:

Many of us are using promises without really understanding them.

If you find that hard to believe, consider this puzzle I recently posted to Twitter:

Q: What is the difference between these four promises?

js
doSomething().then(function () {
  return doSomethingElse();
});

doSomething().then(function () {
  doSomethingElse();
});

doSomething().then(doSomethingElse());

doSomething().then(doSomethingElse);

If you know the answer, then congratulations: you're a promises ninja. You have my permission to stop reading this blog post.

For the other 99.99% of you, you're in good company. Nobody who responded to my tweet could solve it, and I myself was surprised by the answer to #3. Yes, even though I wrote the quiz!

The answers are at the end of this post, but first, I'd like to explore why promises are so tricky in the first place, and why so many of us – novices and experts alike – get tripped up by them. I'm also going to offer what I consider to be the singular insight, the one weird trick, that makes promises a cinch to understand. And yes, I really do believe they're not so hard after that!

But to start with, let's challenge some common assumptions about promises.

Wherefore promises?

If you read the literature on promises, you'll often find references to the pyramid of doom, with some horrible callback-y code that steadily stretches toward the right side of the screen.

Promises do indeed solve this problem, but it's about more than just indentation. As explained in the brilliant talk "Redemption from Callback Hell", the real problem with callbacks it that they deprive us of keywords like return and throw. Instead, our program's entire flow is based on side effects: one function incidentally calling another one.

And in fact, callbacks do something even more sinister: they deprive us of the stack, which is something we usually take for granted in programming languages. Writing code without a stack is a lot like driving a car without a brake pedal: you don't realize how badly you need it, until you reach for it and it's not there.

The whole point of promises is to give us back the language fundamentals we lost when we went async: return, throw, and the stack. But you have to know how to use promises correctly in order to take advantage of them.

Rookie mistakes

Some people try to explain promises as a cartoon, or in a very noun-oriented way: "Oh, it's this thing you can pass around that represents an asynchronous value."

I don't find such explanations very helpful. To me, promises are all about code structure and flow. So I think it's better to just go over some common mistakes and show how to fix them. I call these "rookie mistakes" in the sense of, "you're a rookie now, kid, but you'll be a pro soon."

Quick digression: "promises" mean a lot of different things to different people, but for the purposes of this article, I'm only going to talk about the official spec, as exposed in modern browsers as window.Promise. Not all browsers have window.Promise though, so for a good polyfill, check out the cheekily-named Lie, which is about the smallest spec-compliant library out there.

Rookie mistake #1: the promisey pyramid of doom

Looking at how people use PouchDB, which has a largely promise-based API, I see a lot of poor promise patterns. The most common bad practice is this one:

js
remotedb.allDocs({
  include_docs: true,
  attachments: true
}).then(function (result) {
  var docs = result.rows;
  docs.forEach(function(element) {
    localdb.put(element.doc).then(function(response) {
      alert("Pulled doc with id " + element.doc._id + " and added to local db.");
    }).catch(function (err) {
      if (err.name == 'conflict') {
        localdb.get(element.doc._id).then(function (resp) {
          localdb.remove(resp._id, resp._rev).then(function (resp) {
// et cetera...

Yes, it turns out you can use promises as if they were callbacks, and yes, it's a lot like using a power sander to file your nails, but you can do it.

And if you think this sort of mistake is only limited to absolute beginners, you'll be surprised to learn that I actually took the above code from the official BlackBerry developer blog! Old callback habits die hard. (And to the developer: sorry to pick on you, but your example is instructive.)

A better style is this one:

js
remotedb.allDocs(...).then(function (resultOfAllDocs) {
  return localdb.put(...);
}).then(function (resultOfPut) {
  return localdb.get(...);
}).then(function (resultOfGet) {
  return localdb.put(...);
}).catch(function (err) {
  console.log(err);
});

This is called composing promises, and it's one of the great superpowers of promises. Each function will only be called when the previous promise has resolved, and it'll be called with that promise's output. More on that later.

Rookie mistake #2: WTF, how do I use `forEach()` with promises?

This is where most people's understanding of promises starts to break down. As soon as they reach for their familiar forEach() loop (or for loop, or while loop), they have no idea how to make it work with promises. So they write something like this:

js
// I want to remove() all docs
db.allDocs({include_docs: true}).then(function (result) {
  result.rows.forEach(function (row) {
    db.remove(row.doc);  
  });
}).then(function () {
  // I naively believe all docs have been removed() now!
});

What's the problem with this code? The problem is that the first function is actually returning undefined, meaning that the second function isn't waiting for db.remove() to be called on all the documents. In fact, it isn't waiting on anything, and can execute when any number of docs have been removed!

This is an especially insidious bug, because you may not notice anything is wrong, assuming PouchDB removes those documents fast enough for your UI to be updated. The bug may only pop up in the odd race conditions, or in certain browsers, at which point it will be nearly impossible to debug.

The TLDR of all this is that forEach()/for/while are not the constructs you're looking for. You want Promise.all():

js
db.allDocs({include_docs: true}).then(function (result) {
  return Promise.all(result.rows.map(function (row) {
    return db.remove(row.doc);
  }));
}).then(function (arrayOfResults) {
  // All docs have really been removed() now!
});

What's going on here? Basically Promise.all() takes an array of promises as input, and then it gives you another promise that only resolves when every one of those other promises has resolved. It is the asynchronous equivalent of a for-loop.

Promise.all() also passes an array of results to the next function, which can get very useful, for instance if you are trying to get() multiple things from PouchDB. The all() promise is also rejected if any one of its sub-promises are rejected, which is even more useful.

Rookie mistake #3: forgetting to add .catch()

This is another common mistake. Blissfully confident that their promises could never possibly throw an error, many developers forget to add a .catch() anywhere in their code. Unfortunately this means that any thrown errors will be swallowed, and you won't even see them in your console. This can be a real pain to debug.

To avoid this nasty scenario, I've gotten into the habit of simply adding the following code to my promise chains:

js
somePromise().then(function () {
  return anotherPromise();
}).then(function () {
  return yetAnotherPromise();
}).catch(console.log.bind(console)); // <-- this is badass

Even if you never expect an error, it's always prudent to add a catch(). It'll make your life easier, if your assumptions ever turn out to be wrong.

Rookie mistake #4: using "deferred"

This is a mistake I see all the time, and I'm reluctant to even repeat it here, for fear that, like Beetlejuice, merely invoking its name will summon more instances of it.

In short, promises have a long and storied history, and it took the JavaScript community a long time to get them right. In the early days, jQuery and Angular were using this "deferred" pattern all over the place, which has now been replaced with the ES6 Promise spec, as implemented by "good" libraries like Q, When, RSVP, Bluebird, Lie, and others.

So if you are writing that word in your code (I won't repeat it a third time!), you are doing something wrong. Here's how to avoid it.

First off, most promise libraries give you a way to "import" promises from third-party libraries. For instance, Angular's $q module allows you to wrap non-$q promises using $q.when(). So Angular users can wrap PouchDB promises this way:

js
$q.when(db.put(doc)).then(/* ... */); // <-- this is all the code you need

Another strategy is to use the revealing constructor pattern, which is useful for wrapping non-promise APIs. For instance, to wrap a callback-based API like Node's fs.readFile(), you can simply do:

js
new Promise(function (resolve, reject) {
  fs.readFile('myfile.txt', function (err, file) {
    if (err) {
      return reject(err);
    }
    resolve(file);
  });
}).then(/* ... */)

Done! We have defeated the dreaded def... Aha, caught myself. :)

{% include alert/start.html variant="info"%}
{% markdown %}
For more about why this is an anti-pattern, check out the Bluebird wiki page on promise anti-patterns.
{% endmarkdown %}
{% include alert/end.html %}

Rookie mistake #5: using side effects instead of returning

What's wrong with this code?

js
somePromise().then(function () {
  someOtherPromise();
}).then(function () {
  // Gee, I hope someOtherPromise() has resolved!
  // Spoiler alert: it hasn't.
});

Okay, this is a good point to talk about everything you ever need to know about promises.

Seriously, this is the one weird trick that, once you understand it, will prevent all of the errors I've been talking about. You ready?

As I said before, the magic of promises is that they give us back our precious return and throw. But what does this actually look like in practice?

Every promise gives you a then() method (or catch(), which is just sugar for then(null, ...)). Here we are inside of a then() function:

js
somePromise().then(function () {
  // I'm inside a then() function!
});

What can we do here? There are three things:

  1. return another promise
  2. return a synchronous value (or undefined)
  3. throw a synchronous error

That's it. Once you understand this trick, you understand promises. So let's go through each point one at a time.

1. Return another promise

This is a common pattern you see in the promise literature, as in the "composing promises" example above:

js
getUserByName('nolan').then(function (user) {
  return getUserAccountById(user.id);
}).then(function (userAccount) {
  // I got a user account!
});

Notice that I'm returning the second promise – that return is crucial. If I didn't say return, then the getUserAccountById() would actually be a side effect, and the next function would receive undefined instead of the userAccount.

2. Return a synchronous value (or undefined)

Returning undefined is often a mistake, but returning a synchronous value is actually an awesome way to convert synchronous code into promisey code. For instance, let's say we have an in-memory cache of users. We can do:

js
getUserByName('nolan').then(function (user) {
  if (inMemoryCache[user.id]) {
    return inMemoryCache[user.id];    // returning a synchronous value!
  }
  return getUserAccountById(user.id); // returning a promise!
}).then(function (userAccount) {
  // I got a user account!
});

Isn't that awesome? The second function doesn't care whether the userAccount was fetched synchronously or asynchronously, and the first function is free to return either a synchronous or asynchronous value.

Unfortunately, there's the inconvenient fact that non-returning functions in JavaScript technically return undefined, which means it's easy to accidentally introduce side effects when you meant to return something.

For this reason, I make it a personal habit to always return or throw from inside a then() function. I'd recommend you do the same.

3. Throw a synchronous error

Speaking of throw, this is where promises can get even more awesome. Let's say we want to throw a synchronous error in case the user is logged out. It's quite easy:

js
getUserByName('nolan').then(function (user) {
  if (user.isLoggedOut()) {
    throw new Error('user logged out!'); // throwing a synchronous error!
  }
  if (inMemoryCache[user.id]) {
    return inMemoryCache[user.id];       // returning a synchronous value!
  }
  return getUserAccountById(user.id);    // returning a promise!
}).then(function (userAccount) {
  // I got a user account!
}).catch(function (err) {
  // Boo, I got an error!
});

Our catch() will receive a synchronous error if the user is logged out, and it will receive an asynchronous error if any of the promises are rejected. Again, the function doesn't care whether the error it gets is synchronous or asynchronous.

This is especially useful because it can help identify coding errors during development. For instance, if at any point inside of a then() function, we do a JSON.parse(), it might throw a synchronous error if the JSON is invalid. With callbacks, that error would get swallowed, but with promises, we can simply handle it inside our catch() function.

Advanced mistakes

Okay, now that you've learned the single trick that makes promises dead-easy, let's talk about the edge cases. Because of course, there are always edge cases.

These mistakes I'd classify as "advanced," because I've only seen them made by programmers who are already fairly adept with promises. But we're going to need to discuss them, if we want to be able to solve the puzzle I posed at the beginning of this post.

Advanced mistake #1: not knowing about `Promise.resolve()`

As I showed above, promises are very useful for wrapping synchronous code as asynchronous code. However, if you find yourself typing this a lot:

js
new Promise(function (resolve, reject) {
  resolve(someSynchronousValue);
}).then(/* ... */);

You can express this more succinctly using Promise.resolve():

js
Promise.resolve(someSynchronousValue).then(/* ... */);

This is also incredibly useful for catching any synchronous errors. It's so useful, that I've gotten in the habit of beginning nearly all of my promise-returning API methods like this:

js
function somePromiseAPI() {
  return Promise.resolve().then(function () {
    doSomethingThatMayThrow();
    return 'foo';
  }).then(/* ... */);
}

Just remember: any code that might throw synchronously is a good candidate for a nearly-impossible-to-debug swallowed error somewhere down the line. But if you wrap everything in Promise.resolve(), then you can always be sure to catch() it later.

Similarly, there is a Promise.reject() that you can use to return a promise that is immediately rejected:

js
Promise.reject(new Error('some awful error'));

Advanced mistake #2: `then(resolveHandler).catch(rejectHandler)` isn't exactly the same as `then(resolveHandler, rejectHandler)`

I said above that catch() is just sugar. So these two snippets are equivalent:

js
somePromise().catch(function (err) {
  // handle error
});

somePromise().then(null, function (err) {
  // handle error
});

However, that doesn't mean that the following two snippets are equivalent:

js
somePromise().then(function () {
  return someOtherPromise();
}).catch(function (err) {
  // handle error
});

somePromise().then(function () {
  return someOtherPromise();
}, function (err) {
  // handle error
});

If you're wondering why they're not equivalent, consider what happens if the first function throws an error:

js
somePromise().then(function () {
  throw new Error('oh noes');
}).catch(function (err) {
  // I caught your error! :)
});

somePromise().then(function () {
  throw new Error('oh noes');
}, function (err) {
  // I didn't catch your error! :(
});

As it turns out, when you use the then(resolveHandler, rejectHandler) format, the rejectHandler won't actually catch an error if it's thrown by the resolveHandler itself.

For this reason, I've made it a personal habit to never use the second argument to then(), and to always prefer catch(). The exception is when I'm writing asynchronous Mocha tests, where I might write a test to ensure that an error is thrown:

js
it('should throw an error', function () {
  return doSomethingThatThrows().then(function () {
    throw new Error('I expected an error!');
  }, function (err) {
    should.exist(err);
  });
});

Speaking of which, Mocha and Chai are a lovely combination for testing promise APIs. The pouchdb-plugin-seed project has some sample tests that can get you started.

Advanced mistake #3: promises vs promise factories

Let's say you want to execute a series of promises one after the other, in a sequence. That is, you want something like Promise.all(), but which doesn't execute the promises in parallel.

You might naïvely write something like this:

js
function executeSequentially(promises) {
  var result = Promise.resolve();
  promises.forEach(function (promise) {
    result = result.then(promise);
  });
  return result;
}

Unfortunately, this will not work the way you intended. The promises you pass in to executeSequentially() will still execute in parallel.

The reason this happens is that you don't want to operate over an array of promises at all. Per the promise spec, as soon as a promise is created, it begins executing. So what you really want is an array of promise factories:

js
function executeSequentially(promiseFactories) {
  var result = Promise.resolve();
  promiseFactories.forEach(function (promiseFactory) {
    result = result.then(promiseFactory);
  });
  return result;
}

I know what you're thinking: "Who the hell is this Java programmer, and why is he talking about factories?" A promise factory is very simple, though – it's just a function that returns a promise:

js
function myPromiseFactory() {
  return somethingThatCreatesAPromise();
}

Why does this work? It works because a promise factory doesn't create the promise until it's asked to. It works the same way as a then function – in fact, it's the same thing!

If you look at the executeSequentially() function above, and then imagine myPromiseFactory being substituted inside of result.then(...), then hopefully a light bulb will click in your brain. At that moment, you will have achieved promise enlightenment.

Advanced mistake #4: okay, what if I want the result of two promises?

Often times, one promise will depend on another, but we'll want the output of both promises. For instance:

js
getUserByName('nolan').then(function (user) {
  return getUserAccountById(user.id);
}).then(function (userAccount) {
  // dangit, I need the "user" object too!
});

Wanting to be good JavaScript developers and avoid the pyramid of doom, we might just store the user object in a higher-scoped variable:

js
var user;
getUserByName('nolan').then(function (result) {
  user = result;
  return getUserAccountById(user.id);
}).then(function (userAccount) {
  // okay, I have both the "user" and the "userAccount"
});

This works, but I personally find it a bit kludgey. My recommended strategy: just let go of your preconceptions and embrace the pyramid:

js
getUserByName('nolan').then(function (user) {
  return getUserAccountById(user.id).then(function (userAccount) {
    // okay, I have both the "user" and the "userAccount"
  });
});

...at least, temporarily. If the indentation ever becomes an issue, then you can do what JavaScript developers have been doing since time immemorial, and extract the function into a named function:

js
function onGetUserAndUserAccount(user, userAccount) {
  return doSomething(user, userAccount);
}

function onGetUser(user) {
  return getUserAccountById(user.id).then(function (userAccount) {
    return onGetUserAndUserAccount(user, userAccount);
  });
}

getUserByName('nolan')
  .then(onGetUser)
  .then(function () {
  // at this point, doSomething() is done, and we are back to indentation 0
});

As your promise code starts to get more complex, you may find yourself extracting more and more functions into named functions. I find this leads to very aesthetically-pleasing code, which might look like this:

js
putYourRightFootIn()
  .then(putYourRightFootOut)
  .then(putYourRightFootIn)  
  .then(shakeItAllAbout);

That's what promises are all about.

Advanced mistake #5: promises fall through

Finally, this is the mistake I alluded to when I introduced the promise puzzle above. This is a very esoteric use case, and it may never come up in your code, but it certainly surprised me.

What do you think this code prints out?

js
Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {
  console.log(result);
});

If you think it prints out bar, you're mistaken. It actually prints out foo!

The reason this happens is because when you pass then() a non-function (such as a promise), it actually interprets it as then(null), which causes the previous promise's result to fall through. You can test this yourself:

js
Promise.resolve('foo').then(null).then(function (result) {
  console.log(result);
});

Add as many then(null)s as you want; it will still print foo.

This actually circles back to the previous point I made about promises vs promise factories. In short, you can pass a promise directly into a then() method, but it won't do what you think it's doing. then() is supposed to take a function, so most likely you meant to do:

js
Promise.resolve('foo').then(function () {
  return Promise.resolve('bar');
}).then(function (result) {
  console.log(result);
});

This will print bar, as we expected.

So just remind yourself: always pass a function into then()!

Solving the puzzle

Now that we've learned everything there is to know about promises (or close to it!), we should be able to solve the puzzle I originally posed at the start of this post.

Here is the answer to each one, in graphical format so you can better visualize it:

Puzzle #1

js
doSomething().then(function () {
  return doSomethingElse();
}).then(finalHandler);

Answer:

text
doSomething
|-----------------|
                  doSomethingElse(undefined)
                  |------------------|
                                     finalHandler(resultOfDoSomethingElse)
                                     |------------------|

Puzzle #2

js
doSomething().then(function () {
  doSomethingElse();
}).then(finalHandler);

Answer:

text
doSomething
|-----------------|
                  doSomethingElse(undefined)
                  |------------------|
                  finalHandler(undefined)
                  |------------------|

Puzzle #3

js
doSomething().then(doSomethingElse())
  .then(finalHandler);

Answer:

text
doSomething
|-----------------|
doSomethingElse(undefined)
|---------------------------------|
                  finalHandler(resultOfDoSomething)
                  |------------------|

Puzzle #4

js
doSomething().then(doSomethingElse)
  .then(finalHandler);

Answer:

text
doSomething
|-----------------|
                  doSomethingElse(resultOfDoSomething)
                  |------------------|
                                     finalHandler(resultOfDoSomethingElse)
                                     |------------------|

If these answers still don't make sense, then I encourage you to re-read the post, or to define the doSomething() and doSomethingElse() methods and try it out yourself in your browser.

{% include alert/start.html variant="info"%}
{% markdown %}
Clarification: for these examples, I'm assuming that both doSomething() and doSomethingElse() return promises, and that those promises represent something done outside of the JavaScript event loop (e.g. IndexedDB, network, setTimeout), which is why they're shown as being concurrent when appropriate. Here's a JSBin to demonstrate.
{% endmarkdown %}
{% include alert/end.html %}

And for more advanced uses of promises, check out my promise protips cheat sheet.

Final word about promises

Promises are great. If you are still using callbacks, I strongly encourage you to switch over to promises. Your code will become smaller, more elegant, and easier to reason about.

And if you don't believe me, here's proof: a refactor of PouchDB's map/reduce module to replace callbacks with promises. The result: 290 insertions, 555 deletions.

Incidentally, the one who wrote that nasty callback code was… me! So this served as my first lesson in the raw power of promises, and I thank the other PouchDB contributors for coaching me along the way.

That being said, promises aren't perfect. It's true that they're better than callbacks, but that's a lot like saying that a punch in the gut is better than a kick in the teeth. Sure, one is preferable to the other, but if you had a choice, you'd probably avoid them both.

While superior to callbacks, promises are still difficult to understand and error-prone, as evidenced by the fact that I felt compelled to write this blog post. Novices and experts alike will frequently mess this stuff up, and really, it's not their fault. The problem is that promises, while similar to the patterns we use in synchronous code, are a decent substitute but not quite the same.

In truth, you shouldn't have to learn a bunch of arcane rules and new APIs to do things that, in the synchronous world, you can do perfectly well with familiar patterns like return, catch, throw, and for-loops. There shouldn't be two parallel systems that you have to keep straight in your head at all times.

Awaiting async/await

That's the point I made in ["Taming the asynchronous beast with ES7"]({{ site.baseurl }}/2015/03/05/taming-the-async-beast-with-es7.html), where I explored the ES7 async/await keywords, and how they integrate promises more deeply into the language. Instead of having to write pseudo-synchronous code (with a fake catch() method that's kinda like catch, but not really), ES7 will allow us to use the real try/catch/return keywords, just like we learned in CS 101.

This is a huge boon to JavaScript as a language. Because in the end, these promise anti-patterns will still keep cropping up, as long as our tools don't tell us when we're making a mistake.

To take an example from JavaScript's history, I think it's fair to say that JSLint and JSHint did a greater service to the community than JavaScript: The Good Parts, even though they effectively contain the same information. It's the difference between being told exactly the mistake you just made in your code, as opposed to reading a book where you try to understand other people's mistakes.

The beauty of ES7 async/await is that, for the most part, your mistakes will reveal themselves as syntax/compiler errors rather than subtle runtime bugs. Until then, though, it's good to have a grasp of what promises are capable of, and how to use them properly in ES5 and ES6.

So while I recognize that, like JavaScript: The Good Parts, this blog post can only have a limited impact, it's hopefully something you can point people to when you see them making these same mistakes. Because there are still way too many of us who just need to admit: "I have a problem with promises!"

{% include alert/start.html variant="info"%}
{% markdown %}
Update: it's been pointed out to me that Bluebird 3.0 will print out warnings that can prevent many of the mistakes I've identified in this post. So using Bluebird is another great option while we wait for ES7!
{% endmarkdown %}
{% include alert/end.html %}


Posts 2015 06 01 Pouchdb 3.6.0 (`docs/posts/2015-06-01-pouchdb-3.6.0.md`)


layout: post.html

title: PouchDB 3.6.0
author: Nolan Lawson


PouchDB 3.6.0 is a monthly release containing several bugfixes, most notably for "retry" replication and attachment replication. It also contains one minor new feature.

New features

  • getAttachment() now supports rev (#3871)
  • Add androidDatabaseImplementation option for the SQLite Plugin in Cordova (#3835)

Performance improvements

  • Avoid application/json when GETing remote documents with attachments. This makes the replicator and get()s on remote PouchDBs more efficient, by preferring to fetch the attachments directly in separate parallel requests. You may see a negative performance impact if you have many small attachments, in which case you should inline them instead of using attachments. However, "normal" attachment use cases should see a big improvement, because attachments are no longer sent over the wire as base64-encoded strings. This also improves support for syncing attachments in Couchbase Sync Gateway, which does not support the application/json format. (#3870)
  • Similarly, for uploading of documents with attachments, use the multipart/related format rather than application/json, because it is more efficient. This also improves Couchbase Sync Gateway support. (#3876)
  • Avoid using readAsBinaryString when readAsArrayBuffer is more direct (#3877)
  • Better bit-twiddling in the md5 checksum (#3878)

Bugfixes

  • Fix EventEmitter memory leaks in retry replication (#3858 #3699)
  • Fix retry replication when started offline (#3768)
  • Fixes for fetch() in Service Workers on Chrome 41 (#3885)
  • Fix a race condition when retry replication is immediately canceled (#3894)

Test infrastructure

  • Fuzzy replication tests (#3767)
  • Test multiple versions of Node/io.js (#3843)
  • BlackBerry Cordova tests (#3818 #3845)

Get in touch

Please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2015 08 03 Pouchdb 4.0.0 Ballast Overboard (`docs/posts/2015-08-03-pouchdb-4.0.0-ballast-overboard.md`)


layout: post.html

title: PouchDB 4.0.0 - Ballast Overboard
author: Dale Harvey


When discussing what features we should add,
we should also discuss what to remove. - possibly Joe Armstrong

I can't remember or find the exact quote, but it was something along those lines. We try to maintain backwards compatibility as much as possible with PouchDB, however sometimes we need to let go of those features that just didn't work out like planned. We have previously marked quite a few things as deprecated, and in this 4.0.0 release we have finally removed them along with our usual slew of bugfixes, improvements and new features.

Deprecations

  • Remove bluebird and use lie as only Promise polyfill - (#3839)

We previously used bluebird as our Promise polyfill in node.js because it is a fast library. However bluebird contains a lot of non-standard Promise functionality, which authors could use and then have their code break in browsers as well as iojs and future versions of node.js, which have introduced a standard Promise implementation.

PouchDB will always use the globally available Promise object where available, so if you have only used standard Promise functionality, this change will not break anything. If you require the extra functionality provided by bluebird, then you can have PouchDB use it with

js
global.Promise = require('bluebird');
  • Remove local_seq - (#4080)

This was a little-used functionality whose semantics are due to change in CouchDB 2.0.

  • Remove onChange and complete callbacks - (#4098)

These callbacks have long been replaced with the EventEmitter-style changes(), replicate() and sync() APIs, and are finally being removed. If you still have:

js
db.changes({
  onChange: changeFun,
  complete: completeFun
});

You can replace them with:

js
db.changes()
  .on('change', changeFun)
  .on('complete', completeFun);
  • Remove uptodate event - (#4100)

uptodate was an event introduced to indicate when a live replication had finished processing all current changes and was waiting on future changes. It has since been replaced by the paused event, which will do the same and additionally indicate whether the replication was paused due to an error. If you have:

js
replication.on('uptodate', doneFun);

You can replace it with:

js
replication.on('paused', doneFun);

New features

  • Return Blobs (or Buffers) in get() + allDocs() + changes() + query() with {binary: true} (#2858)
  • Allow chaining of plugin registration (#3943)
  • Allow option to raise timeouts in changes / replication (#4022)
  • Add direct edit button to website (#3747)

Bugfixes

  • Fix attachment length for stubs (#3963)
  • Reduce the number of checkpoint mismatches (#4009)
  • Fallback from checkpoint mismatch correctly (less replication start from the beginning) (#3999)
  • Don't fail replication when attachment request fails (#4021)
  • Fix check for localStorage in Safari private mode (#4070)

Coverage improvements

Get in touch

Please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2015 09 01 Pouchdb 4.0.1 Gotta Go Fast (`docs/posts/2015-09-01-pouchdb-4.0.1-gotta-go-fast.md`)


layout: post.html

title: PouchDB 4.0.1 - Gotta Go Fast
author: Nolan Lawson


I'm a big fan of classic video games. So when I hack on PouchDB, my inspiration is this guy:

{% include img.html src="sonic_waiting.gif" alt="Sonic the Hedgehog tapping his foot with visible irritation" %}

In Sonic the Hedgehog, the titular hero taps his foot impatiently any time you stop moving. It's almost as if he's egging you on, urging you to go faster. Come on, what's the holdup?

With PouchDB, performance is one of our top concerns; we don't want any holdups either. So 4.0.1 is a patch release containing some bugfixes, but mostly a lot of speedups.

This blog post has a detailed performance report as well as some community news, but first the changelog:

Changelog

Bugfixes

  • Correctly support start_key and end_key aliases (#3833 #4154)
  • Fix memory leak in PouchDB constructor (#4157 #4168 #4182)
  • Update Uglify to patch security vulnerability (#4203)
  • Fix inconsistent Date serialization in IndexedDB (#3444)
  • Fix error for invalid doc_ids (#2204)
  • Better CORS warning message (#4189)
  • Fix memory adapter in IE10 by shimming Function.prototype.name (#4216)
  • Don't fail replication if fetching uuid fails (#4094)

Code cleanup, simplification

Performance improvements

Community news

Slack channel

PouchDB now has a Slack channel. It's linked to the IRC channel, so any messages will show up in both.

Please note: this isn't a vote against IRC. While we love open-source, decentralized software (such as PouchDB itself!), many folks find it easier and more familiar to collaborate with Slack. So we're happy to support both systems.

First timers

Inspired by Kent C. Dodd's article "First Timers Only", we set up two issues designed for first-time contributors, with detailed instructions to get started with the PouchDB source code.

We're happy to report that the experiment was a success. Both issues were fixed in no time flat – less than 24 hours each! And as a result, we've been pleased to welcome Charlotte Spencer and Nicolas Brugneaux as PouchDB contributors. Welcome to the gang, Charlotte and Nicolas!

If you'd like to join PouchDB's esteemed roster of contributors (161 and counting!), then hit us up in IRC, Slack, or Github, and we'll be happy to coach you to help find an issue where you can pitch in. We also plan to continue this program by marking issues as "Help wanted" and "First-timers only".

PouchDB Server improvements

PouchDB Server and its core, express-pouchdb, have seen some notable improvements in the past month. In addition to a roughly 4% per-request performance improvement, the UI now supports uploading attachments and sports a snazzier version of the Fauxton UI:

{% include img.html src="fauxton_updated.png" alt="New Fauxton UI in PouchDB Server" %}

This update fixes lots of UI and UX bugs with the Fauxton interface, as well as allowing an easy way to test the new Mango query language, which are partially supported in PouchDB Server via pouchdb-find. You can get the latest version by running npm install pouchdb-server.

Many thanks to Nick Colley, Marten de Vries, and the formidable CouchDB Fauxton team for their help with this release!

Hoodie + PouchDB Server = ❤

In related news, Hoodie has added support for PouchDB Server, replacing CouchDB as their default backend.

This is a great vote of confidence for PouchDB Server, and shows how its combination of easy installation and near-complete API compatibility with CouchDB makes it a perfect drop-in replacement, especially during development and testing.

Performance report

We recently had a performance problem reported to us via StackOverflow. So I took it as an opportunity to thoroughly profile the codebase and experiment with some performance improvements.

Analysis of the problem

The part of the code that was causing a slowdown was related to updating documents. Per the CouchDB spec, whenever a document is updated, we need to merge its revision tree with that of the previous version. However, the merge algorithm was not very optimized, so PouchDB was spending a lot of time in JavaScript just traversing through tree structures, as well as allocating unnecessary memory along the way.

The most shocking illustration of the slowdown came when we ran performance tests using the in-memory adapter. Most of the time, PouchDB is bound by the underlying storage engine (LevelDB, IndexedDB, and WebSQL), but in this case even the in-memory adapter was chugging along much too slowly.

The fix is in

The fix involved some major overhauls to the merge algorithm:

  • Use well-known JavaScript optimization techniques, such as removing functions-within-functions and preferring for loops to forEach().
  • Where possible, cache the calculated metadata values such as the winning revision and whether or not the winner was deleted.
  • Simplify PouchDB's extend() and clone() methods, removing our long-in-the-tooth jQuery version.
  • Use less memory by avoiding extend() and clone() entirely when we don't need them.

Of course, these improvements were justified by data! The Chrome Dev Tools profiler and Node.js flame graphs are a performance junkie's best friends.

Measuring the fix

To determine how much we improved over PouchDB v4.0.0, I ran the basic-updates test with 200 iterations, meaning that it inserted 100 documents and updated them 200 times. All numbers are in milliseconds, and were recorded on a 2013 MacBook Pro running OS X Yosemite.

v4.0.0 v4.0.1 Improvement
Safari 9.0 (WebSQL) 24867 17232 30.70%
Firefox 40 (IndexedDB) 50519 26265 48.01%
Chrome 44 (IndexedDB) 49613 26667 46.25%
Chrome 44 (WebSQL) 46336 26358 43.12%
iojs 2.1.0 (LevelDB via LevelDOWN) 37593 15426 58.97%
iojs 2.1.0 (in-memory via MemDOWN) 33908 11433 66.28%

Note that these numbers don't apply to PouchDB as a whole – for instance, you can't really say "PouchDB is now 46% faster in Chrome." This test applies to a very specific part of the codebase (updating documents), so basic reads, writes, and secondary indexes will be largely unaffected.

Just for fun, I also ran the above performance test against CouchDB 1.6.1, over HTTP. Surprisingly it finished in 10956 milliseconds, which is even faster than our in-memory adapter! So clearly we've still got some work to do, and I encourage any JavaScript performance gurus to run the tests and lend a hand.

A note on PouchDB performance

PouchDB is a very fast database, and I think it holds up well compared to alternatives. It's worth noting, though, that we have a lot of factors that make performance a challenge.

First off, PouchDB is optimized for syncing. This means that we have some complicated data structures (such as revision trees) that add overhead compared to databases that don't need to retain old or conflicting revisions of documents.

Second off, PouchDB supports multiple storage engines, and they have different performance characteristics. For instance, allDocs() can be 40x slower in IndexedDB than in WebSQL because joins and cursors are fairly slow in IndexedDB, whereas in WebSQL we can use one big SQL query to do it all.

There's been some discussion in the W3C about improving IndexedDB performance, and IndexedDB v2 offers some new methods like getAll() that could boost performance. However, this has limited value for PouchDB, because 1) it only solves half the problem (IndexedDB cursors being slow) while leaving the other half unresolved (joins being slow), and 2) methods like getAll() are only implemented in recent versions of Chrome and Firefox, so it would significantly complicate the codebase to support both versions.

Thirdly, PouchDB relies heavily on these underlying storage engines. This means that, when PouchDB gives you a callback after a put(), data has actually been written to disk, and when you do an allDocs(), PouchDB is actually progressively fetching data from disk. This disk-heavy model allows PouchDB to run well on low-memory devices (such as phones), but also means that it runs better when the storage engine provides better tools, which is why WebSQL is still faster than IndexedDB for us.

What you can do about performance

Here are some tips to get the best performance out of PouchDB:

  • Don't update your documents too many times. For instance, if you're writing a text editor, don't update the document for every keystroke; set a debounce, or allow the user to explicitly save.
  • Don't continually read the entire database in and out of memory; use [pagination]({{ site.baseurl }}/2014/04/14/pagination-strategies-with-pouchdb.html) and [the changes feed]({{ site.baseurl }}/2015/02/28/efficiently-managing-ui-state-in-pouchdb.html).
  • Prefer allDocs() to either query() or pouchdb-find. Building up secondary indexes [is expensive]({{ site.baseurl }}/guides/queries.html).

If you are not using PouchDB's sync capabilities, or if you require complicated queries (such as full-text search) on a large set of data (>1000 documents), I would also advise looking into other databases. PouchDB is designed for sync and it excels at that, but if you have different needs, then you should explore other libraries.

For instance, databases like LocalForage, Dexie,
Lovefield, and YDN-DB run in more-or-less the same browsers as PouchDB, but have a much simpler data model, where updated/removed data is simply overwritten. These databases are particularly good for quick storage, lookup, and querying. If you are not using sync, you will probably also appreciate the cognitive benefit of not having to think about _rev!

If you need even more sophisticated querying capabilities and don't require a low memory footprint, then you could also look into in-memory databases such as AlaSQL and LokiJS. These databases can periodically write data to a storage engine like IndexedDB, but are primarily queried via in-memory methods, meaning they can be much faster than PouchDB because they don't have to touch disk. On the other hand, they have a high memory footprint and may drop data unless you are careful to flush the in-memory representation to disk.

Make no mistake: PouchDB is committed to being the fastest JavaScript database it can be. However, our primary goal is correctness, especially when it comes to sync. (The massive test suite should be a testament to that.) Every design decision comes with tradeoffs, and our decisions are driven by the goal of providing a free, open-source database that 1) never drops data, 2) syncs like a champ, and 3) runs flawlessly in every modern browser. If PouchDB can do all that while being fast, then that's just icing on the cake!

Get in touch

Please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2015 09 12 Pouchdb 4.0.2 A Little Extra (`docs/posts/2015-09-12-pouchdb-4.0.2-a-little-extra.md`)


layout: post.html

title: PouchDB 4.0.2 - A Little Extra
author: Nolan Lawson


4.0.2 is a quick update to sneak in some important bugfixes in anticipation of the next monthly release.

The most visible change in 4.0.2 is the long-awaited upgrade of levelup and leveldown to 1.x (#3873), which contains some big improvements to how leveldown is installed. With 4.0.2, instead of needing to recompile leveldown every time PouchDB is npm installed, it will simply be downloaded thanks to leveldown-prebuilt. So if you've ever groaned at the dreaded node-gyp rebuild console message, hop onto 4.0.2 and enjoy the faster install times!

Also noteworthy is that the in-memory adapter is now fully tested and working in IE 10+, thanks to fixes in #4216.

Another big change is that PouchDB is more stable when replicating during intermittent connectivity, thanks to a new "stateless constructor" in the HTTP adapter (#4146). Instead of kicking off HTTP requests immediately when you call new PouchDB('http://example.com/db'), the new version will wait for the first method call, such as db.info().

Changelog

Bugfixes

  • Handle errors thrown from filter functions (#3356)
  • Upgrade levelup to 1.2.1, leveldown to 1.4.1, and level-sublevel to 6.5.0 (#3873)
  • Stateless constructor for the HTTP adapter, fixes errors syncing while offline (#4146)
  • Ensure document properties are maintained, e.g. docs with keys like hasOwnProperty (#3968)
  • Validate attachment names don't start with _ (#3357)
  • Correctly respect the revs_limit of 1000, trim revision trees (#4204)
  • Allow passing in opts to destroy() (#4219)
  • Update request to 2.61.0 (#4260)
  • Use Function.prototype.name shim consistently for IE (#4216)
  • Better CORS warning (#4256)

Thank yous

A special thanks for this release goes to Julian Gruber of the Level community, without whose help we wouldn't have been able to upgrade levelup/leveldown/level-sublevel. Thanks, Julian!

Get in touch

Please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2015 10 06 Pouchdb 5.0.0 Five Years Of Pouchdb (`docs/posts/2015-10-06-pouchdb-5.0.0-five-years-of-pouchdb.md`)


layout: post.html

title: PouchDB 5.0.0 - Five years of PouchDB
author: Nolan Lawson


Fitting with the fact that PouchDB turned five years old this year, we're proud to announce PouchDB 5.0.0. This release contains the usual number of bugfixes and improvements, as well as several deprecations, since it is a major version bump.

The biggest news in terms of features is support for _bulk_get, which is a new CouchDB API that enables vastly faster replication, set to be debuted in CouchDB 2.0. This API is also shipped in the newly-released PouchDB Server 1.0.0, meaning you can test out the faster replication now.

Deprecations

  • Remove PouchDB.destroy() (#4223)

Having a destroy() function on the PouchDB object was always a little awkward, which is why we introduced the db.destroy() alternative. So now that PouchDB.destroy() is out, upgrading your code should look like this:

js
PouchDB.destroy('mydb');

becomes:

js
new PouchDB('mydb').destroy();

Keep in mind that the PouchDB object is no longer usable after you destroy() it. So you should call new PouchDB() again if you want to re-use it.

  • Remove CRUD events (#4224)

These events ('create', 'update', and 'delete') were intended to make the changes() API easier to work with, but they ended up being too ambitious and confusing, so 5.0.0 removes them.

If you are relying on these events, then you can upgrade like so:

js
db.changes({live: true})
  .on('create', createListener)
  .on('update', updateListener)
  .on('delete', deleteListener);

becomes:

js
db.changes({live: true})
  .on('change', function (change) {
    if (change.deleted) {
      deleteListener(change);
    } else if (change.changes.length === 1 &&
               /^1-/.test(change.changes[0].rev)) {
      createListener(change);
    } else {
      updateListener(change);
    }
  });

Keep in mind that this "update vs. create" test is not foolproof (what happens if a 2- document is the first version that gets synced? what happens if two conflicting 1- revisions are synced?), but it will match the old behavior.

Most of the time, your UI should be able to handle document "updates" or "creates" equivalently, so change.deleted becomes the only special case. See [Efficiently managing UI state with PouchDB]({{ site.baseurl }}/2015/02/28/efficiently-managing-ui-state-in-pouchdb.html) for some details about how to do this.

  • Remove idb-alt plugin (#4222)

This was an alternative IndexedDB adapter based on Level.js, which was experimental and probably unused by anyone except the PouchDB developers themselves.

New features

  • Implement bulkGet() (#3424)
  • Add FruitDOWN as a supported adapter (#4329)
  • Allow user to specify Firefox persistent storage (#4315)
  • Allow setting http options per request (#3941)

Bugfixes

  • Maintain canceled state in replicator better (#4276)
  • Don't mutate binary objects provided by the user (#3955 #4273)
  • Fire events on sync, ensure we only fire one active event (#4251)
  • Catch errors from WebSQL openDatabase() (#4018)
  • Ensure setup errors propagate to the API (#4358)
  • Prevent replication firing extra events (#4293)
  • Proper propagation of quota exceeded errors (#4018)
  • Remove 'use strict' from eval() (#4344)
  • Alias skip_setup to skipSetup (#3535)
  • Fix options usage within http adapter (#4313)
  • Fix Firefox FileReader usage in a web worker (#4402)

Get in touch

Please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2015 11 03 Cover All (`docs/posts/2015-11-03-cover-all.md`)


layout: post.html

title: PouchDB 5.1.0 - Cover all the things
author: Dale Harvey


It is time for the regularly scheduled PouchDB release, this month has a nice set of improvements, and we even managed to remove a little (3KB) around the edges.

Coverage

We are now very happy to be hitting 100% coverage on our tests. This currently only covers Node.js and excludes some cases that are difficult to test but it does give us confidence that our shared code is very well tested and we can ensure new code that is merged has the same level of testing.

GreenKeeper

We have had issues in the past with how to manage npm dependencies. Version ranges can lead to other people breaking PouchDB with their releases, and strict versions lead to us using stagnant packages and not being aware of upcoming breakages. During this month, the hood.ie team released GreenKeeper which gives us best of both worlds, stable versions that are always kept up to date!

New features

  • Add an option to configure withCredentials on CORS requests (#2513)
  • Allow user to disable heartbeat in HTTP requests (#4490)

Bugfixes

  • Explain http error caused by checking for _bulk_get (#4435)
  • Don't create a database just to delete it (#4467)
  • Add friendly err msg to common leveldown err (#4447)
  • Avoid empty bulkGet() (#4474)
  • Fix http url with port 80 (#247)
  • Url encode http database name (#4314)
  • Handle invalid view name consistently (#4451)
  • Remove put implementation and fix local doc handling (#1526)
  • Fix cross origin check (#4501)

Get in touch

Please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2016 01 13 Pouchdb 5.2.0 A Better Build System With Rollup (`docs/posts/2016-01-13-pouchdb-5.2.0-a-better-build-system-with-rollup.md`)


layout: post.html

title: PouchDB 5.2.0: A better build system with Rollup
author: Nolan Lawson


"Rollup! Rollup for the mystery tour!"

  • Lennon and McCartney

Besides a slew of bugfixes and the new revs_limit feature, the big news about 5.2.0 is that PouchDB is now authored with ES6 modules rather than CommonJS. Using Rollup, it's transpiled into one big index.js for Node and an index-browser.js for Webpack/Browserify.

The most immediate benefit is that PouchDB's minified/gzipped size has dropped by 3KB (6.7% smaller!) with no loss of functionality. This is thanks to Rollup's ability to remove the typical Browserify/Webpack cruft by hoisting all submodules into a single scope.

Another benefit is that PouchDB is now insulated from subtle differences between build systems like Browserify, Webpack, Babel, and Rollup itself. This is an esoteric problem for JavaScript library authors, but I think it's worth going into detail, because it affects more libraries than just PouchDB.

I'll explore that topic later on in the post, but first, here's the full changelog for 5.2.0:

Changelog

Features and improvements

  • Implement revs_limit (#2839)
  • Migrate to ES6/Rollup, build one index.js (#4652)
  • Add ability to disable timeout in changes() (#4583)

Bugfixes

  • Return conflict error when inserting an unknown rev (#4712)
  • Catch XHR errors and propagate (#4595)
  • Fix and test Webpack (#4700)
  • Improve error when user sends invalid base64 (#4208)
  • Consider changes erroring a valid result (#4677)
  • Allow bulkGet requests without an explicit rev (#4530)
  • Fix for xhr.upload detection failing (#4560)
  • Coerce options which should be numbers to number (#4578)
  • Add timeout option to replication (#4540)
  • Remove binary string conversion (#4529)
  • Remove CORS explanation (#4677)
  • Add browser sniffing and remove nonce by default (#4543)
  • Remove unneeded host.headers from ajax (#4567)
  • Fix double-encoding name in http adapter (#4514)
  • Fix and document heartbeat for replication (#4538)
  • Fallback from _bulk_get on 50x requests (#4542)
  • Remove Cordova init checks (#4756)
  • Reset changedDocs in write_docs (#4627)

PouchDB and JavaScript build systems

To explain why we're now using Rollup to bundle one big index.js for Node, we'll need to take a look at the open-source JavaScript landscape, as it stands in 2016.

I've been working on PouchDB for about two years. In that short amount of time, I've seen the frontend ecosystem largely transition from prebuilt <script>-ready JavaScript files, usually distributed via Bower, CDN, or direct download, to Node modules distributed by npm and directly consumed by build tools like Browserify, Webpack, Babel, Grunt, Gulp, etc.

A big impact of this is that library authors no longer have control over the "last mile." Whereas our code used to be plopped directly into an HTML page (or merely concatenated), nowadays it's often parsed, chopped up, and transformed before it reaches that HTML page. This process inevitably introduces bugs and inconsistencies.

For instance, PouchDB has been struggling with Webpack support for much of the past year. The number of times we've had Webpack-related bugs filed on us, and the number of times we've later regressed because we only test with Browserify, eventually compelled us to test Webpack separately. This means we're essentially testing both Browserify and Webpack as build targets.

Think this sounds extreme? Consider this example.

Example 1: requiring JSON

Let's say you added this seemingly-innocuous line of code to your JavaScript library:

js
MyLibrary.version = require('./package.json').version;

This will work in Browserify and Node, but it will break in Webpack unless the end-user adds a special json-loader to their Webpack configuration.

Does your library do this? Do you have a dependency that does this? Then congratulations, you cannot fix this for your users. All you can do is document it in your README and/or endure the inevitable bug reports.

This is not something Webpack intends to fix, since it seems to constitute a philosophical difference between Browserify and Webpack. Their argument is that end-users should have control over how libraries are built. (You can read this discussion for details.)

Example 2: Browserify transforms

Another good example of Browserify/Webpack differences is Browserify transforms. These are modules that can transform your library at build time, when it's consumed by Browserify.

Transforms are very popular in the Browserify ecosystem, but unfortunately they don't work in Webpack unless you use the transform-loader. Worse, this is another thing that end-users are expected to configure for every dependency (and every dependency of a dependency…).

So if your project is built with Browserify, it's easy to accidentally add a Browserify transform, while forgetting that it will break for Webpack users. (We nearly made that mistake ourselves, before we started testing Webpack.) So the only reasonable choice for library authors is to avoid Browserify transforms entirely.

Example 3: strict mode

To stop picking on Webpack for a bit, I also recently blogged about a bug in the "buffer" project that was caused by Babel re-interpreting the code as strict, even though it was not distributed in strict mode. This is another great example of a "last mile" bug that ended up being something the library author needed to deal with.

Of course, in this case, the library author could have just said "not my bug." But then the Babel authors might have (quite reasonably) responded, "your library should be strict; we don't support non-strict code," at which point it would become a game of hot potato. And all the while, end-users would be adding messy hacks to their build scripts to work around the issue.

Lessons from the browser world

This story should sound familiar. We've been here before. JavaScript developers have always had to struggle with a hostile and unpredictable environment, which required us to write robust code that could anticipate bugs and implementation differences. Of course, I'm talking about the browser.

Now, I won't argue that writing code for Browserify, Webpack, Babel, and friends is nearly as difficult as writing code for a half-dozen browsers, but it's definitely getting there. So in my opinion, the only reasonable solution is to start bundling code before distribution on npm, which is desirable anyway since we have so many nice tools for transpiling future JavaScript to browser-safe ES5 (such as Babel itself).

The benefits are clear: we can use Browserify transforms and require('./package.json') and whatever else we want, as long as they're only compile steps that build a bundle to be consumed by Node (and therefore Browserify/Webpack). As a bonus, this allows us to build a smaller and sleeker bundle, which is also faster for the end-user to build, since most of the build steps have already been run in advance.

I believe most JavaScript libraries are already moving in this direction, so this puts PouchDB firmly in that bandwagon. But to be clear: we don't intend to start using all the various experimental ES6/ES7 features in PouchDB. In fact, we are only transforming ES6 modules into CommonJS ; in all other ways, PouchDB is still standard ES5. The reason we're doing this is that transpiled ES5 is often cruftier than hand-written ES5. So for now, we're avoiding it.

The goal of implementing CouchDB in JavaScript has always been an ambitious one. The fact that we've done so in ~45KB is an even more impressive accomplishment. But despite the challenges, we're going to keep the bar high; we even have a CI test that fails if our minified bundle ever exceeds 50KB.

Along those same lines, we also have an open issue to add the ability to create custom builds (e.g. no WebSQL, no map/reduce, no replication, etc.). Presumably this can be made even easier with Rollup's tree-shaking capabilities, but we're still investigating this feature.

Get in touch

As always, please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2016 03 04 Pouchdb 5.3.0 Sqlite Support In Node (`docs/posts/2016-03-04-pouchdb-5.3.0-sqlite-support-in-node.md`)


layout: post.html

title: PouchDB 5.3.0: SQLite support in Node
author: Nolan Lawson


PouchDB 5.3.0 is released today, with a new feature: SQLite support in Node,
using node-websql. This means
that, in addition to the LevelDB-based LevelDOWN (or any other *DOWN adapter), you can now
also use sqlite3, via PouchDB's
WebSQL adapter.

To get started, just do:

js
require('pouchdb/extras/websql');
var db = new PouchDB('database.db', {adapter: 'websql'})

This feature will also be available in PouchDB Server, as pouchdb-server --sqlite.

Note that this doesn't change the behavior of the WebSQL adapter in the browser.
That adapter is still bundled by default in PouchDB's browser version. For more
details, see the ["extras" API documentation]({{ site.baseurl }}/api.html#extras).

Changelog

Features

  • Support WebSQL in Node via node-websql (#4915)

Bugfixes

  • Escape unicode in auth header (#4903)
  • Fix cache-busting in Edge (#4827)
  • Better error when trying to re-use a destroyed database (#4339)
  • Cache ddoc views and filters (#4867)
  • Allow creating databases with reserved words (#4898)
  • Fix missing key in ungrouped (custom) reduce functions (#4878)
  • Fix leaking of stemmed revisions (#4372)

Get in touch

As always, please file issues or tell us what you think. And as always, a big thanks to all of our new and existing contributors!


Posts 2016 04 28 Prebuilt Databases With Pouchdb (`docs/posts/2016-04-28-prebuilt-databases-with-pouchdb.md`)


layout: post.html

title: Prebuilt databases with PouchDB
author: Nolan Lawson


A common scenario when writing an app is that you have some data in a database,
and you want it to be immediately available when the app starts up. Your users
might be able to modify the data once it's loaded, but either way, they should
be able to quickly start from a predetermined state.

PouchDB offers two ways to do this:

  1. As a prebuilt SQLite file (appropriate for hybrid apps)
  2. As a "dump" file (appropriate for web apps)

In this tutorial, I'll give step-by-step instructions for both techniques.

Prebuilt SQLite files

{% include alert/start.html variant="info" %}
{% markdown %}

Note: this method only works for hybrid apps, i.e. apps built with tools like Cordova,
PhoneGap, and Ionic.

{% endmarkdown %}
{% include alert/end.html %}

The advantage of bundling a SQLite file with your app is that the database is instantly
available. There's no need to process any data to convert it to a usable format; the SQLite
file is already ready for PouchDB to use.

Creating the SQLite file

To start, we'll need some data. Let's say I have a CouchDB or a PouchDB Server that looks like this:

{% include img.html src="turtles_db.png" alt="A database with some Teenage Mutant Ninja Turtles" %}

We have four documents in this database, which I've named turtles.

Next, we need to convert this data into a SQLite file. If we started PouchDB Server like so:

bash
$ pouchdb-server --sqlite

Then we're already done. There will be a file called turtles in the directory where we ran PouchDB Server, which is already a SQLite file.
You can verify this for yourself by opening the file with the sqlite3 command:

text
$ sqlite3 turtles
SQLite version 3.8.10.2 2015-05-20 18:17:19
Enter ".help" for usage hints.
sqlite> .tables
attach-seq-store  by-sequence       local-store
attach-store      document-store    metadata-store
sqlite> select * from 'by-sequence';
1|{"name":"Donatello","weapon":"bo","bandana":"purple"}|0|donatello|1-c2f9e6a91b946fb378d53c6a4dd6eaa2
2|{"name":"Leonardo","weapon":"katana","bandana":"blue"}|0|leonardo|1-c95202ca170be0318d085b33528f7995
3|{"name":"Michelangelo","weapon":"nunchaku","bandana":"orange"}|0|michelangelo|1-52ebc5a2f8dbc0dc247cd87213e742d1
4|{"name":"Raphael","weapon":"sai","bandana":"red"}|0|raphael|1-77812e9da146bc18a37e51efb063dbac

However, if you're not using PouchDB Server with the --sqlite option, then we'll need to use a more generic approach, which
can copy any CouchDB or PouchDB database to a SQLite file.

So let's write a short Node script to do that:

js
// load PouchDB with the optional node-websql adapter
var PouchDB = require('pouchdb').plugin(require('pouchdb-adapter-node-websql'));

// set up our databases - make sure the URL is correct!
var inputDB = new PouchDB('http://localhost:5984/turtles');
var outputDB = new PouchDB('turtles.db', {adapter: 'websql'});

// replicate
inputDB.replicate.to(outputDB);

Let's call this script dump.js.

Now, if you haven't already, you'll need to install PouchDB and the pouchdb-adapter-node-websql plugin in the directory where we'll run the script:

bash
$ npm install pouchdb
$ npm install pouchdb-adapter-node-websql

Next, we'll run our script using:

bash
$ node dump.js

If this works, you will have a file called turtles.db in the directory where you ran the script. (We're adding the .db extension, which is not strictly necessary, but is a common convention for SQLite files.)

You can still inspect the database file using the sqlite3 command, as described above. Note that the tables
were built by PouchDB, though, and are not meant to be modified by hand.

Bundling the SQLite file in your app

Now we'll want to bundle the SQLite database file in our app. To do so, we'll use
SQLite Plugin 2 and follow
the instructions for creating prepopulated databases.

If you don't have it already, install the cordova tool:

bash
$ npm install cordova

Then create a new Cordova project:

bash
$ cordova create pouchdb-prebuilt-demo

Next, we'll want to copy the turtles.db into the www/ directory. This will ensure that the file
is bundled with the app.

{% include alert/start.html variant="warning" %}
{% markdown %}

PouchDB 5.x instructions: Before PouchDB 6, SQLite databases need to be
prefixed with _pouch_ in order to be located. So you would need to rename
turtles.db to _pouch_turtles.db. This is not necessary in PouchDB 6+.

{% endmarkdown %}
{% include alert/end.html %}

At this point, we'll also need to install some plugins for our Cordova app. In particular, we'll need the File Plugin
and SQLite Plugin 2. Inside of the pouchdb-prebuilt-demo directory, run:

bash
$ cordova plugin add cordova-plugin-file --save
$ cordova plugin add cordova-plugin-sqlite-2 --save

(For Ionic, you can also do ionic plugin add, and it will have the same effect.)

We'll also need PouchDB itself. For expediency, let's just [download PouchDB]({{ site.baseurl }}/download.html)
and include pouchdb.js in the app, under www/js. Then we'll add it to index.html:

html
<script src="js/pouchdb.js"></script>

Next, we'll need to write some code to copy the turtles.db file at runtime from the read-only www/ directory
to a read-write directory. This is the only cost we pay at startup when using this approach, but it allows us to
modify the database after it's been loaded.

So let's write a helper function to do this:

js
// copy a database file from www/ in the app directory to the data directory
function copyDatabaseFile(dbName) {
  var sourceFileName = cordova.file.applicationDirectory + 'www/' + dbName;
  var targetDirName = cordova.file.dataDirectory;
  // resolve the source and target filenames simultaneously
  return Promise.all([
    new Promise(function (resolve, reject) {
      resolveLocalFileSystemURL(sourceFileName, resolve, reject);
    }),
    new Promise(function (resolve, reject) {
      resolveLocalFileSystemURL(targetDirName, resolve, reject);
    })
  ]).then(function (files) {
    var sourceFile = files[0];
    var targetDir = files[1];
    // try to fetch the target file, to check if it exists
    return new Promise(function (resolve, reject) {
      targetDir.getFile(dbName, {}, resolve, reject);
    }).catch(function () {
      // target file doesn't exist already, so copy it
      return new Promise(function (resolve, reject) {
        sourceFile.copyTo(targetDir, dbName, resolve, reject);
      });
    });
  });
}

This uses the standard Cordova File APIs – such as getFile() and copyTo() – while adding Promises to make it a bit more easy to use. (Note that you will need a Promise shim if you are targeting older devices.)
It also checks whether the target file already exists, ensuring that the database is only ever copied once.

Next, we'll need to include a plugin, [pouchdb-adapter-cordova-sqlite(https://github.com/nolanlawson/pouchdb-adapter-cordova-sqlite)]. This plugin will allow us to use native SQLite rather than WebSQL (which doesn't support prebuilt databases).

To include this plugin, either npm install pouchdb-adapter-cordova-sqlite or directly download it, then link to it:

html
<script src="js/pouchdb.js"></script>
<script src="js/pouchdb.cordova-sqlite.js"></script>

{% include alert/start.html variant="info" %}
{% markdown %}

PouchDB 5.x instructions: Before PouchDB 6, the WebSQL adapter would automatically fall back to SQLite if it detected it was running in a Cordova environment and the Cordova SQLite plugin was installed. This behavior was removed because it was difficult to maintain and configure.

{% endmarkdown %}
{% include alert/end.html %}

Next, let's write the code to call this function and then print the contents of our preloaded PouchDB. Add this code to www/js/index.js, ensuring that it runs after the deviceready event:

js
copyDatabaseFile('turtles.db').then(function () {
  // using the Cordova SQLite plugin. Make sure this plugin is loaded correctly!
  var db = new PouchDB('turtles.db', {adapter: 'cordova-sqlite'});
  return db.allDocs({include_docs: true});
}).then(function (results) {
  var pre = document.createElement('pre');
  pre.innerHTML = JSON.stringify(results, null, '  ');
  document.body.appendChild(pre);
}).catch(console.log.bind(console));

Next, to run the app on Android, we can do:

bash
$ cordova platform add android
$ cordova run android

Or on iOS:

bash
$ cordova platform add ios
$ cordova run ios

If all goes well, you should see the following screens for Android and iOS:

{% include img.html src="prebuilt.png" alt="Prebuilt database app screenshot on iOS and Android" %}

The text in the background indicates that the database was preloaded and ready to go!

{% include alert/start.html variant="info" %}
{% markdown %}

The full source code for this example is available on Github.

{% endmarkdown %}
{% include alert/end.html %}

Loading from a dump file

On the web, we don't have the luxury of prebuilt SQLite files, because WebSQL
isn't supported in every browser, and even those browsers that have WebSQL don't support prebuilt files.

However, we can dump any PouchDB or CouchDB to a "dump file" using pouchdb-dump-cli.
This is less efficient than the prebuilt database (since PouchDB has to process the
file and write the data to IndexedDB/WebSQL), but it gets the job done.

Going back to our "turtles" example, let's use pouchdb-dump to dump the contents
of the database to a plaintext dump file. First, install pouchdb-dump-cli:

bash
$ npm install pouchdb-dump-cli

Then dump the database:

bash
$ pouchdb-dump http://localhost:5984/turtles > turtles.txt

Now that we have a turtles.txt file, we can use the pouchdb-load
plugin to load it into our database.

{% include alert/start.html variant="info" %}
{% markdown %}

Note: if you'd like to make this dump file even smaller, you can use short-revs
to shorten all of the revision strings to 1-A, 1-B, 2-A, etc. Be forewarned that this will
make it incompatible with the source database, though, so you won't be able to replicate it back!

{% endmarkdown %}
{% include alert/end.html %}

Assuming the dump file is hosted on our web site, we can then load it using db.load():

html
<pre id="display"></pre>
<script src="pouchdb.js"></script>
<script src="pouchdb.load.js"></script>
<script>
  var db = new PouchDB('turtles');
  // check a local document to see if we've already preloaded
  db.get('_local/preloaded').then(function (doc) {
  }).catch(function (err) {
    if (err.name !== 'not_found') {
      throw err;
    }
    // we got a 404, so the local document doesn't exist. so let's preload!
    return db.load('turtles.txt').then(function () {
      // create the local document to note that we've preloaded
      return db.put({_id: '_local/preloaded'});
    });
  }).then(function () {
    return db.allDocs({include_docs: true});
  }).then(function (res) {
    display.innerHTML = JSON.stringify(res, null, '  ');
  }).catch(console.log.bind(console));
</script>

This will fetch the turtles.txt file via AJAX and then load it into PouchDB. A special _local document
lets us know if we've already loaded it, which means we can skip this step on subsequent views.

If this works, you should see the four turtle documents printed to the screen, since they
were successfully loaded from the dump file.

{% include alert/start.html variant="info" %}
{% markdown %}

The full source code for this example is available in this Gist. And here's a live version.

{% endmarkdown %}
{% include alert/end.html %}

Wrapping up

Prebuilding a database can be a nice way to speed up the initial load of your
app or web site. However, it is an optimization, so it takes a little bit of
extra work to get it up and running.

For the average use case, if you can avoid any complicated prebuild steps and simply
replicate between CouchDB and PouchDB via the normal replicate() and sync() APIs,
you should. In fact, CouchDB's replication is slated to speed up dramatically
with v2.0 and v1.7, thanks to the new _bulk_get endpoint.

When _bulk_get lands, you may find that pouchdb-load isn't significantly
more performant than just replicating directly from CouchDB. (Prebuilt SQLite files, however, are likely
to remain much more performant.) Also note that PouchDB's replicator will automatically switch to the
faster algorithm if it detects that the server supports _bulk_get.

Hopefully this tutorial was useful for anybody struggling to get preloaded PouchDB databases into their app.
If not, feel free to get in touch anytime!


Posts 2016 06 06 Introducing Pouchdb Custom Builds (`docs/posts/2016-06-06-introducing-pouchdb-custom-builds.md`)


layout: post.html

title: Introducing PouchDB custom builds
author: Nolan Lawson


"Hold the pickles, hold the lettuce; special orders don't upset us."

– Burger King jingle

When you're building a large open-source project, the challenge often becomes less about "how do we add new features?" and more about "how do we let people opt-out of the features they don't want?".

This is especially important in JavaScript projects, where the code is usually shipped to users' browsers, meaning every byte counts. Furthermore, with modern build systems based on npm, it's painful to sit and wait for a large dependency to be npm installed, especially if you're only using a small part of it.

Large dependencies can mean a loading spinner in your user's browser, as well as an npm spinner in your own console.

PouchDB – not so pocket-sized anymore

PouchDB is an ambitious project, and as such it's grown a lot over the years. It started out as a simple way to sync CouchDB to IndexedDB (originally only supporting Firefox Nightly!), whereas now it's used in a variety of contexts:

  • In Node.js using LevelDB
  • In hybrid apps using native SQLite
  • As its own server
  • As an AJAX interface to CouchDB
  • As a layer over WebSQL, LocalStorage, or even in-memory

With each new addition, PouchDB's codebase has steadily swollen to support the new use case. And while we've been pretty good at keeping the core size down (45kB min+gz is hefty for a JavaScript library, but not enormous), users still often ask us these sorts of questions:

  • What if I don't want map/reduce?
  • What if I'm only using the IndexedDB adapter?
  • What if I'm only using PouchDB to talk to CouchDB?

If you're not using any of the dependencies, it's a shame to include them in the bundle that gets shipped to your users. We feel ya.

Also, because PouchDB has some large native dependencies (such as LevelDB), the first npm install can take a very long time, and it can even fail on certain operating systems (Windows, looking at you). This is especially bothersome when you consider that those dependencies are only required if you're using PouchDB in Node.js. (And even then, you could just use the in-memory adapter instead.)

So unfortunately, folks who are only using PouchDB in the browser still have to pay the extra cost of installing LevelDB. And ditto for SQLite, even though it's only used for the optional node-websql adapter! This is a less-than-ideal scenario.

Breaking up is hard to do

With PouchDB 5.4.0, though, we've made a huge architectural change to support users who want a slimmer or more customized PouchDB experience. Starting with this release, many of the core
modules that were previously internal to PouchDB can now be npm installed and configured separately.

By adopting this pattern, PouchDB is joining the likes of Babel, Lodash, Ember, and other projects that have been unbundled for quite some time. However, even though many JavaScripters now take this kind of unbundling for granted, it's still very difficult for library authors to implement. npm itself does not come with built-in tools to manage multi-package projects, and even npm link can quickly break down if you're trying to run a large test suite with multiple projects across multiple repos.

Luckily, the Babel authors have risen to the occasion and created Lerna, which is the tool they use to manage Babel's numerous sub-packages. Lerna allows JavaScript library authors to define multiple packages for a single repo, and then link and test them all as one. In other words, it's a tool to manage monorepos.

As it turns out, this monorepo strategy is perfect for PouchDB. Logically, we have many small interdependent components (such as the replication module, the map/reduce module, or the IndexedDB adapter), but we prefer to test them all in one large test suite, because a change in one module can have cascading effects in another module. Breaking everything up into many tiny repos, which would be tested, versioned, and developed independently, is just not practical for us.

Using Lerna, though, we can keep all of PouchDB's codebase in one repo, and still release it as separate packages. There are some tricky bits – such as how to manage source code versus bundled code, and how to measure code coverage – but in those cases Rollup remains a great tool to allow us to de-bundle and re-bundle at will. In particular, Rollup allows us to build a "debug" version of PouchDB with many more endpoints exposed, which we then test as a single index.js for measuring code coverage. Then, we are still free to ship a "production" version of PouchDB with those endpoints disabled.

With this release, we are also committing to the jsnext:main standard as promoted by Rollup, meaning that all of our packages (including pouchdb itself) now ship with both ES modules and CommonJS, and you are free to use whichever one you want. In general, the ES modules will tend to give you smaller bundle sizes (assuming you are using Rollup, SystemJS, or Webpack 2 as your bundler), whereas the CommonJS bundle is more appropriate for backwards compatibility. (Neither release makes use of ES6 features aside from ES modules, so you do not need a transpiler like Babel or Bublé).

Cool, how does it work?

PouchDB now ships with a few presets, which are a good demonstration of how the new plugin feature works. Here's pouchdb-browser (as CommonJS), which is the "browser" version of PouchDB:

js
var PouchDB = require('pouchdb-core');

PouchDB.plugin(require('pouchdb-adapter-idb'))
  .plugin(require('pouchdb-adapter-websql'))
  .plugin(require('pouchdb-adapter-http'))
  .plugin(require('pouchdb-mapreduce'))
  .plugin(require('pouchdb-replication'));

module.exports = PouchDB;

As you can see, pouchdb-browser is just an extension of pouchdb-core, with some key modules added in: the IndexedDB adapter, the WebSQL adapter, the HTTP adapter, map/reduce, and replication. If you're not using any of these features, you can just delete that plugin and enjoy the file savings.

Additionally, you'll notice that pouchdb-browser does not depend on LevelDB at all. Instead, that lives in pouchdb-node, a.k.a. the "Node" version of PouchDB:

js
var PouchDB = require('pouchdb-core');

PouchDB.plugin(require('pouchdb-adapter-leveldb'))
  .plugin(require('pouchdb-adapter-http'))
  .plugin(require('pouchdb-mapreduce'))
  .plugin(require('pouchdb-replication'));

module.exports = PouchDB;

The only difference here is that pouchdb-browser uses IndexedDB and WebSQL, whereas pouchdb-node uses LevelDB. Hence, LevelDB is not installed if you do npm install pouchdb-browser.

Also, if you'd like to use a custom adapter, those are now shipped separately. So if you want to use PouchDB in a purely in-memory mode, you can do:

js
var PouchDB = require('pouchdb-core')
  .plugin(require('pouchdb-adapter-memory'));

Or if you're only using PouchDB to talk to CouchDB:

js
var PouchDB = require('pouchdb-core')
  .plugin(require('pouchdb-adapter-http'));

This applies to all of the plugins we previously shipped in the ["extras" API]({{ site.baseurl }}/api.html#extras). Any of these can now be mixed and matched as desired:

  • pouchdb-adapter-fruitdown
  • pouchdb-adapter-localstorage
  • pouchdb-adapter-memory
  • pouchdb-adapter-node-websql

Note that the plugin order of the non-HTTP adapters matters. So for instance, if you wanted to fall back from IndexedDB to WebSQL to LocalStorage to in-memory, you would do:

js
var PouchDB = require('pouchdb-core')
  .plugin(require('pouchdb-adapter-idb'))
  .plugin(require('pouchdb-adapter-websql'))
  .plugin(require('pouchdb-adapter-localstorage'))
  .plugin(require('pouchdb-adapter-memory'));

This also opens up the possibility of more easily including custom third-party adapters, such
as worker-pouch and socket-pouch.

How many bytes can it save?

Here is a breakdown of the bundle size of various possible PouchDB configurations, using Browserify, bundle-collapser, and Uglify.

Preset Browserify + Uglify Browserify + Uglify + Gzip
pouchdb 140.06 kB 45.15 kB
pouchdb-browser 148.3 kB 47.54 kB
pouchdb-http 64.29 kB 21.54 kB
PouchDB, IndexedDB only 83.47 kB 27.79 kB
PouchDB, WebSQL only 85.15 kB 28.16 kB
PouchDB, replication only 76.28 kB 25.26 kB
PouchDB, in-memory only 350.74 kB 100.81 kB

Note that pouchdb-browser is slightly bigger than pouchdb, because pouchdb
is heavily optimized with Rollup before publishing, whereas pouchdb-browser is
not. Also, some optional plugins (like pouchdb-adapter-memory) are still quite large. In the future, we may consider using Rollup more aggressively, or trying to reduce the size of pouchdb-core. Or we might just wait
for the ecosystem to switch to tools that do automatic tree-shaking for ES modules,
such as Rollup or Webpack 2. (If you use such a tool, and it supports jsnext:main, then you should already be able to get smaller bundle sizes.)

In the meantime, though, a major benefit of PouchDB unbundling is still in reduced npm install times, as well as avoiding compatibility issues with native dependencies like leveldown and sqlite3.

Backwards compatibility?

Yes indeed, as suggested by the 5.4.0 release version, this is a fully backwards-compatible change. The pouchdb module will still act exactly as it did before, including the dist/ and extras/ APIs. Essentially, a npm install pouchdb will install the kitchen sink. We believe this is the best approach, because it provides a batteries-included experience for beginners, without having to burden them with understanding a vast plugin API.

However, the extras/ API is deprecated, and will be removed in a later version. Furthermore, the PouchDB.utils, PouchDB.ajax, and PouchDB.Errors APIs, which have been undocumented and unrecommended for years, are now removed. Plugin authors should update their code to use the new sub-packages.

That said, there is an important note about the new sub-packages: all of them are pegged to PouchDB's version number, meaning that when PouchDB cuts a new release, each of the sub-packages will also be released with the same version number. Furthermore, not all of these sub-packages follow semantic versioning, because conceptually they are internal modules, which are only exposed on npm as an implementation detail. The APIs for these packages may change at any time, so you should use exact versioning if you decide to use them directly. These packages will be clearly marked as non-semver in their READMEs.

In the new system, the packages that serve as "plugins" (as well as pouchdb itself) are considered user-facing and semver-compliant. In particular, these packages follow semver:

  • pouchdb
  • pouchdb-adapter-fruitdown
  • pouchdb-adapter-http
  • pouchdb-adapter-idb
  • pouchdb-adapter-leveldb
  • pouchdb-adapter-localstorage
  • pouchdb-adapter-memory
  • pouchdb-adapter-node-websql
  • pouchdb-adapter-websql
  • pouchdb-browser
  • pouchdb-http
  • pouchdb-mapreduce
  • pouchdb-node
  • pouchdb-replication

Other modules, such as pouchdb-ajax, pouchdb-checkpointer, and pouchdb-promise, may be freely used, but they make no guarantees as to semver. As we did with the extras/ API, all of these are used at your own risk.

Scoped packages?

We considered using scoped packages (e.g. @pouchdb/foo) as opposed to prefixed packages (e.g. pouchdb-foo) for the new monorepo architecture. In fact, we even asked the community to vote on the issue, and as of today the vote stands at 27 for scopes and 24 for prefixes.

However, the core team ultimately decided that it's too early to hop onto the scoping bandwagon. There are still plenty of thorny issues with scopes, such as the fact that npm organizations are not free for open-source yet, meaning we would have to register a single pouchdb account and share it among all the contributors. (See the voting issue for discussion about other issues with scopes.) We look forward to these issues being resolved as scoping matures, at which point we may consider moving over to scoped packages.

For the foreseeable future, though, PouchDB's sub-packages will sport names like pouchdb-browser, pouchdb-ajax, and pouchdb-replication. Even if it's a bit old-fashioned, we hope that PouchDB's users will experience fewer surprises and confusion with this more traditional naming convention. And considering the popularity of ember-*, babel-*, and react-* packages, it's safe to say PouchDB is in good company.

Build-a-bear, build-a-pouch

Since there are dozens of combinations for building your own PouchDB, we encourage the community to mix-and-match, and to publish any presets that they find useful. As of 5.4.0, there are three presets that PouchDB will officially support as first-party packages:

  • pouchdb-browser – the "browser" version of PouchDB
  • pouchdb-node – the "Node" version of PouchDB
  • pouchdb-http – PouchDB as an HTTP interface to CouchDB (sans map/reduce)

In the future, we also plan to move some packages that have previously existed in third-party repos (including pouchdb-server, pouchdb-express-router, and pouchdb-find) into the main PouchDB repository, so that they can be tested, versioned, and released at the same time as PouchDB.

So please, try out the new customizable PouchDB, and let us know what you think! And if there are any bugs, there's only one repo you need to worry about, so please direct your bugs to the PouchDB issues page. Happy unbundling!


Posts 2016 06 06 Pouchdb 5.4.0 (`docs/posts/2016-06-06-pouchdb-5.4.0.md`)


layout: post.html

title: PouchDB 5.4.0
author: Nolan Lawson


I'm happy to announce PouchDB 5.4.0, which is a minor release with a few new features, many bugfixes, and a handful of deprecations.

The big feature of 5.4.0 is the new Custom Builds API, which has its own separate introductory blog post. This post serves mostly as a changelog and summary.

One major bugfix is that PouchDB now supports Node 6. For those who have opted to stick with Node 5, it is certainly the more stable choice
(or even better: Node 4, which is an LTS release), but feel free to upgrade to Node 6 now if you're feeling adventuresome.

Another milestone is that PouchDB is now tested at 100% code coverage, and the build will actually fail if it ever drops below that. Big props to Dale Harvey for the massive amount of effort he's put into the test infrastructure to reach this point.

Another change that fellow library authors may find interesting: we are now building in Node 5 but testing in other versions. This means that our build dependencies do not need to support "troublesome" versions of Node such as 0.10 or 6; only our runtime dependencies need to.

Deprecations

We have outright removed some undocumented APIs, and added warnings for some (formerly) documented APIs. Please read carefully in case you are using any of these features.

Deprecation #1: PouchDB.utils, PouchDB.ajax, PouchDB.Errors

We have removed the PouchDB.utils, PouchDB.ajax, and PouchDB.Errors APIs. These were always
undocumented and unsupported, and we regret if anybody accidentally ended up relying on them.

If you are using these APIs you can now require them directly as so:

js
var PouchDB = require('pouchdb');
PouchDB.utils = { promise: require('pouchdb-promise') };
PouchDB.ajax = require('pouchdb-ajax');
PouchDB.Errors = require('pouchdb-errors');

Alternatively you can look at the Custom Builds API instead.

Deprecation #2: db.put(doc, id, rev)

js
db.put({}, 'myid', '2-xxx');

Instead of doing this, please put the _id and _rev on the document itself:

js
db.put({_id: 'myid', _rev: '2-xxx'});

This API was not removed, but will log a warning if you try to use it.

Deprecation #3: new PouchDB(dbName, callback)

js
var db = new PouchDB('mydb', function (err) {
  // called when PouchDB has asynchronously finished setting up
  // (or has errored)
});

Instead of using this format (which has been undocumented and unrecommended for
some time), please do:

js
var db = new PouchDB('mydb');
db.info().then(function () {
  // db is now ready
}).catch(/* ... */);

There is no need to "wait" for the PouchDB constructor to asynchronously finish
setting up, since the constructor has been made stateless. However, if you really
want to verify that the database is ready and working, you can do an info() request.

This API was not removed, but will log a warning if you try to use it.

New features

  • (#5128) - custom builds
  • (#5128) - multi-package architecture
  • (#5128) - jsnext:main support

Bugfixes

  • (#3539) - Ensure throw in user code does not effect pouch
  • (#5272) - Fix checkpointing against Cloudant/CouchDB 2.0
  • (#5225) - Guard uses of console before calling it
  • (#5238) - Fix unhandled rejection in changes
  • (#3923) - Add ability to set url as prefix
  • (#4922) - Do not call 'destroyed' twice for http instances
  • (#3609) - opts.view implies filter=_view
  • (#4444) - Don't leak event emitters
  • ([#5197](http