UNPKG

pouchdb-mapreduce

Version:

PouchDB's map/reduce query API as a plugin.

148 lines (131 loc) 3.87 kB
import { guardedConsole, scopeEval } from 'pouchdb-utils'; import { BuiltInError, NotFoundError } from 'pouchdb-mapreduce-utils'; import createAbstractMapReduce from 'pouchdb-abstract-mapreduce'; function createBuiltInError(name) { var message = 'builtin ' + name + ' function requires map values to be numbers' + ' or number arrays'; return new BuiltInError(message); } function sum(values) { var result = 0; for (var i = 0, len = values.length; i < len; i++) { var num = values[i]; if (typeof num !== 'number') { if (Array.isArray(num)) { // lists of numbers are also allowed, sum them separately result = typeof result === 'number' ? [result] : result; for (var j = 0, jLen = num.length; j < jLen; j++) { var jNum = num[j]; if (typeof jNum !== 'number') { throw createBuiltInError('_sum'); } else if (typeof result[j] === 'undefined') { result.push(jNum); } else { result[j] += jNum; } } } else { // not array/number throw createBuiltInError('_sum'); } } else if (typeof result === 'number') { result += num; } else { // add number to array result[0] += num; } } return result; } var log = guardedConsole.bind(null, 'log'); var isArray = Array.isArray; var toJSON = JSON.parse; function evalFunctionWithEval(func, emit) { return scopeEval( "return (" + func.replace(/;\s*$/, "") + ");", { emit, sum, log, isArray, toJSON } ); } var builtInReduce = { _sum: function (keys, values) { return sum(values); }, _count: function (keys, values) { return values.length; }, _stats: function (keys, values) { // no need to implement rereduce=true, because Pouch // will never call it function sumsqr(values) { var _sumsqr = 0; for (var i = 0, len = values.length; i < len; i++) { var num = values[i]; _sumsqr += (num * num); } return _sumsqr; } return { sum : sum(values), min : Math.min.apply(null, values), max : Math.max.apply(null, values), count : values.length, sumsqr : sumsqr(values) }; } }; function getBuiltIn(reduceFunString) { if (/^_sum/.test(reduceFunString)) { return builtInReduce._sum; } else if (/^_count/.test(reduceFunString)) { return builtInReduce._count; } else if (/^_stats/.test(reduceFunString)) { return builtInReduce._stats; } else if (/^_/.test(reduceFunString)) { throw new Error(reduceFunString + ' is not a supported reduce function.'); } } function mapper(mapFun, emit) { // for temp_views one can use emit(doc, emit), see #38 if (typeof mapFun === "function" && mapFun.length === 2) { var origMap = mapFun; return function (doc) { return origMap(doc, emit); }; } else { return evalFunctionWithEval(mapFun.toString(), emit); } } function reducer(reduceFun) { var reduceFunString = reduceFun.toString(); var builtIn = getBuiltIn(reduceFunString); if (builtIn) { return builtIn; } else { return evalFunctionWithEval(reduceFunString); } } function ddocValidator(ddoc, viewName) { var fun = ddoc.views && ddoc.views[viewName]; if (typeof fun.map !== 'string') { throw new NotFoundError('ddoc ' + ddoc._id + ' has no string view named ' + viewName + ', instead found object of type: ' + typeof fun.map); } } var localDocName = 'mrviews'; var abstract = createAbstractMapReduce(localDocName, mapper, reducer, ddocValidator); function query(fun, opts, callback) { return abstract.query.call(this, fun, opts, callback); } function viewCleanup(callback) { return abstract.viewCleanup.call(this, callback); } var index = { query, viewCleanup }; export default index;