@decaf-ts/for-couchdb
Version:
decaf-ts couchdb wrappers
288 lines • 11.8 kB
JavaScript
;
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