Skip to content

Instantly share code, notes, and snippets.

@cefn
Last active April 5, 2020 12:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cefn/ac18aeb00b4e67afa2043a8f0b8ca6fa to your computer and use it in GitHub Desktop.
Save cefn/ac18aeb00b4e67afa2043a8f0b8ca6fa to your computer and use it in GitHub Desktop.
Strategy for evaluating + unit-testing Couchdb map, reduce, update functions
const path = require("path")
const glob = require("glob")
const fs = require("fs")
const nodeEval = require("node-eval")
/** Helper object to construct a map of named design docs from a path containing js scripts.
* It populates couchdb design documents from js files targeting Spidermonkey 1.8.5
*
* File paths like
* * designdocs/map/priority.js
* * designdocs/reduce/priority.js`
* * designdocs/update/handleSave.js`
* ...are mapped to literal js strings containing function definitions...
* * lowban.views.priority.map
* * lowban.views.priority.reduce
* * lowban.updates.handleSave
*
* Compatible files can be generated through babel, targeting browserlist `Firefox >= 4`
*/
class Loader{
constructor({
scriptRoot="../designdocs",
scriptExtension=".js"
} = {}){
this.scriptRoot = path.resolve(__dirname,scriptRoot)
this.scriptExtension = scriptExtension
}
locateScript(scriptType, scriptId){
return `${this.scriptRoot}/${scriptType}/${scriptId}${this.scriptExtension}`
}
readScriptPath(scriptPath){
return fs.readFileSync(scriptPath, "utf8")
}
readScript(scriptType, scriptId) {
return this.readScriptPath(this.locateScript(scriptType, scriptId))
}
evalScript(scriptType, scriptId, context) {
const scriptPath = this.locateScript(scriptType, scriptId)
const scriptSource = this.readScriptPath(scriptPath)
return nodeEval(scriptSource, scriptPath, context)
}
getScriptIds(scriptType) {
return glob.sync(`${this.scriptRoot}/${scriptType}/*` + this.scriptExtension).map(filePath => path.basename(filePath, this.scriptExtension))
}
loadDesignDocs() {
const mapIds = this.getScriptIds("map")
const reduceIds = this.getScriptIds("reduce")
const updateIds = this.getScriptIds("update")
const orphanedReducers = reduceIds.filter( id => !mapIds.includes(id))
if(orphanedReducers.length > 0){
throw `No map function for ${orphanedReducers}`
}
const views = mapIds.length ? {views:{
//merge each view's map (and optional reduce)
...Object.assign(...mapIds.map( viewId => ({
[viewId]: {
map:this.readScript("map", viewId), //view map is required
...reduceIds.includes(viewId) //view reduce is optional
? { reduce:this.readScript("reduce", viewId) }
: null
}
})))
}} : null
const updates = updateIds.length ? { updates:{
//merge updates
...Object.assign(...updateIds.map( updateId => ({
[updateId]:this.readScript("update", updateId)
})))
}}: null
return [
{
_id:"_design/lowban",
...views,
...updates,
}
]
}
}
module.exports = {
Loader
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment