@amirmarmul/waba-common
Version:

216 lines (215 loc) • 8.69 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.mongooseCursorPaginate = exports.CursorPaginationModel = void 0;
const core_1 = require("../../core");
const moment_1 = __importDefault(require("moment"));
const mongoose_1 = __importDefault(require("mongoose"));
class CursorPaginationModel {
meta = { limit: 0 };
docs = [];
}
exports.CursorPaginationModel = CursorPaginationModel;
function mongooseCursorPaginate(schema) {
schema.statics.cursor = async function cursor(options, onError) {
let baseQuery = options?.query ? { ...options.query } : {};
let limit = parseInt(options?.limit ?? '10');
let fetchLimit = limit + 1;
let direction = 'next';
let sort = options?.sort ||
(options?.aggregate?.find((agg) => agg.$sort) || {}).$sort ||
{ _id: -1 };
if (options?.cursor) {
try {
const decodedStr = core_1.Crypto.decrypt(options.cursor);
const decoded = JSON.parse(decodedStr);
core_1.logger.debug({ decoded });
direction = decoded.direction;
const sortQuery = decoded.query?.sort || {};
delete decoded.query?.sort;
for (const key of Object.keys(sort)) {
let value = sortQuery[key];
if (key.endsWith('At') && typeof value === 'string') {
value = (0, moment_1.default)(value).toDate();
}
if (value !== undefined) {
const operator = direction === 'next'
? (sort[key] === 1 ? '$gte' : '$lte')
: (sort[key] === 1 ? '$lte' : '$gte');
baseQuery.$or = [
{ [key]: { [operator]: value } },
{ [key]: { $exists: false } },
{ [key]: null }
];
if (decoded.query?.[key]) {
baseQuery.$or = baseQuery.$or.map((condition) => ({
...condition,
...decoded.query[key]
}));
}
}
}
core_1.logger.debug({ baseQuery });
baseQuery = { ...baseQuery, ...decoded.query };
}
catch (e) {
if (onError)
onError(e);
core_1.logger.debug({ e, stack: e.stack });
return undefined;
}
}
if (baseQuery && baseQuery._id) {
['gte', 'lte', 'ne'].forEach(op => {
if (baseQuery._id[`$${op}`] && typeof baseQuery._id[`$${op}`] === 'string') {
baseQuery._id[`$${op}`] = new mongoose_1.default.Types.ObjectId(baseQuery._id[`$${op}`]);
}
});
}
let effectiveQuery = { ...baseQuery };
if (options?.search &&
options.search.value &&
options.search.fields &&
options.search.fields.length) {
const searchQuery = {
$regex: options.search.value,
$options: 'i',
};
if (options.search.fields.length === 1) {
effectiveQuery[options.search.fields[0]] = searchQuery;
}
else {
effectiveQuery.$or = options.search.fields.map(field => ({ [field]: searchQuery }));
}
}
if (direction === 'previous') {
sort = Object.fromEntries(Object.entries(sort).map(([key, value]) => [key, -value]));
}
else {
sort = Object.fromEntries(Object.entries(sort).map(([key, value]) => [key, parseInt(value)]));
}
const projection = options?.projection ?? {};
const populate = options?.populate;
core_1.logger.debug({ effectiveQuery });
let mQuery;
if (options?.aggregate) {
const aggregatePipeline = [...options.aggregate];
const matchIndex = aggregatePipeline.findIndex(stage => stage.$match);
if (matchIndex !== -1) {
aggregatePipeline[matchIndex].$match = {
...aggregatePipeline[matchIndex].$match,
...effectiveQuery
};
}
else if (Object.keys(effectiveQuery).length > 0) {
aggregatePipeline.unshift({ $match: effectiveQuery });
}
const sortIndex = aggregatePipeline.findIndex(stage => stage.$sort);
if (sortIndex !== -1) {
aggregatePipeline[sortIndex].$sort = sort;
}
else {
aggregatePipeline.unshift({ $sort: sort });
}
const limitIndex = aggregatePipeline.findIndex(stage => stage.$limit);
if (limitIndex !== -1) {
aggregatePipeline[limitIndex].$limit = fetchLimit;
}
else {
aggregatePipeline.unshift({ $limit: fetchLimit });
}
core_1.logger.debug({ aggregatePipeline });
mQuery = this.aggregate(aggregatePipeline);
if (options.select) {
mQuery = mQuery.project(options.select);
}
}
else {
mQuery = this.find(effectiveQuery, projection);
if (options?.select) {
mQuery = mQuery.select(options.select);
}
if (populate) {
mQuery = mQuery.populate(populate);
}
mQuery = mQuery.sort(sort).limit(fetchLimit);
}
try {
let docs = await mQuery.exec();
let hasMore = docs.length > fetchLimit - 1;
if (direction === 'next') {
if (hasMore) {
docs.pop();
}
}
else if (direction === 'previous') {
if (hasMore) {
docs.pop();
}
docs = docs.reverse();
}
const sortKeys = Object.keys(sort);
let newCursorNext = undefined;
let newCursorPrevious = undefined;
if (docs.length > 0) {
const lastDoc = docs[docs.length - 1];
const firstDoc = docs[0];
const buildSortValues = (doc) => {
return sortKeys.reduce((acc, key) => {
acc[key] = doc[key];
return acc;
}, {});
};
if ((direction === 'next' && hasMore) || direction === 'previous') {
newCursorNext = core_1.Crypto.encrypt(JSON.stringify({
query: {
_id: { $ne: lastDoc._id },
sort: buildSortValues(lastDoc)
},
direction: 'next'
}));
}
if (options?.cursor) {
if (direction === 'next' || (direction === 'previous' && hasMore)) {
newCursorPrevious = core_1.Crypto.encrypt(JSON.stringify({
query: {
_id: { $ne: firstDoc._id },
sort: buildSortValues(firstDoc)
},
direction: 'previous'
}));
}
}
}
let cursors = { next: newCursorNext, previous: newCursorPrevious };
if (!newCursorNext && !newCursorPrevious) {
cursors = undefined;
}
const result = new CursorPaginationModel();
result.meta = {
limit,
cursors
};
result.docs = docs;
return result;
}
catch (error) {
if (onError)
onError(error);
core_1.logger.debug({ error, stack: error.stack });
return undefined;
}
};
const toJSONOptions = {
virtuals: true,
transform: function (doc, ret) {
ret.id = ret._id;
delete ret.__v;
}
};
schema.set('toJSON', toJSONOptions);
schema.set('toObject', toJSONOptions);
}
exports.mongooseCursorPaginate = mongooseCursorPaginate;