UNPKG

@decaf-ts/for-couchdb

Version:
288 lines 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateViewName = generateViewName; exports.generateDesignDocName = generateDesignDocName; exports.findViewMetadata = findViewMetadata; exports.collectViewMetadata = collectViewMetadata; exports.generateViews = generateViews; exports.generateViewIndexes = generateViewIndexes; exports.viewIndexDefinition = viewIndexDefinition; const core_1 = require("@decaf-ts/core"); const db_decorators_1 = require("@decaf-ts/db-decorators"); const decoration_1 = require("@decaf-ts/decoration"); const decorator_validation_1 = require("@decaf-ts/decorator-validation"); const constants_js_1 = require("./../constants.cjs"); const utils_js_1 = require("./../utils.cjs"); const core_2 = require("@decaf-ts/core"); function toJsLiteral(value) { if (value instanceof Date) return JSON.stringify(value.toISOString()); if (value instanceof RegExp) return `new RegExp(${JSON.stringify(value.source)}, ${JSON.stringify(value.flags)})`; return JSON.stringify(value); } function conditionToJs(condition, docVar) { const { attr1, operator, comparison } = condition; if (attr1 instanceof core_1.Condition) { const left = conditionToJs(attr1, docVar); if (operator === core_2.Operator.NOT) return `!(${left})`; const right = conditionToJs(comparison, docVar); if (operator === core_1.GroupOperator.AND) return `(${left} && ${right})`; if (operator === core_1.GroupOperator.OR) return `(${left} || ${right})`; return left; } const attrExpr = `${docVar}[${JSON.stringify(attr1)}]`; switch (operator) { case core_2.Operator.EQUAL: return `${attrExpr} === ${toJsLiteral(comparison)}`; case core_2.Operator.DIFFERENT: return `${attrExpr} !== ${toJsLiteral(comparison)}`; case core_2.Operator.BIGGER: return `${attrExpr} > ${toJsLiteral(comparison)}`; case core_2.Operator.BIGGER_EQ: return `${attrExpr} >= ${toJsLiteral(comparison)}`; case core_2.Operator.SMALLER: return `${attrExpr} < ${toJsLiteral(comparison)}`; case core_2.Operator.SMALLER_EQ: return `${attrExpr} <= ${toJsLiteral(comparison)}`; case core_2.Operator.IN: return `(${toJsLiteral(comparison)}).indexOf(${attrExpr}) !== -1`; case core_2.Operator.REGEXP: { const regex = comparison instanceof RegExp ? toJsLiteral(comparison) : `new RegExp(${JSON.stringify(comparison)})`; return `${regex}.test(${attrExpr})`; } case core_2.Operator.NOT: return `!(${attrExpr} === ${toJsLiteral(comparison)})`; default: return "true"; } } function buildAuthGuard(auth, docVar = "doc") { if (!auth) return undefined; if (typeof auth === "string") return auth; const field = auth.field || "roles"; const roles = auth.roles || []; if (!roles.length) return auth.expression; const listExpr = `${docVar}[${JSON.stringify(field)}] || []`; if ((auth.mode || "any") === "all") { return roles .map((r) => `${listExpr}.indexOf(${JSON.stringify(r)}) !== -1`) .join(" && "); } return roles .map((r) => `${listExpr}.indexOf(${JSON.stringify(r)}) !== -1`) .join(" || "); } function normalizeKey(key, attr, compositions) { if (!key) return attr ? [attr, ...(compositions || [])] : compositions || []; return Array.isArray(key) ? key : [key]; } function buildEmitKey(key, attr, compositions) { const keys = normalizeKey(key, attr, compositions); if (keys.length === 1) return `doc[${JSON.stringify(keys[0])}]`; return `[${keys.map((k) => `doc[${JSON.stringify(k)}]`).join(", ")}]`; } function buildEmitValue(value, attr) { if (value === "doc") return "doc"; if (!value) return `doc[${JSON.stringify(attr)}]`; if (Array.isArray(value)) return `[${value.map((v) => `doc[${JSON.stringify(v)}]`).join(", ")}]`; if (typeof value === "string") return `doc[${JSON.stringify(value)}]`; return toJsLiteral(value); } function defaultReduce(kind) { switch (kind) { case "count": case "groupBy": case "distinct": return "_count"; case "sum": return "_sum"; case "max": return `function (keys, values, rereduce) {\n var maxVal = null;\n for (var i = 0; i < values.length; i++) {\n var val = values[i];\n if (rereduce && val && typeof val === 'object' && 'value' in val) val = val.value;\n if (maxVal === null || val > maxVal) maxVal = val;\n }\n return maxVal;\n}`; case "min": return `function (keys, values, rereduce) {\n var minVal = null;\n for (var i = 0; i < values.length; i++) {\n var val = values[i];\n if (rereduce && val && typeof val === 'object' && 'value' in val) val = val.value;\n if (minVal === null || val < minVal) minVal = val;\n }\n return minVal;\n}`; default: return undefined; } } function buildMapFunction(tableName, attr, meta) { if (meta.map) { if (typeof meta.map === "function") return meta.map.toString(); return meta.map; } const guards = []; guards.push(`doc[${JSON.stringify(constants_js_1.CouchDBKeys.TABLE)}] === ${JSON.stringify(tableName)}`); if (meta.condition) { if (meta.condition instanceof core_1.Condition) { guards.push(conditionToJs(meta.condition, "doc")); } else if (typeof meta.condition === "string") { guards.push(meta.condition); } else { const resolved = core_1.Condition.from(meta.condition); guards.push(conditionToJs(resolved, "doc")); } } if (meta.kind === "count" && meta.value !== undefined) { guards.push(`doc[${JSON.stringify(attr)}] === ${toJsLiteral(meta.value)}`); } const authGuard = buildAuthGuard(meta.auth, "doc"); if (authGuard) guards.push(authGuard); const guard = guards.length ? guards.join(" && ") : "true"; const emitKey = buildEmitKey(meta.key, attr, meta.compositions); const emitValue = buildEmitValue(meta.value ?? (meta.returnDocs && meta.kind !== "sum" ? "doc" : undefined), attr); return `function (doc) {\n if (!(${guard})) return;\n emit(${emitKey}, ${emitValue});\n}`; } function normalizeViewMetadata(kind, attr, meta, tableName) { const map = buildMapFunction(tableName, attr, meta); const reduce = meta.reduce ? typeof meta.reduce === "function" ? meta.reduce.toString() : meta.reduce : defaultReduce(kind); return { map, reduce }; } function generateViewName(tableName, attr, kind, meta, separator = db_decorators_1.DefaultSeparator) { if (meta.name) return meta.name; const parts = [tableName, attr, kind, "view"]; return parts.join(separator).replace(/\s+/g, "_"); } function generateDesignDocName(tableName, viewName, separator = db_decorators_1.DefaultSeparator) { return [tableName, viewName, constants_js_1.CouchDBKeys.DDOC].join(separator); } function findViewMetadata(model, kind, attribute) { const viewKeyMap = { view: core_2.Operator.VIEW, groupBy: core_2.Operator.GROUP_BY, count: core_2.Operator.COUNT, sum: core_2.Operator.SUM, max: core_2.Operator.MAX, min: core_2.Operator.MIN, distinct: core_2.Operator.DISTINCT, }; const key = viewKeyMap[kind]; if (!key) return []; const meta = decoration_1.Metadata.get(model, key) || {}; return Object.entries(meta).flatMap(([attr, entries]) => { if (!entries || typeof entries !== "object") return []; return Object.values(entries) .map((entry) => ({ ...entry, kind, attribute: entry.attribute || attr, })) .filter((entry) => !attribute || entry.attribute === attribute); }); } function collectViewMetadata(model, key, kind) { const meta = decoration_1.Metadata.get(model, key) || {}; return Object.entries(meta).flatMap(([attr, entries]) => { if (!entries || typeof entries !== "object") return []; return Object.values(entries).map((entry) => { const value = entry; return { ...value, kind, attribute: value.attribute || attr, }; }); }); } function generateViews(models) { const viewDocs = {}; const viewKeys = [ [constants_js_1.CouchDBKeys.VIEW, "view"], [core_2.Operator.GROUP_BY, "groupBy"], [core_2.Operator.COUNT, "count"], [core_2.Operator.SUM, "sum"], [core_2.Operator.MAX, "max"], [core_2.Operator.MIN, "min"], [core_2.Operator.DISTINCT, "distinct"], ]; models.forEach((m) => { const tableName = decorator_validation_1.Model.tableName(m); const metas = viewKeys.flatMap(([key, kind]) => collectViewMetadata(m, key, kind)); console.log("generateViews", tableName, metas.length); metas.forEach((meta) => { const attr = meta.attribute; const viewName = generateViewName(tableName, attr, meta.kind, meta); const ddocName = meta.ddoc || generateDesignDocName(tableName, viewName); const ddocId = `_design/${ddocName}`; const viewDef = normalizeViewMetadata(meta.kind, attr, meta, tableName); if (!viewDocs[ddocId]) { viewDocs[ddocId] = { _id: ddocId, language: "javascript", views: {}, }; } viewDocs[ddocId].views[viewName] = viewDef; }); }); return Object.values(viewDocs); } function generateViewIndexes(models) { const indexes = {}; const viewKeys = [ [constants_js_1.CouchDBKeys.VIEW, "view"], [core_2.Operator.GROUP_BY, "groupBy"], [core_2.Operator.COUNT, "count"], [core_2.Operator.SUM, "sum"], [core_2.Operator.MAX, "max"], [core_2.Operator.MIN, "min"], [core_2.Operator.DISTINCT, "distinct"], ]; models.forEach((m) => { const tableName = decorator_validation_1.Model.tableName(m); const metas = viewKeys.flatMap(([key, kind]) => collectViewMetadata(m, key, kind)); metas.forEach((meta) => { const key = normalizeKey(meta.key, meta.attribute, meta.compositions); if (!key.length) return; const attribute = key[0]; const compositions = key.slice(1); const directions = meta.directions || [ core_1.OrderDirection.ASC, core_1.OrderDirection.DSC, ]; const baseIndex = (0, utils_js_1.generateIndexDoc)(attribute, tableName, compositions); indexes[baseIndex.name || ""] = baseIndex; directions.forEach((direction) => { const dirIndex = (0, utils_js_1.generateIndexDoc)(attribute, tableName, compositions, direction); indexes[dirIndex.name || ""] = dirIndex; }); }); }); return Object.values(indexes); } function viewIndexDefinition(meta) { const key = normalizeKey(meta.key, meta.attribute); return { attribute: key[0], compositions: key.slice(1).concat(meta.compositions || []), directions: meta.directions, }; } //# sourceMappingURL=generator.js.map //# sourceMappingURL=generator.cjs.map