contexture-mongo
Version:
Mongo Provider for Contexture
225 lines (224 loc) • 8.44 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var facet_exports = {};
__export(facet_exports, {
default: () => facet_default
});
module.exports = __toCommonJS(facet_exports);
var import_futil = __toESM(require("futil"));
var import_fp = __toESM(require("lodash/fp.js"));
var import_mongodb = require("mongodb");
let projectStageFromLabelFields = (node) => ({
$project: {
count: 1,
...import_futil.default.arrayToObject(
(fieldName) => `label.${fieldName}`,
import_fp.default.constant(1)
)(import_fp.default.flow(import_fp.default.get("label.fields"), import_fp.default.castArray)(node))
}
});
let sortAndLimitIfSearching = (shouldSortAndLimit, limit) => shouldSortAndLimit ? [{ $sort: { count: -1 } }, limit !== 0 && { $limit: limit || 10 }] : [];
let sortAndLimitIfNotSearching = (should, limit) => sortAndLimitIfSearching(!should, limit);
let getSearchableKeysList = import_fp.default.flow(
import_fp.default.getOr("_nodeFieldStr", "label.fields"),
import_fp.default.castArray,
import_fp.default.map((label) => label === "_nodeFieldStr" ? label : `label.${label}`)
);
let getMatchesForMultipleKeywords = (list, filterWords) => ({
$and: import_fp.default.map(
(option) => ({
$or: import_fp.default.map(
(key) => ({
[key]: {
$regex: option,
$options: "i"
}
}),
list
)
}),
filterWords
)
});
let setMatchOperators = (node) => {
const list = getSearchableKeysList(node);
const filterWords = import_fp.default.compact(import_fp.default.split(/\s+/, node.optionsFilter));
return list.length > 1 ? getMatchesForMultipleKeywords(list, filterWords) : {
$and: import_fp.default.map(
(option) => ({
[import_fp.default.first(list)]: {
$options: "i",
$regex: option
}
}),
filterWords
)
};
};
let mapKeywordFilters = (node) => [
// Cast field we're searching on to string to enable regex matches on it.
{ $addFields: { _nodeFieldStr: { $toString: "$_id" } } },
{ $match: setMatchOperators(node) }
];
let lookupLabel = (node) => import_fp.default.get("label", node) ? [
{
$lookup: {
from: import_fp.default.get("label.collection", node),
as: "label",
localField: "_id",
foreignField: import_fp.default.get("label.foreignField", node)
}
},
{
$unwind: {
path: "$label",
preserveNullAndEmptyArrays: true
}
}
] : [];
let facetValueLabel = (node, label) => {
if (!node.label) {
return {};
}
if (!node.label.fields || import_fp.default.isArray(node.label.fields)) {
return { label };
}
return {
label: import_fp.default.flow(import_fp.default.values, import_fp.default.first)(label)
};
};
let unwindPropOrField = (node) => import_fp.default.map(
(field) => ({ $unwind: `$${field}` }),
import_fp.default.castArray(node.unwind || node.field)
);
let runSearch = ({ options, getSchema, getProvider }, node) => (filters, aggs) => getProvider(node).runSearch(
options,
node,
getSchema(node.schema),
filters,
aggs
);
var facet_default = {
hasValue: import_fp.default.get("values.length"),
filter: (node) => ({
[node.field]: {
[node.mode === "exclude" ? "$nin" : "$in"]: node.isMongoId ? import_fp.default.map((v) => new import_mongodb.ObjectId(v), node.values) : node.values
}
}),
async result(node, search, schema, config = {}) {
let valueIds = import_fp.default.get("values", node);
let optionsFilterAggs = import_fp.default.compact([
...lookupLabel(node),
import_fp.default.get("label.fields", node) && projectStageFromLabelFields(node),
...node.optionsFilter ? mapKeywordFilters(node) : []
]);
let results = await Promise.all([
search(
import_fp.default.compact([
// Unwind allows supporting array and non array fields - for non arrays, it will treat as an array with 1 value
// https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/#non-array-field-path
...unwindPropOrField(node),
{ $group: { _id: `$${node.field}`, count: { $sum: 1 } } },
...sortAndLimitIfNotSearching(node.optionsFilter, node.size),
...optionsFilterAggs,
...sortAndLimitIfSearching(node.optionsFilter, node.size)
])
),
search([
...unwindPropOrField(node),
{ $group: { _id: `$${node.field}` } },
...optionsFilterAggs,
{ $group: { _id: 1, count: { $sum: 1 } } }
])
]).then(([options, cardinality]) => ({
cardinality: import_fp.default.get("0.count", cardinality),
options: import_fp.default.map(
({ _id, label, count }) => import_futil.default.omitNil({
name: _id,
count,
...facetValueLabel(node, label)
}),
options
)
}));
let lostIds = import_fp.default.difference(
valueIds,
import_fp.default.map(
({ name }) => import_futil.default.when(node.isMongoId, import_fp.default.toString, name),
results.options
)
);
let maybeMapObjectId = import_futil.default.when(
node.isMongoId,
import_fp.default.map((x) => new import_mongodb.ObjectId(x))
);
if (!import_fp.default.isEmpty(lostIds)) {
let lostOptions = await search(
import_fp.default.compact([
...unwindPropOrField(node),
{
$match: { [node.field]: { $in: maybeMapObjectId(lostIds) } }
},
{ $group: { _id: `$${node.field}`, count: { $sum: 1 } } },
...sortAndLimitIfNotSearching(node.optionsFilter, node.size),
...optionsFilterAggs
])
);
let zeroCountIds = import_fp.default.difference(
//when values are numeric values, stringify missedValues to avoid the bug.
import_fp.default.map(import_futil.default.unless(import_fp.default.isBoolean, import_fp.default.toString), lostIds),
import_fp.default.map(({ _id }) => import_fp.default.toString(_id), lostOptions)
);
let zeroCountOptions = [];
if (!import_fp.default.isEmpty(zeroCountIds)) {
zeroCountOptions = import_fp.default.map(
({ _id, label }) => ({ _id, label, count: 0 }),
await runSearch(config, node)(
{
[node.field]: { $in: maybeMapObjectId(zeroCountIds) }
},
import_fp.default.compact([
{ $group: { _id: `$${node.field}` } },
...lookupLabel(node),
import_fp.default.get("label.fields", node) && projectStageFromLabelFields(node)
])
)
);
}
let totalMissedOptions = import_fp.default.map(({ _id, label, count }) => ({
name: _id,
count,
...facetValueLabel(node, label)
}))(import_fp.default.concat(lostOptions, zeroCountOptions));
results.options = import_fp.default.concat(totalMissedOptions, results.options);
}
return results;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {});