CouchDB design document bundler

  • January 2, 2023
  • couchdb, javascript and tool

couchilla is a bundler for packing design documents for CouchDB with CommonJS support.

Overview

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

JavaScript support in CouchDB is based on the Mozilla SpiderMonkey engine (and, starting with version 3.4.1, also QuickJS). However, because design functions are language-independent, CouchDB does not include a dedicated tool for creating them.

couchilla creates design documents with CommonJS support. It reads view and filter functions from JavaScript files in a directory and generates a design document in JSON.


Directory structure

Here's an example directory structure:

.
├── filters
│   └── quu.js
├── views
│   ├── foo.map.js
│   └── bar.reduce.js
└── validate_doc_update.js
  • View functions reside in the views directory. Files with .map.js (or simply .js) are converted into map functions.
    • Reduce functions are defined in files with .reduce.js extensions.
  • Filter functions belong in the filters directory.

View functions

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
  }
}

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.

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!' }
}

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))
}