From bade7d6bc972b1310d4d81e1da377a0811d29d87 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:02:53 +0530 Subject: [PATCH 01/15] Feat!: added aggregate paginate plugin --- .../package.json | 50 +++ .../mongoose-aggregate-paginate-v2/readme.md | 335 ++++++++++++++++++ .../src/core.js | 222 ++++++++++++ .../src/index.js | 18 + .../test/index.test.js | 278 +++++++++++++++ .../types/index.d.ts | 84 +++++ plugins/mongoose-audit/test/index.test.js | 2 +- 7 files changed, 988 insertions(+), 1 deletion(-) create mode 100644 plugins/mongoose-aggregate-paginate-v2/package.json create mode 100644 plugins/mongoose-aggregate-paginate-v2/readme.md create mode 100644 plugins/mongoose-aggregate-paginate-v2/src/core.js create mode 100644 plugins/mongoose-aggregate-paginate-v2/src/index.js create mode 100644 plugins/mongoose-aggregate-paginate-v2/test/index.test.js create mode 100644 plugins/mongoose-aggregate-paginate-v2/types/index.d.ts diff --git a/plugins/mongoose-aggregate-paginate-v2/package.json b/plugins/mongoose-aggregate-paginate-v2/package.json new file mode 100644 index 0000000..391a5c6 --- /dev/null +++ b/plugins/mongoose-aggregate-paginate-v2/package.json @@ -0,0 +1,50 @@ +{ + "name": "@sliit-foss/mongoose-aggregate-paginate-v2", + "version": "0.0.0", + "description": "A cursor based custom aggregate pagination library for Mongoose with customizable labels.", + "main": "dist/index.js", + "types": "types/index.d.ts", + "scripts": { + "build": "node ../../scripts/esbuild.config.js", + "build:watch": "bash ../../scripts/esbuild.watch.sh", + "bump-version": "bash ../../scripts/bump-version.sh --name=@sliit-foss/express-http-context", + "lint": "bash ../../scripts/lint.sh", + "release": "bash ../../scripts/release.sh", + "test": "if [ \"$CI\" = \"true\" ]; then \n bash ../../scripts/test/test.sh; else \n echo \"Skipping as it is not a CI environemnt\"; fi" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/sliit-foss/npm-catalogue.git" + }, + "keywords": [ + "aggregate", + "aggregate-paginate", + "aggregate-pagination", + "mongoose-aggregate", + "mongoose", + "pagination", + "plugin", + "mongodb", + "paginate", + "paging", + "next", + "prev", + "nextpage", + "prevpage", + "total", + "paginator", + "plugin" + ], + "author": "Aravind NC (https://aravindnc.com)", + "license": "MIT", + "bugs": { + "url": "https://github.com/sliit-foss/npm-catalogue/issues" + }, + "homepage": "https://github.com/sliit-foss/npm-catalogue/blob/main/plugins/mongoose-aggregate-paginate-v2#readme", + "peerDependencies": { + "mongoose": ">=7.0.0" + }, + "engines": { + "node": ">=4.0.0" + } +} \ No newline at end of file diff --git a/plugins/mongoose-aggregate-paginate-v2/readme.md b/plugins/mongoose-aggregate-paginate-v2/readme.md new file mode 100644 index 0000000..01db4d3 --- /dev/null +++ b/plugins/mongoose-aggregate-paginate-v2/readme.md @@ -0,0 +1,335 @@ +# @sliit-foss/mongoose-aggregate-paginate-v2 + +> A fork of the cursor based custom aggregate pagination library for [Mongoose](http://mongoosejs.com) with customizable labels + +This is a fork of the [mongoose-aggregate-paginate-v2](https://www.npmjs.com/package/mongoose-aggregate-paginate-v2) package with the following changes: + +- Added support for prepagination along with a custom pipeline. This allows for more complex queries to be paginated in a more efficient manner. (Prepagination is a feature that paginates the data at a given placeholder stage in the pipeline rather than at the end) + +If you are looking for basic query pagination library without aggregate, use this one [mongoose-paginate-v2](https://github.com/aravindnc/mongoose-paginate-v2) + +## Installation + +```sh +npm install @sliit-foss/mongoose-aggregate-paginate-v2 +``` + +## Usage + +Adding the plugin to a schema, + +```js +var mongoose = require("mongoose"); +var aggregatePaginate = require("@sliit-foss/mongoose-aggregate-paginate-v2"); + +var mySchema = new mongoose.Schema({ + /* your schema definition */ +}); + +mySchema.plugin(aggregatePaginate); + +var myModel = mongoose.model("SampleModel", mySchema); +``` + +and then use model `aggregatePaginate` method, + +```js +// as Promise + +var myModel = require("/models/samplemodel"); + +const options = { + page: 1, + limit: 10, +}; + +var myAggregate = myModel.aggregate(); +myModel + .aggregatePaginate(myAggregate, options) + .then(function (results) { + console.log(results); + }) + .catch(function (err) { + console.log(err); + }); +``` + +```js +// as Callback + +var myModel = require('/models/samplemodel'); + +const options = { + page: 1, + limit: 10 +}; + +var myAggregate = myModel.aggregate(); +myModel.aggregatePaginate(myAggregate, options, function(err, results) { + if(err) { + console.err(err); + else { + console.log(results); + } +}) +``` + +```js +// Execute pagination from aggregate +const myModel = require('/models/samplemodel'); + +const options = { + page: 1, + limit: 10 +}; + +const myAggregate = myModel.aggregate(); +myAggregate.paginateExec(options, function(err, results) { + if(err) { + console.err(err); + else { + console.log(results); + } +}) +``` + +### Model.aggregatePaginate([aggregateQuery], [options], [callback]) + +Returns promise + +**Parameters** + +- `[aggregate-query]` {Object} - Aggregate Query criteria. [Documentation](https://docs.mongodb.com/manual/aggregation/) +- `[options]` {Object} + - `[sort]` {Object | String} - Sort order. [Documentation](http://mongoosejs.com/docs/api.html#query_Query-sort) + - `[offset=0]` {Number} - Use `offset` or `page` to set skip position + - `[page]` {Number} - Current Page (Defaut: 1) + - `[limit]` {Number} - Docs. per page (Default: 10). + - `[customLabels]` {Object} - Developers can provide custom labels for manipulating the response data. + - `[pagination]` {Boolean} - If `pagination` is set to false, it will return all docs without adding limit condition. (Default: True) + - `[allowDiskUse]` {Bool} - To enable diskUse for bigger queries. (Default: False) + - `[countQuery]` {Object} - Aggregate Query used to count the resultant documents. Can be used for bigger queries. (Default: `aggregate-query`) + - `[useFacet]` {Bool} - To use facet operator instead of using two queries. This is the new default. (Default: true) +- `[callback(err, result)]` - (Optional) If specified the callback is called once pagination results are retrieved or when an error has occurred. + +**Return value** + +Promise fulfilled with object having properties: + +- `docs` {Array} - Array of documents +- `totalDocs` {Number} - Total number of documents that match a query +- `limit` {Number} - Limit that was used +- `page` {Number} - Current page number +- `totalPages` {Number} - Total number of pages. +- `offset` {Number} - Only if specified or default `page`/`offset` values were used +- `hasPrevPage` {Bool} - Availability of prev page. +- `hasNextPage` {Bool} - Availability of next page. +- `prevPage` {Number} - Previous page number if available or NULL +- `nextPage` {Number} - Next page number if available or NULL +- `pagingCounter` {Number} - The starting sl. number of first document. +- `meta` {Object} - Object of pagination meta data (Default false). + +Please note that the above properties can be renamed by setting customLabels attribute. + +### Sample Usage + +#### Return first 10 documents from 100 + +```javascript +const options = { + page: 1, + limit: 10, +}; + +// Define your aggregate. +var aggregate = Model.aggregate(); + +Model.aggregatePaginate(aggregate, options) + .then(function (result) { + // result.docs + // result.totalDocs = 100 + // result.limit = 10 + // result.page = 1 + // result.totalPages = 10 + // result.hasNextPage = true + // result.nextPage = 2 + // result.hasPrevPage = false + // result.prevPage = null + }) + .catch(function (err) { + console.log(err); + }); +``` + +### With custom return labels + +Now developers can specify the return field names if they want. Below are the list of attributes whose name can be changed. + +- totalDocs +- docs +- limit +- page +- nextPage +- prevPage +- totalPages +- hasNextPage +- hasPrevPage +- pagingCounter +- meta + +You should pass the names of the properties you wish to changes using `customLabels` object in options. Labels are optional, you can pass the labels of what ever keys are you changing, others will use the default labels. + +If you want to return paginate properties as a separate object then define `customLabels.meta`. + +Same query with custom labels + +```javascript + +const myCustomLabels = { + totalDocs: 'itemCount', + docs: 'itemsList', + limit: 'perPage', + page: 'currentPage', + nextPage: 'next', + prevPage: 'prev', + totalPages: 'pageCount', + hasPrevPage: 'hasPrev', + hasNextPage: 'hasNext', + pagingCounter: 'pageCounter', + meta: 'paginator' +}; + +const options = { + page: 1, + limit: 10, + customLabels: myCustomLabels +}; + +// Define your aggregate. +var aggregate = Model.aggregate(); + +Model.aggregatePaginate(aggregate, options, function(err, result) { +if(!err) { + // result.itemsList [here docs become itemsList] + // result.itemCount = 100 [here totalDocs becomes itemCount] + // result.perPage = 10 [here limit becomes perPage] + // result.currentPage = 1 [here page becomes currentPage] + // result.pageCount = 10 [here totalPages becomes pageCount] + // result.next = 2 [here nextPage becomes next] + // result.prev = null [here prevPage becomes prev] + + // result.hasNextPage = true [not changeable] + // result.hasPrevPage = false [not changeable] +} else { + console.log(err); +}; +``` + +### Using `offset` and `limit` + +```javascript +Model.aggregatePaginate( + aggregate, + { offset: 30, limit: 10 }, + function (err, result) { + // result + } +); +``` + +### Using `countQuery` + +```javascript +// Define your aggregate query. +var aggregate = Model.aggregate(); + +// Define the count aggregate query. Can be different from `aggregate` +var countAggregate = Model.aggregate(); + +// Set the count aggregate query +const options = { + countQuery: countAggregate, +}; + +Model.aggregatePaginate(aggregate, options) + .then(function (result) { + // result + }) + .catch(function (err) { + console.log(err); + }); +``` + +### Using `prepagination` + +```javascript +// Define your pipeline +const pipeline = [ + { + $match: { + status: "active", + }, + }, + { + $sort: { + date: -1, + }, + }, + "__PREPAGINATE__", + { + $lookup: { + from: "authors", + localField: "author", + foreignField: "_id", + as: "author", + }, + } +]; +Model.aggregatePaginate(pipeline, options) + .then(function (result) { + // result + }) + .catch(function (err) { + console.log(err); + }); +``` + +### Global Options + +If you want to set the pagination options globally across the model. Then you can do like below, + +```js +let mongooseAggregatePaginate = require("mongoose-aggregate-paginate-v2"); + +let BookSchema = new mongoose.Schema({ + title: String, + date: Date, + author: { + type: mongoose.Schema.ObjectId, + ref: "Author", + }, +}); + +BookSchema.plugin(mongooseAggregatePaginate); + +let Book = mongoose.model("Book", BookSchema); + +// Like this. +Book.aggregatePaginate.options = { + limit: 20, +}; +``` + +## Release Note + +v1.0.7 - Upgrade to mongoose v8 + +v1.0.6 - Fixed exporting settings to global object. + +v1.0.5 - Added `meta` attribute to return paginate meta data as a custom object. + +v1.0.42 - Added optional `countQuery` parameter to specify separate count queries in case of bigger aggerate pipeline. + +## License + +[MIT](LICENSE) \ No newline at end of file diff --git a/plugins/mongoose-aggregate-paginate-v2/src/core.js b/plugins/mongoose-aggregate-paginate-v2/src/core.js new file mode 100644 index 0000000..8b161d3 --- /dev/null +++ b/plugins/mongoose-aggregate-paginate-v2/src/core.js @@ -0,0 +1,222 @@ +/** + * Mongoose Aggregate Paginate + * @param {Aggregate} aggregate + * @param {any} options + * @param {function} [callback] + * @returns {Promise} + */ + +const defaultOptions = { + customLabels: { + totalDocs: "totalDocs", + limit: "limit", + page: "page", + totalPages: "totalPages", + docs: "docs", + nextPage: "nextPage", + prevPage: "prevPage", + pagingCounter: "pagingCounter", + hasPrevPage: "hasPrevPage", + hasNextPage: "hasNextPage", + meta: null, + }, + collation: {}, + lean: false, + leanWithId: true, + limit: 10, + projection: {}, + select: "", + options: {}, + pagination: true, + countQuery: null, + useFacet: true, +}; + +export const PREPAGINATION_PLACEHOLDER = "__PREPAGINATE__"; + +export function aggregatePaginate(query, options, callback) { + options = { + ...defaultOptions, + ...aggregatePaginate.options, + ...options, + }; + + let pipeline = Array.isArray(query) ? query : query._pipeline; + + const customLabels = { + ...defaultOptions.customLabels, + ...options.customLabels, + }; + + const defaultLimit = 10; + + // Custom Labels + const labelTotal = customLabels.totalDocs; + const labelLimit = customLabels.limit; + const labelPage = customLabels.page; + const labelTotalPages = customLabels.totalPages; + const labelDocs = customLabels.docs; + const labelNextPage = customLabels.nextPage; + const labelPrevPage = customLabels.prevPage; + const labelHasNextPage = customLabels.hasNextPage; + const labelHasPrevPage = customLabels.hasPrevPage; + const labelPagingCounter = customLabels.pagingCounter; + const labelMeta = customLabels.meta; + + let page = parseInt(options.page || 1, 10) || 1; + let limit = + parseInt(options.limit, 10) > 0 + ? parseInt(options.limit, 10) + : defaultLimit; + + // const skip = (page - 1) * limit; + let skip; + let offset; + + if (Object.prototype.hasOwnProperty.call(options, "offset")) { + offset = Math.abs(parseInt(options.offset, 10)); + skip = offset; + } else if (Object.prototype.hasOwnProperty.call(options, "page")) { + page = Math.abs(parseInt(options.page, 10)) || 1; + skip = (page - 1) * limit; + } else { + offset = 0; + page = 1; + skip = offset; + } + + const sort = options.sort; + const allowDiskUse = options.allowDiskUse || false; + const isPaginationEnabled = options.pagination === false ? false : true; + + const q = this.aggregate() + + if (allowDiskUse) { + q.allowDiskUse(true); + } + + if (sort) { + pipeline.push({ $sort: sort }); + } + + function constructPipelines() { + let cleanedPipeline = pipeline.filter((stage) => stage !== PREPAGINATION_PLACEHOLDER) + + const countPipeline = [...cleanedPipeline, { $count: "count" }]; + + if (isPaginationEnabled) { + cleanedPipeline = pipeline.flatMap((stage) => { + if (stage === PREPAGINATION_PLACEHOLDER) { + return [ + { $skip: skip }, + { $limit: limit }, + ]; + } + return stage; + }); + } + return [cleanedPipeline, countPipeline]; + } + + let promise; + + if (options.useFacet && !options.countQuery) { + const [pipeline, countPipeline] = constructPipelines(); + promise = q.facet({ + docs: pipeline, + count: countPipeline, + }).then(([{ docs, count }]) => [docs, count]); + } else { + const [pipeline] = constructPipelines(); + + const countQuery = options.countQuery + ? options.countQuery + : this.aggregate(pipeline); + + if (allowDiskUse) { + countQuery.allowDiskUse(true); + } + + promise = Promise.all([ + this.aggregate(pipeline).exec(), + countQuery + .group({ + _id: null, + count: { + $sum: 1, + }, + }) + .exec(), + ]); + } + + return promise + .then(function (values) { + var count = values[1][0] ? values[1][0].count : 0; + + if (isPaginationEnabled === false) { + limit = count; + page = 1; + } + + const pages = Math.ceil(count / limit) || 1; + + var result = { + [labelDocs]: values[0], + }; + + var meta = { + [labelTotal]: count, + [labelLimit]: limit, + [labelPage]: page, + [labelTotalPages]: pages, + [labelPagingCounter]: (page - 1) * limit + 1, + [labelHasPrevPage]: false, + [labelHasNextPage]: false, + }; + + if (typeof offset !== "undefined") { + page = Math.ceil((offset + 1) / limit); + + meta.offset = offset; + meta[labelPage] = Math.ceil((offset + 1) / limit); + meta[labelPagingCounter] = offset + 1; + } + + // Set prev page + if (page > 1) { + meta[labelHasPrevPage] = true; + meta[labelPrevPage] = page - 1; + } else { + meta[labelPrevPage] = null; + } + + // Set next page + if (page < pages) { + meta[labelHasNextPage] = true; + meta[labelNextPage] = page + 1; + } else { + meta[labelNextPage] = null; + } + + if (labelMeta) { + result[labelMeta] = meta; + } else { + result = Object.assign(result, meta); + } + + if (typeof callback === "function") { + return callback(null, result); + } + + return Promise.resolve(result); + }) + .catch(function (reject) { + if (typeof callback === "function") { + return callback(reject); + } + return Promise.reject(reject); + }); +} + +export default aggregatePaginate; \ No newline at end of file diff --git a/plugins/mongoose-aggregate-paginate-v2/src/index.js b/plugins/mongoose-aggregate-paginate-v2/src/index.js new file mode 100644 index 0000000..f84b48c --- /dev/null +++ b/plugins/mongoose-aggregate-paginate-v2/src/index.js @@ -0,0 +1,18 @@ +import mongoose from "mongoose"; +import aggregatePaginate from "./core" + +export { PREPAGINATION_PLACEHOLDER } from "./core"; + +/** + * @param {Schema} schema +*/ +const plugin = function (schema) { + schema.statics.aggregatePaginate = aggregatePaginate; + mongoose.Aggregate.prototype.paginateExec = function (options, cb) { + return this.model().aggregatePaginate(this, options, cb); + }; +} + +plugin.aggregatePaginate = aggregatePaginate; + +export default plugin; \ No newline at end of file diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js new file mode 100644 index 0000000..1251a4e --- /dev/null +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -0,0 +1,278 @@ +"use strict"; + +import mongoose from "mongoose"; +import mongooseAggregatePaginate from "../src" + +const execute = promisify(exec); + +const setupMongoServer = async (done) => { + await execute("docker run -d -p 27018:27017 --name mongoose-paginate-test mongo:4.2.8"); + await mongoose.connect("mongodb://localhost:27018/mongoose-paginate-test", { useNewUrlParser: true }).then(done).catch(done); + await mongoose.connection.db.dropDatabase(); +}; + +const AuthorSchema = new mongoose.Schema({ + name: String, +}); + +const Author = mongoose.model("Author", AuthorSchema); + +const BookSchema = new mongoose.Schema({ + title: String, + date: Date, + author: { + type: mongoose.Schema.Types.ObjectId, + ref: "Author", + }, +}); + +BookSchema.plugin(mongooseAggregatePaginate); + +const Book = mongoose.model("Book", BookSchema); + +describe("mongoose-paginate", function () { + before(setupMongoServer); + before(function () { + let book, + books = []; + let date = new Date(); + return Author.create({ + name: "Arthur Conan Doyle", + }).then(function (author) { + for (let i = 1; i <= 100; i++) { + book = new Book({ + title: "Book #" + i, + date: new Date(date.getTime() + i), + author: author._id, + }); + books.push(book); + } + return Book.create(books); + }); + }); + + afterEach(function () { }); + + it("promise return test", function () { + var aggregate = Book.aggregate([ + { + $match: { + title: { + $in: [/Book/i], + }, + }, + }, + ]); + + let promise = aggregate.paginateExec({}); + // let promise = Book.aggregatePaginate(aggregate, {}); + expect(promise.then).to.be.an.instanceof(Function); + }); + + it("callback test", function (done) { + var aggregate = Book.aggregate([ + { + $match: { + title: { + $in: [/Book/i], + }, + }, + }, + ]); + + aggregate.paginateExec({}, function (err, result) { + expect(err).to.be.null; + expect(result).to.be.an.instanceOf(Object); + done(); + }); + }); + + it("count query test", function () { + var query = { + title: { + $in: [/Book/i], + }, + }; + var aggregate = Book.aggregate([ + { + $match: query, + }, + { + $sort: { + date: 1, + }, + }, + ]); + var options = { + limit: 10, + page: 5, + allowDiskUse: true, + countQuery: Book.aggregate([ + { + $match: query, + }, + ]), + }; + return Book.aggregatePaginate(aggregate, options).then((result) => { + expect(result.docs).to.have.length(10); + expect(result.docs[0].title).to.equal("Book #41"); + expect(result.totalDocs).to.equal(100); + expect(result.limit).to.equal(10); + expect(result.page).to.equal(5); + expect(result.pagingCounter).to.equal(41); + expect(result.hasPrevPage).to.equal(true); + expect(result.hasNextPage).to.equal(true); + expect(result.prevPage).to.equal(4); + expect(result.nextPage).to.equal(6); + expect(result.totalPages).to.equal(10); + }); + }); + + describe("paginates", function () { + it("with global limit and page", function () { + Book.aggregatePaginate.options = { + limit: 20, + }; + + var aggregate = Book.aggregate([ + { + $match: { + title: { + $in: [/Book/i], + }, + }, + }, + { + $sort: { + date: 1, + }, + }, + ]); + var options = { + limit: 10, + page: 5, + allowDiskUse: true, + }; + return Book.aggregatePaginate(aggregate, options).then((result) => { + expect(result.docs).to.have.length(10); + expect(result.docs[0].title).to.equal("Book #41"); + expect(result.totalDocs).to.equal(100); + expect(result.limit).to.equal(10); + expect(result.page).to.equal(5); + expect(result.pagingCounter).to.equal(41); + expect(result.hasPrevPage).to.equal(true); + expect(result.hasNextPage).to.equal(true); + expect(result.prevPage).to.equal(4); + expect(result.nextPage).to.equal(6); + expect(result.totalPages).to.equal(10); + }); + }); + + it("with custom labels", function () { + var aggregate = Book.aggregate([ + { + $match: { + title: { + $in: [/Book/i], + }, + }, + }, + { + $sort: { + date: 1, + }, + }, + ]); + + const myCustomLabels = { + totalDocs: "itemCount", + docs: "itemsList", + limit: "perPage", + page: "currentPage", + hasNextPage: "hasNext", + hasPrevPage: "hasPrev", + nextPage: "next", + prevPage: "prev", + totalPages: "pageCount", + pagingCounter: "pageCounter", + }; + + var options = { + // limit: 10, + page: 5, + customLabels: myCustomLabels, + }; + return Book.aggregatePaginate(aggregate, options).then((result) => { + expect(result.itemsList).to.have.length(20); + expect(result.itemsList[0].title).to.equal("Book #81"); + expect(result.itemCount).to.equal(100); + expect(result.perPage).to.equal(20); + expect(result.currentPage).to.equal(5); + expect(result.pageCounter).to.equal(81); + expect(result.hasPrev).to.equal(true); + expect(result.hasNext).to.equal(false); + expect(result.prev).to.equal(4); + expect(result.next).to.equal(null); + expect(result.pageCount).to.equal(5); + }); + }); + + it("with offset", function () { + var aggregate = Book.aggregate([ + { + $match: { + title: { + $in: [/Book/i], + }, + }, + }, + { + $sort: { + date: 1, + }, + }, + ]); + + const myCustomLabels = { + totalDocs: "itemCount", + docs: "itemsList", + limit: "perPage", + page: "currentPage", + hasNextPage: "hasNext", + hasPrevPage: "hasPrev", + nextPage: "next", + prevPage: "prev", + totalPages: "pageCount", + pagingCounter: "pageCounter", + }; + + var options = { + // limit: 10, + offset: 80, + customLabels: myCustomLabels, + }; + + return Book.aggregatePaginate(aggregate, options).then((result) => { + expect(result.itemsList).to.have.length(20); + expect(result.itemsList[0].title).to.equal("Book #81"); + expect(result.itemCount).to.equal(100); + expect(result.perPage).to.equal(20); + expect(result.currentPage).to.equal(5); + expect(result.pageCounter).to.equal(81); + expect(result.hasPrev).to.equal(true); + expect(result.hasNext).to.equal(false); + expect(result.prev).to.equal(4); + expect(result.next).to.equal(null); + expect(result.pageCount).to.equal(5); + }); + }); + }); + + after(async function () { + await mongoose.connection.db.dropDatabase(); + }); + + after(async function () { + await mongoose.disconnect(); + }); +}); \ No newline at end of file diff --git a/plugins/mongoose-aggregate-paginate-v2/types/index.d.ts b/plugins/mongoose-aggregate-paginate-v2/types/index.d.ts new file mode 100644 index 0000000..717fabf --- /dev/null +++ b/plugins/mongoose-aggregate-paginate-v2/types/index.d.ts @@ -0,0 +1,84 @@ +// +// Based on type declarations for mongoose-paginate-v2 1.3. +// +// Thanks to knyuwork +// and LiRen Tu for their contribution + +declare module "mongoose" { + interface CustomLabels { + totalDocs?: T | undefined; + docs?: T | undefined; + limit?: T | undefined; + page?: T | undefined; + nextPage?: T | undefined; + prevPage?: T | undefined; + hasNextPage?: T | undefined; + hasPrevPage?: T | undefined; + totalPages?: T | undefined; + pagingCounter?: T | undefined; + meta?: T | undefined; + } + + interface PaginateOptions { + sort?: object | string | undefined; + offset?: number | undefined; + page?: number | undefined; + limit?: number | undefined; + customLabels?: CustomLabels | undefined; + /* If pagination is set to `false`, it will return all docs without adding limit condition. (Default: `true`) */ + pagination?: boolean | undefined; + allowDiskUse?: boolean | undefined; + countQuery?: object | undefined; + useFacet?: boolean | undefined; + } + + interface QueryPopulateOptions { + /** space delimited path(s) to populate */ + path: string; + /** optional fields to select */ + select?: any; + /** optional query conditions to match */ + match?: any; + /** optional model to use for population */ + model?: string | Model | undefined; + /** optional query options like sort, limit, etc */ + options?: any; + /** deep populate */ + populate?: QueryPopulateOptions | QueryPopulateOptions[] | undefined; + } + + interface AggregatePaginateResult { + docs: T[]; + totalDocs: number; + limit: number; + page?: number | undefined; + totalPages: number; + nextPage?: number | null | undefined; + prevPage?: number | null | undefined; + pagingCounter: number; + hasPrevPage: boolean; + hasNextPage: boolean; + meta?: any; + [customLabel: string]: T[] | number | boolean | null | undefined; + } + + interface AggregatePaginateModel extends Model { + aggregatePaginate( + query?: Aggregate | PipelineStage[], + options?: PaginateOptions, + callback?: (err: any, result: AggregatePaginateResult) => void, + ): Promise>; + } + + function model(name: string, schema?: Schema, collection?: string, skipInit?: boolean): AggregatePaginateModel; +} + +import mongoose from "mongoose"; + +declare function mongooseAggregatePaginate(schema: mongoose.Schema): void; + +export default mongooseAggregatePaginate; + +declare namespace _ { + const aggregatePaginate: { options: mongoose.PaginateOptions }; +} diff --git a/plugins/mongoose-audit/test/index.test.js b/plugins/mongoose-audit/test/index.test.js index 7cb1b1d..5d608c4 100644 --- a/plugins/mongoose-audit/test/index.test.js +++ b/plugins/mongoose-audit/test/index.test.js @@ -7,7 +7,7 @@ import { AuditType } from "../src/constants"; const execute = promisify(exec); const setupMongoServer = async (done) => { - await execute("docker run -d -p 27017:27017 --name audit-test mongo:4.2.8"); + await execute("docker run -d -p 27018:27017 --name audit-test mongo:4.2.8"); mongoose.connect("mongodb://localhost:27018/audit-test", { useNewUrlParser: true }).then(done).catch(done); }; From b32e7c50646daba8f5255996328217169376697e Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:06:57 +0530 Subject: [PATCH 02/15] Style: formatted code --- .../mongoose-aggregate-paginate-v2/readme.md | 36 +- .../src/core.js | 388 +++++++-------- .../src/index.js | 18 +- .../test/index.test.js | 471 +++++++++--------- .../types/index.d.ts | 124 ++--- pnpm-lock.yaml | 163 +++++- 6 files changed, 677 insertions(+), 523 deletions(-) diff --git a/plugins/mongoose-aggregate-paginate-v2/readme.md b/plugins/mongoose-aggregate-paginate-v2/readme.md index 01db4d3..5d9cac2 100644 --- a/plugins/mongoose-aggregate-paginate-v2/readme.md +++ b/plugins/mongoose-aggregate-paginate-v2/readme.md @@ -40,7 +40,7 @@ var myModel = require("/models/samplemodel"); const options = { page: 1, - limit: 10, + limit: 10 }; var myAggregate = myModel.aggregate(); @@ -138,7 +138,7 @@ Please note that the above properties can be renamed by setting customLabels att ```javascript const options = { page: 1, - limit: 10, + limit: 10 }; // Define your aggregate. @@ -228,13 +228,9 @@ if(!err) { ### Using `offset` and `limit` ```javascript -Model.aggregatePaginate( - aggregate, - { offset: 30, limit: 10 }, - function (err, result) { - // result - } -); +Model.aggregatePaginate(aggregate, { offset: 30, limit: 10 }, function (err, result) { + // result +}); ``` ### Using `countQuery` @@ -248,7 +244,7 @@ var countAggregate = Model.aggregate(); // Set the count aggregate query const options = { - countQuery: countAggregate, + countQuery: countAggregate }; Model.aggregatePaginate(aggregate, options) @@ -267,13 +263,13 @@ Model.aggregatePaginate(aggregate, options) const pipeline = [ { $match: { - status: "active", - }, + status: "active" + } }, { $sort: { - date: -1, - }, + date: -1 + } }, "__PREPAGINATE__", { @@ -281,8 +277,8 @@ const pipeline = [ from: "authors", localField: "author", foreignField: "_id", - as: "author", - }, + as: "author" + } } ]; Model.aggregatePaginate(pipeline, options) @@ -306,8 +302,8 @@ let BookSchema = new mongoose.Schema({ date: Date, author: { type: mongoose.Schema.ObjectId, - ref: "Author", - }, + ref: "Author" + } }); BookSchema.plugin(mongooseAggregatePaginate); @@ -316,7 +312,7 @@ let Book = mongoose.model("Book", BookSchema); // Like this. Book.aggregatePaginate.options = { - limit: 20, + limit: 20 }; ``` @@ -332,4 +328,4 @@ v1.0.42 - Added optional `countQuery` parameter to specify separate count querie ## License -[MIT](LICENSE) \ No newline at end of file +[MIT](LICENSE) diff --git a/plugins/mongoose-aggregate-paginate-v2/src/core.js b/plugins/mongoose-aggregate-paginate-v2/src/core.js index 8b161d3..5e8f9f3 100644 --- a/plugins/mongoose-aggregate-paginate-v2/src/core.js +++ b/plugins/mongoose-aggregate-paginate-v2/src/core.js @@ -7,216 +7,210 @@ */ const defaultOptions = { - customLabels: { - totalDocs: "totalDocs", - limit: "limit", - page: "page", - totalPages: "totalPages", - docs: "docs", - nextPage: "nextPage", - prevPage: "prevPage", - pagingCounter: "pagingCounter", - hasPrevPage: "hasPrevPage", - hasNextPage: "hasNextPage", - meta: null, - }, - collation: {}, - lean: false, - leanWithId: true, - limit: 10, - projection: {}, - select: "", - options: {}, - pagination: true, - countQuery: null, - useFacet: true, + customLabels: { + totalDocs: "totalDocs", + limit: "limit", + page: "page", + totalPages: "totalPages", + docs: "docs", + nextPage: "nextPage", + prevPage: "prevPage", + pagingCounter: "pagingCounter", + hasPrevPage: "hasPrevPage", + hasNextPage: "hasNextPage", + meta: null + }, + collation: {}, + lean: false, + leanWithId: true, + limit: 10, + projection: {}, + select: "", + options: {}, + pagination: true, + countQuery: null, + useFacet: true }; export const PREPAGINATION_PLACEHOLDER = "__PREPAGINATE__"; export function aggregatePaginate(query, options, callback) { - options = { - ...defaultOptions, - ...aggregatePaginate.options, - ...options, - }; - - let pipeline = Array.isArray(query) ? query : query._pipeline; - - const customLabels = { - ...defaultOptions.customLabels, - ...options.customLabels, - }; - - const defaultLimit = 10; - - // Custom Labels - const labelTotal = customLabels.totalDocs; - const labelLimit = customLabels.limit; - const labelPage = customLabels.page; - const labelTotalPages = customLabels.totalPages; - const labelDocs = customLabels.docs; - const labelNextPage = customLabels.nextPage; - const labelPrevPage = customLabels.prevPage; - const labelHasNextPage = customLabels.hasNextPage; - const labelHasPrevPage = customLabels.hasPrevPage; - const labelPagingCounter = customLabels.pagingCounter; - const labelMeta = customLabels.meta; - - let page = parseInt(options.page || 1, 10) || 1; - let limit = - parseInt(options.limit, 10) > 0 - ? parseInt(options.limit, 10) - : defaultLimit; - - // const skip = (page - 1) * limit; - let skip; - let offset; - - if (Object.prototype.hasOwnProperty.call(options, "offset")) { - offset = Math.abs(parseInt(options.offset, 10)); - skip = offset; - } else if (Object.prototype.hasOwnProperty.call(options, "page")) { - page = Math.abs(parseInt(options.page, 10)) || 1; - skip = (page - 1) * limit; - } else { - offset = 0; - page = 1; - skip = offset; - } - - const sort = options.sort; - const allowDiskUse = options.allowDiskUse || false; - const isPaginationEnabled = options.pagination === false ? false : true; - - const q = this.aggregate() - - if (allowDiskUse) { - q.allowDiskUse(true); - } - - if (sort) { - pipeline.push({ $sort: sort }); - } - - function constructPipelines() { - let cleanedPipeline = pipeline.filter((stage) => stage !== PREPAGINATION_PLACEHOLDER) - - const countPipeline = [...cleanedPipeline, { $count: "count" }]; - - if (isPaginationEnabled) { - cleanedPipeline = pipeline.flatMap((stage) => { - if (stage === PREPAGINATION_PLACEHOLDER) { - return [ - { $skip: skip }, - { $limit: limit }, - ]; - } - return stage; - }); + options = { + ...defaultOptions, + ...aggregatePaginate.options, + ...options + }; + + const pipeline = Array.isArray(query) ? query : query._pipeline; + + const customLabels = { + ...defaultOptions.customLabels, + ...options.customLabels + }; + + const defaultLimit = 10; + + // Custom Labels + const labelTotal = customLabels.totalDocs; + const labelLimit = customLabels.limit; + const labelPage = customLabels.page; + const labelTotalPages = customLabels.totalPages; + const labelDocs = customLabels.docs; + const labelNextPage = customLabels.nextPage; + const labelPrevPage = customLabels.prevPage; + const labelHasNextPage = customLabels.hasNextPage; + const labelHasPrevPage = customLabels.hasPrevPage; + const labelPagingCounter = customLabels.pagingCounter; + const labelMeta = customLabels.meta; + + let page = parseInt(options.page || 1, 10) || 1; + let limit = parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : defaultLimit; + + // const skip = (page - 1) * limit; + let skip; + let offset; + + if (Object.prototype.hasOwnProperty.call(options, "offset")) { + offset = Math.abs(parseInt(options.offset, 10)); + skip = offset; + } else if (Object.prototype.hasOwnProperty.call(options, "page")) { + page = Math.abs(parseInt(options.page, 10)) || 1; + skip = (page - 1) * limit; + } else { + offset = 0; + page = 1; + skip = offset; + } + + const sort = options.sort; + const allowDiskUse = options.allowDiskUse || false; + const isPaginationEnabled = options.pagination === false ? false : true; + + const q = this.aggregate(); + + if (allowDiskUse) { + q.allowDiskUse(true); + } + + if (sort) { + pipeline.push({ $sort: sort }); + } + + function constructPipelines() { + let cleanedPipeline = pipeline.filter((stage) => stage !== PREPAGINATION_PLACEHOLDER); + + const countPipeline = [...cleanedPipeline, { $count: "count" }]; + + if (isPaginationEnabled) { + cleanedPipeline = pipeline.flatMap((stage) => { + if (stage === PREPAGINATION_PLACEHOLDER) { + return [{ $skip: skip }, { $limit: limit }]; } - return [cleanedPipeline, countPipeline]; + return stage; + }); } + return [cleanedPipeline, countPipeline]; + } - let promise; + let promise; - if (options.useFacet && !options.countQuery) { - const [pipeline, countPipeline] = constructPipelines(); - promise = q.facet({ - docs: pipeline, - count: countPipeline, - }).then(([{ docs, count }]) => [docs, count]); - } else { - const [pipeline] = constructPipelines(); + if (options.useFacet && !options.countQuery) { + const [pipeline, countPipeline] = constructPipelines(); + promise = q + .facet({ + docs: pipeline, + count: countPipeline + }) + .then(([{ docs, count }]) => [docs, count]); + } else { + const [pipeline] = constructPipelines(); - const countQuery = options.countQuery - ? options.countQuery - : this.aggregate(pipeline); - - if (allowDiskUse) { - countQuery.allowDiskUse(true); - } + const countQuery = options.countQuery ? options.countQuery : this.aggregate(pipeline); - promise = Promise.all([ - this.aggregate(pipeline).exec(), - countQuery - .group({ - _id: null, - count: { - $sum: 1, - }, - }) - .exec(), - ]); + if (allowDiskUse) { + countQuery.allowDiskUse(true); } - return promise - .then(function (values) { - var count = values[1][0] ? values[1][0].count : 0; - - if (isPaginationEnabled === false) { - limit = count; - page = 1; - } - - const pages = Math.ceil(count / limit) || 1; - - var result = { - [labelDocs]: values[0], - }; - - var meta = { - [labelTotal]: count, - [labelLimit]: limit, - [labelPage]: page, - [labelTotalPages]: pages, - [labelPagingCounter]: (page - 1) * limit + 1, - [labelHasPrevPage]: false, - [labelHasNextPage]: false, - }; - - if (typeof offset !== "undefined") { - page = Math.ceil((offset + 1) / limit); - - meta.offset = offset; - meta[labelPage] = Math.ceil((offset + 1) / limit); - meta[labelPagingCounter] = offset + 1; - } - - // Set prev page - if (page > 1) { - meta[labelHasPrevPage] = true; - meta[labelPrevPage] = page - 1; - } else { - meta[labelPrevPage] = null; - } - - // Set next page - if (page < pages) { - meta[labelHasNextPage] = true; - meta[labelNextPage] = page + 1; - } else { - meta[labelNextPage] = null; - } - - if (labelMeta) { - result[labelMeta] = meta; - } else { - result = Object.assign(result, meta); - } - - if (typeof callback === "function") { - return callback(null, result); - } - - return Promise.resolve(result); + promise = Promise.all([ + this.aggregate(pipeline).exec(), + countQuery + .group({ + _id: null, + count: { + $sum: 1 + } }) - .catch(function (reject) { - if (typeof callback === "function") { - return callback(reject); - } - return Promise.reject(reject); - }); + .exec() + ]); + } + + return promise + .then(function (values) { + const count = values[1][0] ? values[1][0].count : 0; + + if (isPaginationEnabled === false) { + limit = count; + page = 1; + } + + const pages = Math.ceil(count / limit) || 1; + + let result = { + [labelDocs]: values[0] + }; + + const meta = { + [labelTotal]: count, + [labelLimit]: limit, + [labelPage]: page, + [labelTotalPages]: pages, + [labelPagingCounter]: (page - 1) * limit + 1, + [labelHasPrevPage]: false, + [labelHasNextPage]: false + }; + + if (typeof offset !== "undefined") { + page = Math.ceil((offset + 1) / limit); + + meta.offset = offset; + meta[labelPage] = Math.ceil((offset + 1) / limit); + meta[labelPagingCounter] = offset + 1; + } + + // Set prev page + if (page > 1) { + meta[labelHasPrevPage] = true; + meta[labelPrevPage] = page - 1; + } else { + meta[labelPrevPage] = null; + } + + // Set next page + if (page < pages) { + meta[labelHasNextPage] = true; + meta[labelNextPage] = page + 1; + } else { + meta[labelNextPage] = null; + } + + if (labelMeta) { + result[labelMeta] = meta; + } else { + result = Object.assign(result, meta); + } + + if (typeof callback === "function") { + return callback(null, result); + } + + return Promise.resolve(result); + }) + .catch(function (reject) { + if (typeof callback === "function") { + return callback(reject); + } + return Promise.reject(reject); + }); } -export default aggregatePaginate; \ No newline at end of file +export default aggregatePaginate; diff --git a/plugins/mongoose-aggregate-paginate-v2/src/index.js b/plugins/mongoose-aggregate-paginate-v2/src/index.js index f84b48c..e4ff004 100644 --- a/plugins/mongoose-aggregate-paginate-v2/src/index.js +++ b/plugins/mongoose-aggregate-paginate-v2/src/index.js @@ -1,18 +1,18 @@ import mongoose from "mongoose"; -import aggregatePaginate from "./core" +import aggregatePaginate, { PREPAGINATION_PLACEHOLDER } from "./core"; -export { PREPAGINATION_PLACEHOLDER } from "./core"; +export { PREPAGINATION_PLACEHOLDER } /** * @param {Schema} schema -*/ + */ const plugin = function (schema) { - schema.statics.aggregatePaginate = aggregatePaginate; - mongoose.Aggregate.prototype.paginateExec = function (options, cb) { - return this.model().aggregatePaginate(this, options, cb); - }; -} + schema.statics.aggregatePaginate = aggregatePaginate; + mongoose.Aggregate.prototype.paginateExec = function (options, cb) { + return this.model().aggregatePaginate(this, options, cb); + }; +}; plugin.aggregatePaginate = aggregatePaginate; -export default plugin; \ No newline at end of file +export default plugin; diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js index 1251a4e..acc4637 100644 --- a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -1,29 +1,32 @@ "use strict"; import mongoose from "mongoose"; -import mongooseAggregatePaginate from "../src" +import mongooseAggregatePaginate from "../src"; const execute = promisify(exec); const setupMongoServer = async (done) => { - await execute("docker run -d -p 27018:27017 --name mongoose-paginate-test mongo:4.2.8"); - await mongoose.connect("mongodb://localhost:27018/mongoose-paginate-test", { useNewUrlParser: true }).then(done).catch(done); - await mongoose.connection.db.dropDatabase(); + await execute("docker run -d -p 27018:27017 --name mongoose-paginate-test mongo:4.2.8"); + await mongoose + .connect("mongodb://localhost:27018/mongoose-paginate-test", { useNewUrlParser: true }) + .then(done) + .catch(done); + await mongoose.connection.db.dropDatabase(); }; const AuthorSchema = new mongoose.Schema({ - name: String, + name: String }); const Author = mongoose.model("Author", AuthorSchema); const BookSchema = new mongoose.Schema({ - title: String, - date: Date, - author: { - type: mongoose.Schema.Types.ObjectId, - ref: "Author", - }, + title: String, + date: Date, + author: { + type: mongoose.Schema.Types.ObjectId, + ref: "Author" + } }); BookSchema.plugin(mongooseAggregatePaginate); @@ -31,248 +34,248 @@ BookSchema.plugin(mongooseAggregatePaginate); const Book = mongoose.model("Book", BookSchema); describe("mongoose-paginate", function () { - before(setupMongoServer); - before(function () { - let book, - books = []; - let date = new Date(); - return Author.create({ - name: "Arthur Conan Doyle", - }).then(function (author) { - for (let i = 1; i <= 100; i++) { - book = new Book({ - title: "Book #" + i, - date: new Date(date.getTime() + i), - author: author._id, - }); - books.push(book); - } - return Book.create(books); + before(setupMongoServer); + before(function () { + let book, + books = []; + let date = new Date(); + return Author.create({ + name: "Arthur Conan Doyle" + }).then(function (author) { + for (let i = 1; i <= 100; i++) { + book = new Book({ + title: "Book #" + i, + date: new Date(date.getTime() + i), + author: author._id }); + books.push(book); + } + return Book.create(books); }); + }); - afterEach(function () { }); + afterEach(function () {}); - it("promise return test", function () { - var aggregate = Book.aggregate([ - { - $match: { - title: { - $in: [/Book/i], - }, - }, - }, - ]); + it("promise return test", function () { + var aggregate = Book.aggregate([ + { + $match: { + title: { + $in: [/Book/i] + } + } + } + ]); - let promise = aggregate.paginateExec({}); - // let promise = Book.aggregatePaginate(aggregate, {}); - expect(promise.then).to.be.an.instanceof(Function); - }); + let promise = aggregate.paginateExec({}); + // let promise = Book.aggregatePaginate(aggregate, {}); + expect(promise.then).to.be.an.instanceof(Function); + }); - it("callback test", function (done) { - var aggregate = Book.aggregate([ - { - $match: { - title: { - $in: [/Book/i], - }, - }, - }, - ]); + it("callback test", function (done) { + var aggregate = Book.aggregate([ + { + $match: { + title: { + $in: [/Book/i] + } + } + } + ]); - aggregate.paginateExec({}, function (err, result) { - expect(err).to.be.null; - expect(result).to.be.an.instanceOf(Object); - done(); - }); + aggregate.paginateExec({}, function (err, result) { + expect(err).to.be.null; + expect(result).to.be.an.instanceOf(Object); + done(); }); + }); - it("count query test", function () { - var query = { - title: { - $in: [/Book/i], - }, - }; - var aggregate = Book.aggregate([ - { - $match: query, - }, - { - $sort: { - date: 1, - }, - }, - ]); - var options = { - limit: 10, - page: 5, - allowDiskUse: true, - countQuery: Book.aggregate([ - { - $match: query, - }, - ]), - }; - return Book.aggregatePaginate(aggregate, options).then((result) => { - expect(result.docs).to.have.length(10); - expect(result.docs[0].title).to.equal("Book #41"); - expect(result.totalDocs).to.equal(100); - expect(result.limit).to.equal(10); - expect(result.page).to.equal(5); - expect(result.pagingCounter).to.equal(41); - expect(result.hasPrevPage).to.equal(true); - expect(result.hasNextPage).to.equal(true); - expect(result.prevPage).to.equal(4); - expect(result.nextPage).to.equal(6); - expect(result.totalPages).to.equal(10); - }); + it("count query test", function () { + var query = { + title: { + $in: [/Book/i] + } + }; + var aggregate = Book.aggregate([ + { + $match: query + }, + { + $sort: { + date: 1 + } + } + ]); + var options = { + limit: 10, + page: 5, + allowDiskUse: true, + countQuery: Book.aggregate([ + { + $match: query + } + ]) + }; + return Book.aggregatePaginate(aggregate, options).then((result) => { + expect(result.docs).to.have.length(10); + expect(result.docs[0].title).to.equal("Book #41"); + expect(result.totalDocs).to.equal(100); + expect(result.limit).to.equal(10); + expect(result.page).to.equal(5); + expect(result.pagingCounter).to.equal(41); + expect(result.hasPrevPage).to.equal(true); + expect(result.hasNextPage).to.equal(true); + expect(result.prevPage).to.equal(4); + expect(result.nextPage).to.equal(6); + expect(result.totalPages).to.equal(10); }); + }); - describe("paginates", function () { - it("with global limit and page", function () { - Book.aggregatePaginate.options = { - limit: 20, - }; + describe("paginates", function () { + it("with global limit and page", function () { + Book.aggregatePaginate.options = { + limit: 20 + }; - var aggregate = Book.aggregate([ - { - $match: { - title: { - $in: [/Book/i], - }, - }, - }, - { - $sort: { - date: 1, - }, - }, - ]); - var options = { - limit: 10, - page: 5, - allowDiskUse: true, - }; - return Book.aggregatePaginate(aggregate, options).then((result) => { - expect(result.docs).to.have.length(10); - expect(result.docs[0].title).to.equal("Book #41"); - expect(result.totalDocs).to.equal(100); - expect(result.limit).to.equal(10); - expect(result.page).to.equal(5); - expect(result.pagingCounter).to.equal(41); - expect(result.hasPrevPage).to.equal(true); - expect(result.hasNextPage).to.equal(true); - expect(result.prevPage).to.equal(4); - expect(result.nextPage).to.equal(6); - expect(result.totalPages).to.equal(10); - }); - }); + var aggregate = Book.aggregate([ + { + $match: { + title: { + $in: [/Book/i] + } + } + }, + { + $sort: { + date: 1 + } + } + ]); + var options = { + limit: 10, + page: 5, + allowDiskUse: true + }; + return Book.aggregatePaginate(aggregate, options).then((result) => { + expect(result.docs).to.have.length(10); + expect(result.docs[0].title).to.equal("Book #41"); + expect(result.totalDocs).to.equal(100); + expect(result.limit).to.equal(10); + expect(result.page).to.equal(5); + expect(result.pagingCounter).to.equal(41); + expect(result.hasPrevPage).to.equal(true); + expect(result.hasNextPage).to.equal(true); + expect(result.prevPage).to.equal(4); + expect(result.nextPage).to.equal(6); + expect(result.totalPages).to.equal(10); + }); + }); - it("with custom labels", function () { - var aggregate = Book.aggregate([ - { - $match: { - title: { - $in: [/Book/i], - }, - }, - }, - { - $sort: { - date: 1, - }, - }, - ]); + it("with custom labels", function () { + var aggregate = Book.aggregate([ + { + $match: { + title: { + $in: [/Book/i] + } + } + }, + { + $sort: { + date: 1 + } + } + ]); - const myCustomLabels = { - totalDocs: "itemCount", - docs: "itemsList", - limit: "perPage", - page: "currentPage", - hasNextPage: "hasNext", - hasPrevPage: "hasPrev", - nextPage: "next", - prevPage: "prev", - totalPages: "pageCount", - pagingCounter: "pageCounter", - }; + const myCustomLabels = { + totalDocs: "itemCount", + docs: "itemsList", + limit: "perPage", + page: "currentPage", + hasNextPage: "hasNext", + hasPrevPage: "hasPrev", + nextPage: "next", + prevPage: "prev", + totalPages: "pageCount", + pagingCounter: "pageCounter" + }; - var options = { - // limit: 10, - page: 5, - customLabels: myCustomLabels, - }; - return Book.aggregatePaginate(aggregate, options).then((result) => { - expect(result.itemsList).to.have.length(20); - expect(result.itemsList[0].title).to.equal("Book #81"); - expect(result.itemCount).to.equal(100); - expect(result.perPage).to.equal(20); - expect(result.currentPage).to.equal(5); - expect(result.pageCounter).to.equal(81); - expect(result.hasPrev).to.equal(true); - expect(result.hasNext).to.equal(false); - expect(result.prev).to.equal(4); - expect(result.next).to.equal(null); - expect(result.pageCount).to.equal(5); - }); - }); + var options = { + // limit: 10, + page: 5, + customLabels: myCustomLabels + }; + return Book.aggregatePaginate(aggregate, options).then((result) => { + expect(result.itemsList).to.have.length(20); + expect(result.itemsList[0].title).to.equal("Book #81"); + expect(result.itemCount).to.equal(100); + expect(result.perPage).to.equal(20); + expect(result.currentPage).to.equal(5); + expect(result.pageCounter).to.equal(81); + expect(result.hasPrev).to.equal(true); + expect(result.hasNext).to.equal(false); + expect(result.prev).to.equal(4); + expect(result.next).to.equal(null); + expect(result.pageCount).to.equal(5); + }); + }); - it("with offset", function () { - var aggregate = Book.aggregate([ - { - $match: { - title: { - $in: [/Book/i], - }, - }, - }, - { - $sort: { - date: 1, - }, - }, - ]); + it("with offset", function () { + var aggregate = Book.aggregate([ + { + $match: { + title: { + $in: [/Book/i] + } + } + }, + { + $sort: { + date: 1 + } + } + ]); - const myCustomLabels = { - totalDocs: "itemCount", - docs: "itemsList", - limit: "perPage", - page: "currentPage", - hasNextPage: "hasNext", - hasPrevPage: "hasPrev", - nextPage: "next", - prevPage: "prev", - totalPages: "pageCount", - pagingCounter: "pageCounter", - }; + const myCustomLabels = { + totalDocs: "itemCount", + docs: "itemsList", + limit: "perPage", + page: "currentPage", + hasNextPage: "hasNext", + hasPrevPage: "hasPrev", + nextPage: "next", + prevPage: "prev", + totalPages: "pageCount", + pagingCounter: "pageCounter" + }; - var options = { - // limit: 10, - offset: 80, - customLabels: myCustomLabels, - }; + var options = { + // limit: 10, + offset: 80, + customLabels: myCustomLabels + }; - return Book.aggregatePaginate(aggregate, options).then((result) => { - expect(result.itemsList).to.have.length(20); - expect(result.itemsList[0].title).to.equal("Book #81"); - expect(result.itemCount).to.equal(100); - expect(result.perPage).to.equal(20); - expect(result.currentPage).to.equal(5); - expect(result.pageCounter).to.equal(81); - expect(result.hasPrev).to.equal(true); - expect(result.hasNext).to.equal(false); - expect(result.prev).to.equal(4); - expect(result.next).to.equal(null); - expect(result.pageCount).to.equal(5); - }); - }); + return Book.aggregatePaginate(aggregate, options).then((result) => { + expect(result.itemsList).to.have.length(20); + expect(result.itemsList[0].title).to.equal("Book #81"); + expect(result.itemCount).to.equal(100); + expect(result.perPage).to.equal(20); + expect(result.currentPage).to.equal(5); + expect(result.pageCounter).to.equal(81); + expect(result.hasPrev).to.equal(true); + expect(result.hasNext).to.equal(false); + expect(result.prev).to.equal(4); + expect(result.next).to.equal(null); + expect(result.pageCount).to.equal(5); + }); }); + }); - after(async function () { - await mongoose.connection.db.dropDatabase(); - }); + after(async function () { + await mongoose.connection.db.dropDatabase(); + }); - after(async function () { - await mongoose.disconnect(); - }); -}); \ No newline at end of file + after(async function () { + await mongoose.disconnect(); + }); +}); diff --git a/plugins/mongoose-aggregate-paginate-v2/types/index.d.ts b/plugins/mongoose-aggregate-paginate-v2/types/index.d.ts index 717fabf..31fe5c3 100644 --- a/plugins/mongoose-aggregate-paginate-v2/types/index.d.ts +++ b/plugins/mongoose-aggregate-paginate-v2/types/index.d.ts @@ -5,72 +5,72 @@ // and LiRen Tu for their contribution declare module "mongoose" { - interface CustomLabels { - totalDocs?: T | undefined; - docs?: T | undefined; - limit?: T | undefined; - page?: T | undefined; - nextPage?: T | undefined; - prevPage?: T | undefined; - hasNextPage?: T | undefined; - hasPrevPage?: T | undefined; - totalPages?: T | undefined; - pagingCounter?: T | undefined; - meta?: T | undefined; - } + interface CustomLabels { + totalDocs?: T | undefined; + docs?: T | undefined; + limit?: T | undefined; + page?: T | undefined; + nextPage?: T | undefined; + prevPage?: T | undefined; + hasNextPage?: T | undefined; + hasPrevPage?: T | undefined; + totalPages?: T | undefined; + pagingCounter?: T | undefined; + meta?: T | undefined; + } - interface PaginateOptions { - sort?: object | string | undefined; - offset?: number | undefined; - page?: number | undefined; - limit?: number | undefined; - customLabels?: CustomLabels | undefined; - /* If pagination is set to `false`, it will return all docs without adding limit condition. (Default: `true`) */ - pagination?: boolean | undefined; - allowDiskUse?: boolean | undefined; - countQuery?: object | undefined; - useFacet?: boolean | undefined; - } + interface PaginateOptions { + sort?: object | string | undefined; + offset?: number | undefined; + page?: number | undefined; + limit?: number | undefined; + customLabels?: CustomLabels | undefined; + /* If pagination is set to `false`, it will return all docs without adding limit condition. (Default: `true`) */ + pagination?: boolean | undefined; + allowDiskUse?: boolean | undefined; + countQuery?: object | undefined; + useFacet?: boolean | undefined; + } - interface QueryPopulateOptions { - /** space delimited path(s) to populate */ - path: string; - /** optional fields to select */ - select?: any; - /** optional query conditions to match */ - match?: any; - /** optional model to use for population */ - model?: string | Model | undefined; - /** optional query options like sort, limit, etc */ - options?: any; - /** deep populate */ - populate?: QueryPopulateOptions | QueryPopulateOptions[] | undefined; - } + interface QueryPopulateOptions { + /** space delimited path(s) to populate */ + path: string; + /** optional fields to select */ + select?: any; + /** optional query conditions to match */ + match?: any; + /** optional model to use for population */ + model?: string | Model | undefined; + /** optional query options like sort, limit, etc */ + options?: any; + /** deep populate */ + populate?: QueryPopulateOptions | QueryPopulateOptions[] | undefined; + } - interface AggregatePaginateResult { - docs: T[]; - totalDocs: number; - limit: number; - page?: number | undefined; - totalPages: number; - nextPage?: number | null | undefined; - prevPage?: number | null | undefined; - pagingCounter: number; - hasPrevPage: boolean; - hasNextPage: boolean; - meta?: any; - [customLabel: string]: T[] | number | boolean | null | undefined; - } + interface AggregatePaginateResult { + docs: T[]; + totalDocs: number; + limit: number; + page?: number | undefined; + totalPages: number; + nextPage?: number | null | undefined; + prevPage?: number | null | undefined; + pagingCounter: number; + hasPrevPage: boolean; + hasNextPage: boolean; + meta?: any; + [customLabel: string]: T[] | number | boolean | null | undefined; + } - interface AggregatePaginateModel extends Model { - aggregatePaginate( - query?: Aggregate | PipelineStage[], - options?: PaginateOptions, - callback?: (err: any, result: AggregatePaginateResult) => void, - ): Promise>; - } + interface AggregatePaginateModel extends Model { + aggregatePaginate( + query?: Aggregate | PipelineStage[], + options?: PaginateOptions, + callback?: (err: any, result: AggregatePaginateResult) => void + ): Promise>; + } - function model(name: string, schema?: Schema, collection?: string, skipInit?: boolean): AggregatePaginateModel; + function model(name: string, schema?: Schema, collection?: string, skipInit?: boolean): AggregatePaginateModel; } import mongoose from "mongoose"; @@ -80,5 +80,5 @@ declare function mongooseAggregatePaginate(schema: mongoose.Schema): void; export default mongooseAggregatePaginate; declare namespace _ { - const aggregatePaginate: { options: mongoose.PaginateOptions }; + const aggregatePaginate: { options: mongoose.PaginateOptions }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3adf56..76d6363 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -218,6 +218,12 @@ importers: specifier: ^2.27.5 version: 2.27.5(eslint@8.38.0) + plugins/mongoose-aggregate-paginate-v2: + dependencies: + mongoose: + specifier: '>=7.0.0' + version: 7.0.0 + plugins/mongoose-audit: dependencies: deep-diff: @@ -3137,6 +3143,17 @@ packages: resolution: {integrity: sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==} dev: false + /@types/webidl-conversions@7.0.3: + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + dev: false + + /@types/whatwg-url@8.2.2: + resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==} + dependencies: + '@types/node': 18.15.11 + '@types/webidl-conversions': 7.0.3 + dev: false + /@types/yargs-parser@21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: false @@ -3562,6 +3579,11 @@ packages: deprecated: Fixed a critical issue with BSON serialization documented in CVE-2019-2391, see https://bit.ly/2KcpXdo for more details dev: false + /bson@5.5.1: + resolution: {integrity: sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==} + engines: {node: '>=14.20.1'} + dev: false + /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: false @@ -5355,6 +5377,14 @@ packages: engines: {node: '>= 0.10'} dev: false + /ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + dev: false + /ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -6076,6 +6106,10 @@ packages: argparse: 2.0.1 dev: true + /jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + dev: false + /jsesc@0.5.0: resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} hasBin: true @@ -6139,6 +6173,11 @@ packages: resolution: {integrity: sha512-SsR+TZe595qXYzbWS5KWHBt4mM5h1MA7HFXp3oZnPkunxjaymx0fKhB8cxl6/R7Qm8aFXnI6J7DnyxV/QUSKLA==} dev: false + /kareem@2.5.1: + resolution: {integrity: sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==} + engines: {node: '>=12.0.0'} + dev: false + /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -6354,6 +6393,11 @@ packages: engines: {node: '>= 0.6'} dev: true + /memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + dev: false + optional: true + /meow@8.1.2: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} engines: {node: '>=10'} @@ -6454,6 +6498,13 @@ packages: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} dev: false + /mongodb-connection-string-url@2.6.0: + resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==} + dependencies: + '@types/whatwg-url': 8.2.2 + whatwg-url: 11.0.0 + dev: false + /mongodb-core@3.0.1: resolution: {integrity: sha512-aEy7iaynWVkydrkE9vtRffQ0RZiETsbhmqo6p5dIyB3sin3SrKXJruj16bxi87chF++S2QLnUyQ1i+dZn4GdJw==} dependencies: @@ -6468,6 +6519,28 @@ packages: mongodb-core: 3.0.1 dev: false + /mongodb@5.1.0: + resolution: {integrity: sha512-qgKb7y+EI90y4weY3z5+lIgm8wmexbonz0GalHkSElQXVKtRuwqXuhXKccyvIjXCJVy9qPV82zsinY0W1FBnJw==} + engines: {node: '>=14.20.1'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.201.0 + mongodb-client-encryption: ^2.3.0 + snappy: ^7.2.2 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + dependencies: + bson: 5.5.1 + mongodb-connection-string-url: 2.6.0 + socks: 2.8.3 + optionalDependencies: + saslprep: 1.0.3 + dev: false + /mongoose-legacy-pluralize@1.0.1(mongoose@5.0.0): resolution: {integrity: sha512-X5/N3sNj1p+y7Bg1vouQdST1vkInEzNAwqVjfDpNrhnugih2p2rV7jLrrb71sbQUPMJPm0Hhe6rH5fQV1Ve4XQ==} peerDependencies: @@ -6495,10 +6568,33 @@ packages: - supports-color dev: false + /mongoose@7.0.0: + resolution: {integrity: sha512-U0YPURDld+k/nvvSG1mRClQSjZMRXwQKSU5yb9PslRnOmVz0UlBD7SjSnjUuGT0yk+7BH+kJNimsKqMxYAKkMA==} + engines: {node: '>=14.0.0'} + dependencies: + bson: 5.5.1 + kareem: 2.5.1 + mongodb: 5.1.0 + mpath: 0.9.0 + mquery: 5.0.0 + ms: 2.1.3 + sift: 16.0.1 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - mongodb-client-encryption + - snappy + - supports-color + dev: false + /mpath@0.3.0: resolution: {integrity: sha512-43/pbEQ9nmJ+lklKWsK5gtCf9QC5b9lYTJvGtTHXzyqWHHJ1BSkyvfYd7ICoPJB5CF+7yTnGBEIdCB2AqHpS3g==} dev: false + /mpath@0.9.0: + resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==} + engines: {node: '>=4.0.0'} + dev: false + /mquery@3.0.0-rc0: resolution: {integrity: sha512-tEAVSvlmd22irKJ8Q/tyI0LKRv8cV3aEkQ/EHW391ktGRWDDlfcpZyq6GYqu8yXGoz2JkC4aMJdNGca19wU1NQ==} dependencies: @@ -6510,6 +6606,15 @@ packages: - supports-color dev: false + /mquery@5.0.0: + resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==} + engines: {node: '>=14.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -7032,7 +7137,6 @@ packages: /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} - dev: true /pure-rand@6.0.1: resolution: {integrity: sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==} @@ -7351,6 +7455,15 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: false + /saslprep@1.0.3: + resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + sparse-bitfield: 3.0.3 + dev: false + optional: true + /selenium-webdriver@4.0.0-rc-1: resolution: {integrity: sha512-bcrwFPRax8fifRP60p7xkWDGSJJoMkPAzufMlk5K2NyLPht/YZzR2WcIk1+3gR8VOCLlst1P2PI+MXACaFzpIw==} engines: {node: '>= 10.15.0'} @@ -7517,6 +7630,10 @@ packages: object-inspect: 1.13.1 dev: true + /sift@16.0.1: + resolution: {integrity: sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==} + dev: false + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -7560,6 +7677,11 @@ packages: resolution: {integrity: sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==} dev: false + /smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + dev: false + /snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: @@ -7567,6 +7689,14 @@ packages: tslib: 2.5.0 dev: false + /socks@2.8.3: + resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + dev: false + /source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} dependencies: @@ -7579,6 +7709,13 @@ packages: engines: {node: '>=0.10.0'} dev: false + /sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + dependencies: + memory-pager: 1.5.0 + dev: false + optional: true + /spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: @@ -7611,6 +7748,10 @@ packages: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: false + /sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + dev: false + /stack-chain@1.3.7: resolution: {integrity: sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==} dev: false @@ -7846,6 +7987,13 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false + /tr46@3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} + dependencies: + punycode: 2.3.0 + dev: false + /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} @@ -8164,6 +8312,11 @@ packages: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + dev: false + /websocket-driver@0.7.4: resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} engines: {node: '>=0.8.0'} @@ -8182,6 +8335,14 @@ packages: resolution: {integrity: sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==} dev: false + /whatwg-url@11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + dev: false + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: From 30f9c96cfa9d45385c2832abeed2edc82c282b15 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:14:47 +0530 Subject: [PATCH 03/15] Test: fixed import err --- plugins/mongoose-aggregate-paginate-v2/test/index.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js index 979c0f1..7244af0 100644 --- a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -1,6 +1,8 @@ "use strict"; -import mongoose from "mongoose"; +import { default as mongoose } from "mongoose"; +import { exec } from "child_process"; +import { promisify } from "util"; import mongooseAggregatePaginate from "../src"; const execute = promisify(exec); From 47dfcdac0300f25884db7a8438e635feaaf2bb4a Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:15:38 +0530 Subject: [PATCH 04/15] Chore: updated lockfile --- pnpm-lock.yaml | 89 ++++++++++---------------------------------------- 1 file changed, 17 insertions(+), 72 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9b0b2d..0477488 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -6442,12 +6442,7 @@ packages: /memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} -<<<<<<< HEAD - dev: false -======= requiresBuild: true - dev: true ->>>>>>> origin/development optional: true /meow@8.1.2: @@ -6550,7 +6545,6 @@ packages: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} dev: false -<<<<<<< HEAD /mongodb-connection-string-url@2.6.0: resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==} dependencies: @@ -6558,19 +6552,8 @@ packages: whatwg-url: 11.0.0 dev: false - /mongodb-core@3.0.1: - resolution: {integrity: sha512-aEy7iaynWVkydrkE9vtRffQ0RZiETsbhmqo6p5dIyB3sin3SrKXJruj16bxi87chF++S2QLnUyQ1i+dZn4GdJw==} - dependencies: - bson: 1.0.9 - require_optional: 1.0.1 - dev: false - - /mongodb@3.0.1: - resolution: {integrity: sha512-JaPqe+qN1f0m6bLzpNsp4tsNNasPa6+G3CW/rUtJUtFIxaGf9cD+FD8TTe8cqk1uWWhTWl7kVZoaUdvkoOKE5w==} -======= /mongodb@3.7.4: resolution: {integrity: sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==} ->>>>>>> origin/development engines: {node: '>=4'} peerDependencies: aws4: '*' @@ -6602,7 +6585,6 @@ packages: saslprep: 1.0.3 dev: true -<<<<<<< HEAD /mongodb@5.1.0: resolution: {integrity: sha512-qgKb7y+EI90y4weY3z5+lIgm8wmexbonz0GalHkSElQXVKtRuwqXuhXKccyvIjXCJVy9qPV82zsinY0W1FBnJw==} engines: {node: '>=14.20.1'} @@ -6625,12 +6607,8 @@ packages: saslprep: 1.0.3 dev: false - /mongoose-legacy-pluralize@1.0.1(mongoose@5.0.0): - resolution: {integrity: sha512-X5/N3sNj1p+y7Bg1vouQdST1vkInEzNAwqVjfDpNrhnugih2p2rV7jLrrb71sbQUPMJPm0Hhe6rH5fQV1Ve4XQ==} -======= /mongoose-legacy-pluralize@1.0.2(mongoose@5.13.22): resolution: {integrity: sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==} ->>>>>>> origin/development peerDependencies: mongoose: '*' dependencies: @@ -6665,25 +6643,6 @@ packages: - supports-color dev: true - /mpath@0.8.4: - resolution: {integrity: sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==} - engines: {node: '>=4.0.0'} - dev: true - - /mquery@3.2.5: - resolution: {integrity: sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==} - engines: {node: '>=4.0.0'} - dependencies: - bluebird: 3.5.1 - debug: 3.1.0 - regexp-clone: 1.0.0 - safe-buffer: 5.1.2 - sliced: 1.0.1 - transitivePeerDependencies: - - supports-color -<<<<<<< HEAD - dev: false - /mongoose@7.0.0: resolution: {integrity: sha512-U0YPURDld+k/nvvSG1mRClQSjZMRXwQKSU5yb9PslRnOmVz0UlBD7SjSnjUuGT0yk+7BH+kJNimsKqMxYAKkMA==} engines: {node: '>=14.0.0'} @@ -6702,28 +6661,28 @@ packages: - supports-color dev: false - /mpath@0.3.0: - resolution: {integrity: sha512-43/pbEQ9nmJ+lklKWsK5gtCf9QC5b9lYTJvGtTHXzyqWHHJ1BSkyvfYd7ICoPJB5CF+7yTnGBEIdCB2AqHpS3g==} - dev: false + /mpath@0.8.4: + resolution: {integrity: sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==} + engines: {node: '>=4.0.0'} + dev: true /mpath@0.9.0: resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==} engines: {node: '>=4.0.0'} dev: false - /mquery@3.0.0-rc0: - resolution: {integrity: sha512-tEAVSvlmd22irKJ8Q/tyI0LKRv8cV3aEkQ/EHW391ktGRWDDlfcpZyq6GYqu8yXGoz2JkC4aMJdNGca19wU1NQ==} + /mquery@3.2.5: + resolution: {integrity: sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==} + engines: {node: '>=4.0.0'} dependencies: - bluebird: 3.5.0 - debug: 2.6.9 - regexp-clone: 0.0.1 - sliced: 0.0.5 + bluebird: 3.5.1 + debug: 3.1.0 + regexp-clone: 1.0.0 + safe-buffer: 5.1.2 + sliced: 1.0.1 transitivePeerDependencies: - supports-color - dev: false -======= dev: true ->>>>>>> origin/development /mquery@5.0.0: resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==} @@ -7583,11 +7542,6 @@ packages: requiresBuild: true dependencies: sparse-bitfield: 3.0.3 -<<<<<<< HEAD - dev: false -======= - dev: true ->>>>>>> origin/development optional: true /selenium-webdriver@4.0.0-rc-1: @@ -7756,15 +7710,13 @@ packages: object-inspect: 1.13.1 dev: true -<<<<<<< HEAD - /sift@16.0.1: - resolution: {integrity: sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==} - dev: false -======= /sift@13.5.2: resolution: {integrity: sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==} dev: true ->>>>>>> origin/development + + /sift@16.0.1: + resolution: {integrity: sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==} + dev: false /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -7839,16 +7791,9 @@ packages: /sparse-bitfield@3.0.3: resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} -<<<<<<< HEAD - dependencies: - memory-pager: 1.5.0 - dev: false -======= requiresBuild: true dependencies: memory-pager: 1.5.0 - dev: true ->>>>>>> origin/development optional: true /spdx-correct@3.2.0: From 9024f9efc970748f4111dff4644739e190f03c71 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:18:34 +0530 Subject: [PATCH 05/15] Chore: updated test before all --- .../test/index.test.js | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js index 7244af0..4f42d3d 100644 --- a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -8,10 +8,7 @@ import mongooseAggregatePaginate from "../src"; const execute = promisify(exec); const connectToDatabase = async () => { - await execute("docker run -d -p 27017:27017 mongo:5.0") - await new Promise((resolve) => setTimeout(resolve, 3000)) - await mongoose.connect("mongodb://localhost:27017/test") - await mongoose.connection.db.dropDatabase(); + }; const AuthorSchema = new mongoose.Schema({ @@ -33,30 +30,29 @@ BookSchema.plugin(mongooseAggregatePaginate); const Book = mongoose.model("Book", BookSchema); -beforeAll(connectToDatabase); - -describe("mongoose-paginate", function () { - beforeAll(function () { - let book, - books = []; - let date = new Date(); - return Author.create({ - name: "Arthur Conan Doyle" - }).then(function (author) { - for (let i = 1; i <= 100; i++) { - book = new Book({ - title: "Book #" + i, - date: new Date(date.getTime() + i), - author: author._id - }); - books.push(book); - } - return Book.create(books); - }); +beforeAll(async () => { + await execute("docker run -d -p 27017:27017 mongo:5.0") + await new Promise((resolve) => setTimeout(resolve, 3000)) + await mongoose.connect("mongodb://localhost:27017/test") + await mongoose.connection.db.dropDatabase(); + let book, books = []; + const date = new Date(); + await Author.create({ + name: "Arthur Conan Doyle" + }).then(async function (author) { + for (let i = 1; i <= 100; i++) { + book = new Book({ + title: "Book #" + i, + date: new Date(date.getTime() + i), + author: author._id + }); + books.push(book); + } + await Book.create(books); }); +}); - afterEach(function () { }); - +describe("mongoose-paginate", function () { it("promise return test", function () { var aggregate = Book.aggregate([ { From 8f157cedbdc5a30fd59a4287fd3acb23b66eafb6 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:20:15 +0530 Subject: [PATCH 06/15] Test: increased timeout --- plugins/mongoose-aggregate-paginate-v2/test/index.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js index 4f42d3d..ad6f979 100644 --- a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -5,6 +5,8 @@ import { exec } from "child_process"; import { promisify } from "util"; import mongooseAggregatePaginate from "../src"; +jest.setTimeout(120000) + const execute = promisify(exec); const connectToDatabase = async () => { From 39f80d353bc0e77c57ad577dce9659c832f6771f Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:22:03 +0530 Subject: [PATCH 07/15] Test: removed after all hooks --- .../test/index.test.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js index ad6f979..fcd2a1e 100644 --- a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -9,10 +9,6 @@ jest.setTimeout(120000) const execute = promisify(exec); -const connectToDatabase = async () => { - -}; - const AuthorSchema = new mongoose.Schema({ name: String }); @@ -269,12 +265,4 @@ describe("mongoose-paginate", function () { }); }); }); - - afterAll(async function () { - await mongoose.connection.db.dropDatabase(); - }); - - afterAll(async function () { - await mongoose.disconnect(); - }); }); From 30927fa3a21ad675264e3d7f501d55ca909d144b Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:32:01 +0530 Subject: [PATCH 08/15] Fix: default pagination without prepagination --- .../src/core.js | 385 +++++++++--------- .../test/index.test.js | 10 +- 2 files changed, 200 insertions(+), 195 deletions(-) diff --git a/plugins/mongoose-aggregate-paginate-v2/src/core.js b/plugins/mongoose-aggregate-paginate-v2/src/core.js index 5e8f9f3..456dcba 100644 --- a/plugins/mongoose-aggregate-paginate-v2/src/core.js +++ b/plugins/mongoose-aggregate-paginate-v2/src/core.js @@ -7,210 +7,215 @@ */ const defaultOptions = { - customLabels: { - totalDocs: "totalDocs", - limit: "limit", - page: "page", - totalPages: "totalPages", - docs: "docs", - nextPage: "nextPage", - prevPage: "prevPage", - pagingCounter: "pagingCounter", - hasPrevPage: "hasPrevPage", - hasNextPage: "hasNextPage", - meta: null - }, - collation: {}, - lean: false, - leanWithId: true, - limit: 10, - projection: {}, - select: "", - options: {}, - pagination: true, - countQuery: null, - useFacet: true + customLabels: { + totalDocs: "totalDocs", + limit: "limit", + page: "page", + totalPages: "totalPages", + docs: "docs", + nextPage: "nextPage", + prevPage: "prevPage", + pagingCounter: "pagingCounter", + hasPrevPage: "hasPrevPage", + hasNextPage: "hasNextPage", + meta: null + }, + collation: {}, + lean: false, + leanWithId: true, + limit: 10, + projection: {}, + select: "", + options: {}, + pagination: true, + countQuery: null, + useFacet: true }; export const PREPAGINATION_PLACEHOLDER = "__PREPAGINATE__"; export function aggregatePaginate(query, options, callback) { - options = { - ...defaultOptions, - ...aggregatePaginate.options, - ...options - }; - - const pipeline = Array.isArray(query) ? query : query._pipeline; - - const customLabels = { - ...defaultOptions.customLabels, - ...options.customLabels - }; - - const defaultLimit = 10; - - // Custom Labels - const labelTotal = customLabels.totalDocs; - const labelLimit = customLabels.limit; - const labelPage = customLabels.page; - const labelTotalPages = customLabels.totalPages; - const labelDocs = customLabels.docs; - const labelNextPage = customLabels.nextPage; - const labelPrevPage = customLabels.prevPage; - const labelHasNextPage = customLabels.hasNextPage; - const labelHasPrevPage = customLabels.hasPrevPage; - const labelPagingCounter = customLabels.pagingCounter; - const labelMeta = customLabels.meta; - - let page = parseInt(options.page || 1, 10) || 1; - let limit = parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : defaultLimit; - - // const skip = (page - 1) * limit; - let skip; - let offset; - - if (Object.prototype.hasOwnProperty.call(options, "offset")) { - offset = Math.abs(parseInt(options.offset, 10)); - skip = offset; - } else if (Object.prototype.hasOwnProperty.call(options, "page")) { - page = Math.abs(parseInt(options.page, 10)) || 1; - skip = (page - 1) * limit; - } else { - offset = 0; - page = 1; - skip = offset; - } - - const sort = options.sort; - const allowDiskUse = options.allowDiskUse || false; - const isPaginationEnabled = options.pagination === false ? false : true; - - const q = this.aggregate(); - - if (allowDiskUse) { - q.allowDiskUse(true); - } - - if (sort) { - pipeline.push({ $sort: sort }); - } - - function constructPipelines() { - let cleanedPipeline = pipeline.filter((stage) => stage !== PREPAGINATION_PLACEHOLDER); - - const countPipeline = [...cleanedPipeline, { $count: "count" }]; - - if (isPaginationEnabled) { - cleanedPipeline = pipeline.flatMap((stage) => { - if (stage === PREPAGINATION_PLACEHOLDER) { - return [{ $skip: skip }, { $limit: limit }]; - } - return stage; - }); + options = { + ...defaultOptions, + ...aggregatePaginate.options, + ...options + }; + + const pipeline = Array.isArray(query) ? query : query._pipeline; + + const customLabels = { + ...defaultOptions.customLabels, + ...options.customLabels + }; + + const defaultLimit = 10; + + // Custom Labels + const labelTotal = customLabels.totalDocs; + const labelLimit = customLabels.limit; + const labelPage = customLabels.page; + const labelTotalPages = customLabels.totalPages; + const labelDocs = customLabels.docs; + const labelNextPage = customLabels.nextPage; + const labelPrevPage = customLabels.prevPage; + const labelHasNextPage = customLabels.hasNextPage; + const labelHasPrevPage = customLabels.hasPrevPage; + const labelPagingCounter = customLabels.pagingCounter; + const labelMeta = customLabels.meta; + + let page = parseInt(options.page || 1, 10) || 1; + let limit = parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : defaultLimit; + + // const skip = (page - 1) * limit; + let skip; + let offset; + + if (Object.prototype.hasOwnProperty.call(options, "offset")) { + offset = Math.abs(parseInt(options.offset, 10)); + skip = offset; + } else if (Object.prototype.hasOwnProperty.call(options, "page")) { + page = Math.abs(parseInt(options.page, 10)) || 1; + skip = (page - 1) * limit; + } else { + offset = 0; + page = 1; + skip = offset; } - return [cleanedPipeline, countPipeline]; - } - let promise; + const sort = options.sort; + const allowDiskUse = options.allowDiskUse || false; + const isPaginationEnabled = options.pagination === false ? false : true; - if (options.useFacet && !options.countQuery) { - const [pipeline, countPipeline] = constructPipelines(); - promise = q - .facet({ - docs: pipeline, - count: countPipeline - }) - .then(([{ docs, count }]) => [docs, count]); - } else { - const [pipeline] = constructPipelines(); - - const countQuery = options.countQuery ? options.countQuery : this.aggregate(pipeline); + const q = this.aggregate(); if (allowDiskUse) { - countQuery.allowDiskUse(true); + q.allowDiskUse(true); } - promise = Promise.all([ - this.aggregate(pipeline).exec(), - countQuery - .group({ - _id: null, - count: { - $sum: 1 - } - }) - .exec() - ]); - } + if (sort) { + pipeline.push({ $sort: sort }); + } - return promise - .then(function (values) { - const count = values[1][0] ? values[1][0].count : 0; + function constructPipelines() { + let cleanedPipeline = pipeline.filter((stage) => stage !== PREPAGINATION_PLACEHOLDER); + + const countPipeline = [...cleanedPipeline, { $count: "count" }]; + + if (isPaginationEnabled) { + let foundPrepagination = false; + cleanedPipeline = pipeline.flatMap((stage) => { + if (stage === PREPAGINATION_PLACEHOLDER) { + foundPrepagination = true; + return [{ $skip: skip }, { $limit: limit }]; + } + return stage; + }); + if (!foundPrepagination) { + cleanedPipeline.push({ $skip: skip }, { $limit: limit }); + } + } + return [cleanedPipeline, countPipeline]; + } - if (isPaginationEnabled === false) { - limit = count; - page = 1; - } - - const pages = Math.ceil(count / limit) || 1; - - let result = { - [labelDocs]: values[0] - }; - - const meta = { - [labelTotal]: count, - [labelLimit]: limit, - [labelPage]: page, - [labelTotalPages]: pages, - [labelPagingCounter]: (page - 1) * limit + 1, - [labelHasPrevPage]: false, - [labelHasNextPage]: false - }; - - if (typeof offset !== "undefined") { - page = Math.ceil((offset + 1) / limit); - - meta.offset = offset; - meta[labelPage] = Math.ceil((offset + 1) / limit); - meta[labelPagingCounter] = offset + 1; - } - - // Set prev page - if (page > 1) { - meta[labelHasPrevPage] = true; - meta[labelPrevPage] = page - 1; - } else { - meta[labelPrevPage] = null; - } - - // Set next page - if (page < pages) { - meta[labelHasNextPage] = true; - meta[labelNextPage] = page + 1; - } else { - meta[labelNextPage] = null; - } - - if (labelMeta) { - result[labelMeta] = meta; - } else { - result = Object.assign(result, meta); - } - - if (typeof callback === "function") { - return callback(null, result); - } - - return Promise.resolve(result); - }) - .catch(function (reject) { - if (typeof callback === "function") { - return callback(reject); - } - return Promise.reject(reject); - }); + let promise; + + if (options.useFacet && !options.countQuery) { + const [pipeline, countPipeline] = constructPipelines(); + promise = q + .facet({ + docs: pipeline, + count: countPipeline + }) + .then(([{ docs, count }]) => [docs, count]); + } else { + const [pipeline] = constructPipelines(); + + const countQuery = options.countQuery ? options.countQuery : this.aggregate(pipeline); + + if (allowDiskUse) { + countQuery.allowDiskUse(true); + } + + promise = Promise.all([ + this.aggregate(pipeline).exec(), + countQuery + .group({ + _id: null, + count: { + $sum: 1 + } + }) + .exec() + ]); + } + + return promise + .then(function (values) { + const count = values[1][0] ? values[1][0].count : 0; + + if (isPaginationEnabled === false) { + limit = count; + page = 1; + } + + const pages = Math.ceil(count / limit) || 1; + + let result = { + [labelDocs]: values[0] + }; + + const meta = { + [labelTotal]: count, + [labelLimit]: limit, + [labelPage]: page, + [labelTotalPages]: pages, + [labelPagingCounter]: (page - 1) * limit + 1, + [labelHasPrevPage]: false, + [labelHasNextPage]: false + }; + + if (typeof offset !== "undefined") { + page = Math.ceil((offset + 1) / limit); + + meta.offset = offset; + meta[labelPage] = Math.ceil((offset + 1) / limit); + meta[labelPagingCounter] = offset + 1; + } + + // Set prev page + if (page > 1) { + meta[labelHasPrevPage] = true; + meta[labelPrevPage] = page - 1; + } else { + meta[labelPrevPage] = null; + } + + // Set next page + if (page < pages) { + meta[labelHasNextPage] = true; + meta[labelNextPage] = page + 1; + } else { + meta[labelNextPage] = null; + } + + if (labelMeta) { + result[labelMeta] = meta; + } else { + result = Object.assign(result, meta); + } + + if (typeof callback === "function") { + return callback(null, result); + } + + return Promise.resolve(result); + }) + .catch(function (reject) { + if (typeof callback === "function") { + return callback(reject); + } + return Promise.reject(reject); + }); } export default aggregatePaginate; diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js index fcd2a1e..d779fb8 100644 --- a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -64,7 +64,7 @@ describe("mongoose-paginate", function () { let promise = aggregate.paginateExec({}); // let promise = Book.aggregatePaginate(aggregate, {}); - expect(promise.then).to.be.an.instanceof(Function); + expect(promise.then).toBeInstanceOf(Function); }); it("callback test", function (done) { @@ -79,8 +79,8 @@ describe("mongoose-paginate", function () { ]); aggregate.paginateExec({}, function (err, result) { - expect(err).to.be.null; - expect(result).to.be.an.instanceOf(Object); + expect(err).toBeNull(); + expect(result).toBeInstanceOf(Object); done(); }); }); @@ -112,7 +112,7 @@ describe("mongoose-paginate", function () { ]) }; return Book.aggregatePaginate(aggregate, options).then((result) => { - expect(result.docs).to.have.length(10); + expect(result.docs).toHaveLength(10); expect(result.docs[0].title).toEqual("Book #41"); expect(result.totalDocs).toEqual(100); expect(result.limit).toEqual(10); @@ -152,7 +152,7 @@ describe("mongoose-paginate", function () { allowDiskUse: true }; return Book.aggregatePaginate(aggregate, options).then((result) => { - expect(result.docs).to.have.length(10); + expect(result.docs).toHaveLength(10); expect(result.docs[0].title).toEqual("Book #41"); expect(result.totalDocs).toEqual(100); expect(result.limit).toEqual(10); From 361358a3a7b5cd0f27a9b7955f2ac28f3c239dbf Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:33:56 +0530 Subject: [PATCH 09/15] Fix: test termination --- plugins/mongoose-aggregate-paginate-v2/test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js index d779fb8..ca93339 100644 --- a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -35,7 +35,7 @@ beforeAll(async () => { await mongoose.connection.db.dropDatabase(); let book, books = []; const date = new Date(); - await Author.create({ + return Author.create({ name: "Arthur Conan Doyle" }).then(async function (author) { for (let i = 1; i <= 100; i++) { From dcf2fda67d7839e837ad4fde3b655e11fd4a958d Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:41:36 +0530 Subject: [PATCH 10/15] Fix: test termination --- plugins/mongoose-aggregate-paginate-v2/test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js index ca93339..c157169 100644 --- a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -46,7 +46,7 @@ beforeAll(async () => { }); books.push(book); } - await Book.create(books); + return Book.create(books); }); }); From bb0378c49d2cae43986c70c869ccf7b56ff0b575 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:46:16 +0530 Subject: [PATCH 11/15] Fix: test termination --- plugins/mongoose-aggregate-paginate-v2/test/index.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js index c157169..90f1873 100644 --- a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -67,7 +67,7 @@ describe("mongoose-paginate", function () { expect(promise.then).toBeInstanceOf(Function); }); - it("callback test", function (done) { + it("callback test", async (done) =>{ var aggregate = Book.aggregate([ { $match: { @@ -78,7 +78,7 @@ describe("mongoose-paginate", function () { } ]); - aggregate.paginateExec({}, function (err, result) { + await aggregate.paginateExec({}, function (err, result) { expect(err).toBeNull(); expect(result).toBeInstanceOf(Object); done(); From dd10ad836c54745c2ecc38ccb76b0376c98f23d9 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:48:51 +0530 Subject: [PATCH 12/15] Fix: test termination --- .../test/index.test.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js index 90f1873..137fad5 100644 --- a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -32,7 +32,6 @@ beforeAll(async () => { await execute("docker run -d -p 27017:27017 mongo:5.0") await new Promise((resolve) => setTimeout(resolve, 3000)) await mongoose.connect("mongodb://localhost:27017/test") - await mongoose.connection.db.dropDatabase(); let book, books = []; const date = new Date(); return Author.create({ @@ -50,6 +49,12 @@ beforeAll(async () => { }); }); +afterAll(async () => { + await mongoose.disconnect() + await execute("docker stop $(docker ps -q)") + await execute("docker rm $(docker ps -aq)") +}); + describe("mongoose-paginate", function () { it("promise return test", function () { var aggregate = Book.aggregate([ @@ -67,7 +72,7 @@ describe("mongoose-paginate", function () { expect(promise.then).toBeInstanceOf(Function); }); - it("callback test", async (done) =>{ + it("callback test", (done) => { var aggregate = Book.aggregate([ { $match: { @@ -77,8 +82,7 @@ describe("mongoose-paginate", function () { } } ]); - - await aggregate.paginateExec({}, function (err, result) { + aggregate.paginateExec({}, function (err, result) { expect(err).toBeNull(); expect(result).toBeInstanceOf(Object); done(); From 302d510a78c9c48c585ed2ab86bec02d857feb2d Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:53:29 +0530 Subject: [PATCH 13/15] Chore: added detect open handles flag --- scripts/test/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test/test.sh b/scripts/test/test.sh index e3ba689..911492c 100644 --- a/scripts/test/test.sh +++ b/scripts/test/test.sh @@ -1 +1 @@ -shx cp ../../.babelrc ../../jest.config.js . && dotenv -- jest --coverage --verbose --runInBand --forceExit && rimraf .babelrc jest.config.js \ No newline at end of file +shx cp ../../.babelrc ../../jest.config.js . && dotenv -- jest --coverage --verbose --runInBand --forceExit --detectOpenHandles && rimraf .babelrc jest.config.js \ No newline at end of file From f01e791e4a655edf6cfc2553c6e69ca75a23297a Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:56:00 +0530 Subject: [PATCH 14/15] Fix: test termination --- plugins/mongoose-aggregate-paginate-v2/test/index.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js index 137fad5..b90d458 100644 --- a/plugins/mongoose-aggregate-paginate-v2/test/index.test.js +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -68,7 +68,6 @@ describe("mongoose-paginate", function () { ]); let promise = aggregate.paginateExec({}); - // let promise = Book.aggregatePaginate(aggregate, {}); expect(promise.then).toBeInstanceOf(Function); }); From 093be4188e3399592a06e5be0fe78228cdac5e93 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sat, 22 Jun 2024 22:58:02 +0530 Subject: [PATCH 15/15] Feat!: prepare for release --- plugins/mongoose-aggregate-paginate-v2/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mongoose-aggregate-paginate-v2/package.json b/plugins/mongoose-aggregate-paginate-v2/package.json index 391a5c6..c5c17b9 100644 --- a/plugins/mongoose-aggregate-paginate-v2/package.json +++ b/plugins/mongoose-aggregate-paginate-v2/package.json @@ -35,7 +35,7 @@ "paginator", "plugin" ], - "author": "Aravind NC (https://aravindnc.com)", + "author": "SLIIT FOSS", "license": "MIT", "bugs": { "url": "https://github.com/sliit-foss/npm-catalogue/issues"