Building better CouchDB design functions

I wanted to use npm packages as dependencies for CouchDB design functions in order to re-use code between projects. I also wanted to be able to test my code in Node.js before injecting it into CouchDB. This sometimes happens for map or filter functions, but really validate_doc_update is the one that requires much datalove.

I’ve been able to do this for web projects using browserify and webpack for a while, and I realized I could use the same process for CouchDB.

In order to simplify writing validation functions, I started by writing a Domain Specific Language (DSL) for my needs (windy-moon), which contains the usual suspects: required, unchanged, etc.

I’m then using those in another project. In order to get the build steps to work, I first created a main.js file that lists the functions the design document will need. This is mostly so that I can keep the design document short; the main.js file will be bundled will all the dependencies:

// validation function
exports.validate_user_doc = function(doc,oldDoc,userCtx,secObj){ .. }

// map function
exports.provisioning = function(doc) { ... }

while the design document’s functions will simply do:

(function(){
  return require('views/lib/main').validate_user_doc.apply(this,arguments);
})

for example. (CouchDB’s query server caches the modules used by require, so this works efficiently.)

The code is then bundled using webpack. The webpack.config.js file instructs webpack to bundle main.js and all its dependencies into main.bundle.js.

The last step was to build the design document; the bundled code goes into views.lib.main, which is then accessed using require('views/lib/main') inside the actual functions executed by CouchDB’s query server.

var fun = function(t) {
  return "(function(){\n  return " + t + ".apply(this,arguments);\n})"
}

var lib_main = fs.readFileSync(path.join(__dirname, 'main.bundle.js'), 'utf-8')

var ddoc = {
  _id: "_design/" + id,
  language: 'javascript',
  views: {
    lib: {
      main: lib_main
    }
  },
  validate_doc_update: fun("require('views/lib/main').validate_user_doc"),
  filters: {
    provisioning: fun("require('views/lib/main').provisioning")
  }
};

The webpack configuration file allows to build multiple bundles using different names:

entry: {
  main: './main.js'
},
output: {
  filename: '[name].bundle.js',
  library: '[name]'
  // ...
}

Here the only entry I specified has main as its name, so it will be bundled as main.bundle.js.

The design document can then be uploaded to CouchDB, while Node.js is used locally to run tests on the functions exported by main.js.

Written on August 9, 2016