January 2, 2023 · couchdb, database and javascript

couchilla is a bundler for packing design documents for CouchDB.

In CouchDB, design documents are special database entries that contain JavaScript functions, such as views and updates. These functions, executed on demand, generate secondary indexes, often termed MapReduce views.

Although CouchDB supports JavaScript and Erlang, design functions themselves are language-independent, which is why CouchDB doesn't include a dedicated tool for creating design documents.

JavaScript support in CouchDB relies on the Mozilla SpiderMonkey engine, which imposes specific module dependency rules and limitations on design function development.

couchilla addresses this by providing a convenient way to bundle design documents with CommonJS support. It aggregates view and filter functions from a JavaScript directory and produces a design document in JSON format.

Directory structure

Here's an example of a basic design document directory structure:

.
├── filters
│   └── quu.js
├── views
│   ├── foo.map.js
│   └── bar.reduce.js
└── validate_doc_update.js

Examples

Map functions

Emit key/value pairs to store them in a view.

views/foo.map.js

export default doc => emit(doc._id, 42)

Reduce functions

Take sum of mapped values:

views/foo.reduce.js

export default (keys, values, rereduce) => {
  if (rereduce) {
    return sum(values)
  } else {
    return values.length
  }
}

Filter functions

Filter by field:

filters/foo.js

export default (doc, req) => {
  if (doc && doc.title && doc.title.startsWith('C')) {
    return true
  }
  return false
}

Validate document update functions

Log incoming requests and respond with forbidden:

export default (newDoc, oldDoc, userCtx, secObj) => {
  log(newDoc)
  log(oldDoc)
  log(userCtx)
  log(secObj)
  throw { forbidden: 'not able now!' }
}

Builtin reduce functions

You can opt to use Erlang native functions using the builtin annotation. For example the sum function above can be rewritten using _sum.

views/foo.reduce.js

/* builtin _sum */

During compilation this will be replaced with a call to the builtin _sum function.

Requiring other modules

All code, including require() statements, must be enclosed within the exported default function.

views/gamma.map.js

export default doc => {
  const gamma = require('gamma')

  emit(doc._id, gamma(doc.value))
}