UNPKG

contexture-mongo

Version:
201 lines (200 loc) 8.17 kB
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 });