diff --git a/plugins/mongoose-aggregate-paginate-v2/package.json b/plugins/mongoose-aggregate-paginate-v2/package.json new file mode 100644 index 00000000..c5c17b9f --- /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": "SLIIT FOSS", + "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 00000000..5d9cac29 --- /dev/null +++ b/plugins/mongoose-aggregate-paginate-v2/readme.md @@ -0,0 +1,331 @@ +# @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) 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 00000000..456dcba8 --- /dev/null +++ b/plugins/mongoose-aggregate-paginate-v2/src/core.js @@ -0,0 +1,221 @@ +/** + * 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 + }; + + 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) { + 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]; + } + + 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/src/index.js b/plugins/mongoose-aggregate-paginate-v2/src/index.js new file mode 100644 index 00000000..e4ff0043 --- /dev/null +++ b/plugins/mongoose-aggregate-paginate-v2/src/index.js @@ -0,0 +1,18 @@ +import mongoose from "mongoose"; +import aggregatePaginate, { 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); + }; +}; + +plugin.aggregatePaginate = aggregatePaginate; + +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 new file mode 100644 index 00000000..b90d4584 --- /dev/null +++ b/plugins/mongoose-aggregate-paginate-v2/test/index.test.js @@ -0,0 +1,271 @@ +"use strict"; + +import { default as mongoose } from "mongoose"; +import { exec } from "child_process"; +import { promisify } from "util"; +import mongooseAggregatePaginate from "../src"; + +jest.setTimeout(120000) + +const execute = promisify(exec); + +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); + +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") + let book, books = []; + const date = new Date(); + return 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); + } + return Book.create(books); + }); +}); + +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([ + { + $match: { + title: { + $in: [/Book/i] + } + } + } + ]); + + let promise = aggregate.paginateExec({}); + expect(promise.then).toBeInstanceOf(Function); + }); + + it("callback test", (done) => { + var aggregate = Book.aggregate([ + { + $match: { + title: { + $in: [/Book/i] + } + } + } + ]); + aggregate.paginateExec({}, function (err, result) { + expect(err).toBeNull(); + expect(result).toBeInstanceOf(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).toHaveLength(10); + expect(result.docs[0].title).toEqual("Book #41"); + expect(result.totalDocs).toEqual(100); + expect(result.limit).toEqual(10); + expect(result.page).toEqual(5); + expect(result.pagingCounter).toEqual(41); + expect(result.hasPrevPage).toEqual(true); + expect(result.hasNextPage).toEqual(true); + expect(result.prevPage).toEqual(4); + expect(result.nextPage).toEqual(6); + expect(result.totalPages).toEqual(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).toHaveLength(10); + expect(result.docs[0].title).toEqual("Book #41"); + expect(result.totalDocs).toEqual(100); + expect(result.limit).toEqual(10); + expect(result.page).toEqual(5); + expect(result.pagingCounter).toEqual(41); + expect(result.hasPrevPage).toEqual(true); + expect(result.hasNextPage).toEqual(true); + expect(result.prevPage).toEqual(4); + expect(result.nextPage).toEqual(6); + expect(result.totalPages).toEqual(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).toHaveLength(20); + expect(result.itemsList[0].title).toEqual("Book #81"); + expect(result.itemCount).toEqual(100); + expect(result.perPage).toEqual(20); + expect(result.currentPage).toEqual(5); + expect(result.pageCounter).toEqual(81); + expect(result.hasPrev).toEqual(true); + expect(result.hasNext).toEqual(false); + expect(result.prev).toEqual(4); + expect(result.next).toEqual(null); + expect(result.pageCount).toEqual(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).toHaveLength(20); + expect(result.itemsList[0].title).toEqual("Book #81"); + expect(result.itemCount).toEqual(100); + expect(result.perPage).toEqual(20); + expect(result.currentPage).toEqual(5); + expect(result.pageCounter).toEqual(81); + expect(result.hasPrev).toEqual(true); + expect(result.hasNext).toEqual(false); + expect(result.prev).toEqual(4); + expect(result.next).toEqual(null); + expect(result.pageCount).toEqual(5); + }); + }); + }); +}); 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 00000000..31fe5c3c --- /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/pnpm-lock.yaml b/pnpm-lock.yaml index 92b77dfd..0477488f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -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: @@ -3157,6 +3163,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 @@ -3582,6 +3599,11 @@ packages: engines: {node: '>=0.6.19'} dev: true + /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 @@ -5399,6 +5421,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'} @@ -6119,6 +6149,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 @@ -6182,6 +6216,11 @@ packages: resolution: {integrity: sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==} dev: true + /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'} @@ -6404,7 +6443,6 @@ packages: /memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} requiresBuild: true - dev: true optional: true /meow@8.1.2: @@ -6507,6 +6545,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@3.7.4: resolution: {integrity: sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==} engines: {node: '>=4'} @@ -6540,6 +6585,28 @@ packages: saslprep: 1.0.3 dev: true + /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.2(mongoose@5.13.22): resolution: {integrity: sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==} peerDependencies: @@ -6576,11 +6643,34 @@ packages: - supports-color dev: true + /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.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.2.5: resolution: {integrity: sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==} engines: {node: '>=4.0.0'} @@ -6594,6 +6684,15 @@ packages: - supports-color dev: true + /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==} dev: true @@ -7128,7 +7227,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==} @@ -7444,7 +7542,6 @@ packages: requiresBuild: true dependencies: sparse-bitfield: 3.0.3 - dev: true optional: true /selenium-webdriver@4.0.0-rc-1: @@ -7617,6 +7714,10 @@ packages: resolution: {integrity: sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==} 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==} @@ -7656,6 +7757,11 @@ packages: resolution: {integrity: sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==} dev: true + /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: @@ -7663,6 +7769,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: @@ -7680,7 +7794,6 @@ packages: requiresBuild: true dependencies: memory-pager: 1.5.0 - dev: true optional: true /spdx-correct@3.2.0: @@ -7715,6 +7828,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 @@ -7949,6 +8066,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'} @@ -8267,6 +8391,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'} @@ -8285,6 +8414,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: diff --git a/scripts/test/test.sh b/scripts/test/test.sh index e3ba689e..911492c7 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