131 lines
3.0 KiB
JavaScript
131 lines
3.0 KiB
JavaScript
const TOKEN_MAP = {
|
|
EQUALS: "===",
|
|
LT: "<",
|
|
LTE: "<=",
|
|
MT: ">",
|
|
MTE: ">=",
|
|
CONTAINS: "includes",
|
|
AND: "&&",
|
|
OR: "||",
|
|
}
|
|
|
|
const GROUP_PROPERTY = {
|
|
group: {
|
|
type: "string",
|
|
},
|
|
}
|
|
|
|
const FIELD_PROPERTY = {
|
|
field: {
|
|
type: "string",
|
|
},
|
|
}
|
|
|
|
const SCHEMA_MAP = {
|
|
stats: {
|
|
sum: {
|
|
type: "number",
|
|
},
|
|
min: {
|
|
type: "number",
|
|
},
|
|
max: {
|
|
type: "number",
|
|
},
|
|
count: {
|
|
type: "number",
|
|
},
|
|
sumsqr: {
|
|
type: "number",
|
|
},
|
|
avg: {
|
|
type: "number",
|
|
},
|
|
},
|
|
}
|
|
|
|
/**
|
|
* Iterates through the array of filters to create a JS
|
|
* expression that gets used in a CouchDB view.
|
|
* @param {Array} filters - an array of filter objects
|
|
* @returns {String} JS Expression
|
|
*/
|
|
function parseFilterExpression(filters) {
|
|
const expression = []
|
|
|
|
for (let filter of filters) {
|
|
if (filter.conjunction) expression.push(TOKEN_MAP[filter.conjunction])
|
|
|
|
if (filter.condition === "CONTAINS") {
|
|
expression.push(
|
|
`doc["${filter.key}"].${TOKEN_MAP[filter.condition]}("${filter.value}")`
|
|
)
|
|
} else {
|
|
expression.push(
|
|
`doc["${filter.key}"] ${TOKEN_MAP[filter.condition]} "${filter.value}"`
|
|
)
|
|
}
|
|
}
|
|
|
|
return expression.join(" ")
|
|
}
|
|
|
|
/**
|
|
* Returns a CouchDB compliant emit() expression that is used to emit the
|
|
* correct key/value pairs for custom views.
|
|
* @param {String?} field - field to use for calculations, if any
|
|
* @param {String?} groupBy - field to group calculation results on, if any
|
|
*/
|
|
function parseEmitExpression(field, groupBy) {
|
|
if (field) return `emit(doc["${groupBy || "_id"}"], doc["${field}"]);`
|
|
return `emit(doc._id);`
|
|
}
|
|
|
|
/**
|
|
* Return a fully parsed CouchDB compliant view definition
|
|
* that will be stored in the design document in the database.
|
|
*
|
|
* @param {Object} viewDefinition - the JSON definition for a custom view.
|
|
* field: field that calculations will be performed on
|
|
* modelId: modelId of the model this view was created from
|
|
* groupBy: field that calculations will be grouped by. Field must be present for this to be useful
|
|
* filters: Array of filter objects containing predicates that are parsed into a JS expression
|
|
* calculation: an optional calculation to be performed over the view data.
|
|
*/
|
|
function viewTemplate({ field, modelId, groupBy, filters = [], calculation }) {
|
|
const parsedFilters = parseFilterExpression(filters)
|
|
const filterExpression = parsedFilters ? `&& ${parsedFilters}` : ""
|
|
|
|
const emitExpression = parseEmitExpression(field, groupBy)
|
|
|
|
const reduction = field ? { reduce: "_stats" } : {}
|
|
|
|
let schema = null
|
|
|
|
if (calculation) {
|
|
schema = {
|
|
...(groupBy ? GROUP_PROPERTY : FIELD_PROPERTY),
|
|
...SCHEMA_MAP[calculation],
|
|
}
|
|
}
|
|
|
|
return {
|
|
meta: {
|
|
field,
|
|
modelId,
|
|
groupBy,
|
|
filters,
|
|
schema,
|
|
calculation,
|
|
},
|
|
map: `function (doc) {
|
|
if (doc.modelId === "${modelId}" ${filterExpression}) {
|
|
${emitExpression}
|
|
}
|
|
}`,
|
|
...reduction,
|
|
}
|
|
}
|
|
|
|
module.exports = viewTemplate
|