contexture-mongo
Version:
Mongo Provider for Contexture
201 lines (200 loc) • 8.17 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 results_exports = {};
__export(results_exports, {
default: () => results_default,
getSortStage: () => getSortStage
});
module.exports = __toCommonJS(results_exports);
var import_futil = __toESM(require("futil"));
var import_fp = __toESM(require("lodash/fp.js"));
let checkPopulate = ({ include: nodeIncludes, populate }) => import_fp.default.isEmpty(nodeIncludes) || import_fp.default.reduce(
(incs, { as, localFieldName, localField, include }) => {
localFieldName = as || localFieldName;
if (!import_fp.default.includes(localFieldName, incs) && !import_fp.default.find(import_fp.default.startsWith(`${localFieldName}.`), incs)) {
throw Error(`Cannot populate an unincluded field: ${localField}`);
}
return import_fp.default.concat(
incs,
import_fp.default.map((inc) => `${localFieldName}.${inc}`, include)
);
},
nodeIncludes,
import_futil.default.unkeyBy("localFieldName", populate)
);
let omitFromInclude = (schema, include, as) => {
let allTargetFields = import_fp.default.keys(import_fp.default.get("fields", schema));
let omittedFields = import_fp.default.reject(
(field) => import_fp.default.some(
// do NOT omit if:
// - include field is an exact match of schema field
// - schema field is part of an object field specified by the include field (schemaField = 'topLevel.nestedField' and includeField = 'topLevel')
// - include field is a dotted field that is part of a schema object field (schemaField = 'topLevel' and includeField = 'topLevel.nestedField')
(includeField) => import_fp.default.startsWith(`${includeField}.`, `${field}.`) || import_fp.default.startsWith(`${field}.`, `${includeField}.`),
include
),
allTargetFields
);
return import_futil.default.arrayToObject(
(field) => `${as}.${field}`,
import_fp.default.constant(0)
)(omittedFields);
};
let convertPopulate = (getSchema) => import_fp.default.flow(
import_futil.default.mapIndexed((x, as) => {
as = x.as || as;
let { unwind, schema, include, localField, foreignField = "_id" } = x;
let targetSchema = getSchema(schema);
if (!targetSchema)
throw Error(`Couldn't find schema configuration for ${schema}`);
if (!targetSchema.mongo)
throw Error("Populating from a non-mongo schema is not supported");
let targetCollection = import_fp.default.get("mongo.collection", targetSchema);
if (!targetCollection)
throw Error(
`The ${schema} schema has a mongo configuration without a 'collection' property`
);
let $lookup = {
$lookup: {
as,
from: targetCollection,
localField,
foreignField
// || node.schema, <-- needs schema lookup
}
};
let $project = include ? { $project: omitFromInclude(targetSchema, include, as) } : null;
let $unwind = unwind && {
$unwind: {
path: `$${as}`,
preserveNullAndEmptyArrays: true
}
};
return import_fp.default.compact([$lookup, $unwind, $project]);
}),
import_fp.default.flatten
);
let getStartRecord = ({ page, pageSize }) => {
page = page < 1 ? 0 : page - 1;
return page * pageSize;
};
let parentPath = (path) => path.replace(/(\.[^.]+)$/, "");
let isParentPathProjected = (include) => (path) => import_fp.default.some(import_fp.default.eq(parentPath(path)), import_fp.default.pull(path, include));
let projectFromInclude = (include) => import_fp.default.flow(
import_fp.default.remove(isParentPathProjected(include)),
import_fp.default.countBy(import_fp.default.identity)
)(include);
let getSortStage = ({ sort, sortField, sortDir }) => {
if (!import_fp.default.isEmpty(sort)) {
return [
{
$sort: Object.fromEntries(
sort.map(({ field, desc }) => [field, desc ? -1 : 1])
)
}
];
}
if (sortField) {
return [{ $sort: { [sortField]: sortDir === "asc" ? 1 : -1 } }];
}
return [];
};
let getResultsQuery = (node, getSchema, startRecord) => {
let { pageSize, sortField, sort, populate, include, skipCount } = node;
let $sort = getSortStage(node);
let $limit = { $limit: import_futil.default.when(skipCount, import_fp.default.add(1), pageSize) };
let skipLimit = import_fp.default.compact([{ $skip: startRecord }, pageSize > 0 && $limit]);
let sortSkipLimit = import_fp.default.compact([...$sort, ...skipLimit]);
let sortOnJoinField = import_fp.default.some((x) => {
let lookupField = import_fp.default.getOr(x, `${x}.as`, populate);
return import_fp.default.some(
({ field: sortField2 }) => import_fp.default.startsWith(`${lookupField}.`, sortField2) || sortField2 === lookupField,
sort ?? [{ field: sortField }]
);
}, import_fp.default.keys(populate));
let hasMany = import_fp.default.some(import_fp.default.get("hasMany"), populate);
let $project = import_fp.default.isEmpty(include) ? [] : [{ $project: projectFromInclude(include) }];
return [
...!sortOnJoinField && !hasMany ? sortSkipLimit : [],
// if "hasMany" is set on a "populate" field but we are not sorting on a
// "populate" field, sort as early as possible
...hasMany && !sortOnJoinField ? $sort : [],
...convertPopulate(getSchema)(populate),
...sortOnJoinField ? sortSkipLimit : [],
...hasMany && !sortOnJoinField ? skipLimit : [],
...$project
];
};
let defaults = import_fp.default.defaults({
page: 1,
pageSize: 10,
sortDir: "desc",
skipCount: false,
// F.when doesn't like undefined
include: []
});
let getResponse = (node, results, count) => {
let startRecord = getStartRecord(node);
return {
totalRecords: count,
startRecord: startRecord + 1,
endRecord: startRecord + import_fp.default.min([results.length, node.pageSize]),
...node.skipCount && { hasMore: results.length > node.pageSize },
results: import_fp.default.take(node.pageSize, results)
};
};
let result = async (node, search, schema, { getSchema }) => {
node = defaults(node);
checkPopulate(node);
let hasMany = import_fp.default.some(import_fp.default.get("hasMany"), node.populate);
let resultsQuery = getResultsQuery(node, getSchema, getStartRecord(node));
let countQuery = [
...hasMany ? convertPopulate(getSchema)(node.populate) : [],
{ $group: { _id: null, count: { $sum: 1 } } }
];
let [results, count] = await Promise.all([
search(resultsQuery),
!node.skipCount && search(countQuery)
]);
return { response: getResponse(node, results, import_fp.default.get("0.count", count)) };
};
var results_default = {
getStartRecord,
getResultsQuery,
getResponse,
defaults,
projectFromInclude,
convertPopulate,
checkPopulate,
// API
result
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
getSortStage
});