├── .editorconfig ├── .gitignore ├── .npmignore ├── .npmrc ├── .nvmrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docker-compose.yml ├── package-lock.json ├── package.json ├── src ├── index.js └── types.d.ts ├── test └── index.js ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | .vscode 29 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | node_modules 3 | coverage 4 | test 5 | CHANGELOG.md 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v10 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | services: 3 | - mysql 4 | cache: 5 | directories: 6 | - ~/.npm 7 | - .nyc_output 8 | node_js: 9 | - "10" 10 | notifications: 11 | email: false 12 | stages: 13 | - lint 14 | - test 15 | - coverage 16 | - name: deploy 17 | if: branch = master 18 | jobs: 19 | include: 20 | - stage: lint 21 | name: eslint 22 | script: npx eslint . 23 | - stage: lint 24 | name: commitlint 25 | before_script: 26 | - npm i -g @commitlint/travis-cli 27 | script: commitlint-travis 28 | - stage: test 29 | node_js: 30 | - "10" 31 | - "8" 32 | env: 33 | - DATABASE=mysql://root@localhost/test 34 | before_install: 35 | - mysql -e 'CREATE DATABASE IF NOT EXISTS test;' 36 | script: npm t 37 | - stage: coverage 38 | before_script: 39 | - npm i -g coveralls codeclimate-test-reporter 40 | script: 41 | - npx nyc check-coverage --lines 100 --per-file 42 | after_success: 43 | - npx nyc report > lcov.info 44 | - coveralls < lcov.info 45 | - codeclimate-test-reporter < lcov.info 46 | - stage: deploy 47 | script: npx semantic-release 48 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.1.6](https://github.com/eclass/sequelize-paginate/compare/v1.1.5...v1.1.6) (2019-04-10) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * allow for inclusion of scope by [@dsmackie](https://github.com/dsmackie) ([5861f9f](https://github.com/eclass/sequelize-paginate/commit/5861f9f)), closes [#46](https://github.com/eclass/sequelize-paginate/issues/46) 7 | 8 | ## [1.1.5](https://github.com/eclass/sequelize-paginate/compare/v1.1.4...v1.1.5) (2019-03-01) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * **typescript:** add types.d.ts ([a0af447](https://github.com/eclass/sequelize-paginate/commit/a0af447)) 14 | 15 | ## [1.1.4](https://github.com/eclass/sequelize-paginate/compare/v1.1.3...v1.1.4) (2018-12-03) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * **count:** remove include from options ([343a0e5](https://github.com/eclass/sequelize-paginate/commit/343a0e5)) 21 | * **pagination:** set page to 1 and paginate to 25 by default ([bf44449](https://github.com/eclass/sequelize-paginate/commit/bf44449)) 22 | 23 | ## [1.1.3](https://github.com/eclass/sequelize-paginate/compare/v1.1.2...v1.1.3) (2018-09-14) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * **options:** add warning if provide limit or offset ([206a8ae](https://github.com/eclass/sequelize-paginate/commit/206a8ae)), closes [#1](https://github.com/eclass/sequelize-paginate/issues/1) 29 | 30 | #### 1.1.2 (2018-05-24) 31 | 32 | ##### Bug Fixes 33 | 34 | * **count:** remove attributes in count query ([96026406](https://github.com/eclass/sequelize-paginate/commit/96026406ae7d6f903fbe6911cc98f8af70a1f6a6)) 35 | 36 | #### 1.1.1 (2018-05-23) 37 | 38 | ##### Bug Fixes 39 | 40 | * **query:** enable all query options ([65858084](https://github.com/eclass/sequelize-paginate/commit/65858084bfc0688ced18986fe5aa0f89398d1032)) 41 | 42 | ### 1.1.0 (2018-05-23) 43 | 44 | ##### Chores 45 | 46 | * **package:** update dependencies ([4ad6ba00](https://github.com/eclass/sequelize-paginate/commit/4ad6ba00b00b27e3f1395820396e5702712b6086)) 47 | 48 | ##### New Features 49 | 50 | * **pagination:** add export total docs ([8ea5bfa3](https://github.com/eclass/sequelize-paginate/commit/8ea5bfa3442eec0c51c04f53cd751d88a5674094)) 51 | 52 | ## 1.0.0 (2018-05-07) 53 | 54 | ##### Chores 55 | 56 | * **project:** first commit ([06ca9148](https://github.com/eclass/sequelize-paginate/commit/06ca9148f83c2a32f27270036e189b089a4c1655)) 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Leonardo Gatica 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sequelize-paginate 2 | 3 | [![npm version](https://img.shields.io/npm/v/sequelize-paginate.svg)](https://www.npmjs.com/package/sequelize-paginate) 4 | [![npm downloads](https://img.shields.io/npm/dm/sequelize-paginate.svg)](https://www.npmjs.com/package/sequelize-paginate) 5 | [![Build Status](https://travis-ci.org/eclass/sequelize-paginate.svg?branch=master)](https://travis-ci.org/eclass/sequelize-paginate) 6 | [![devDependency Status](https://img.shields.io/david/dev/eclass/sequelize-paginate.svg)](https://david-dm.org/eclass/sequelize-paginate#info=devDependencies) 7 | 8 | > Sequelize model plugin for add paginate method 9 | 10 | ## Installation 11 | 12 | ```bash 13 | npm i sequelize-paginate 14 | ``` 15 | 16 | ## Use 17 | 18 | ```js 19 | // model.js 20 | const sequelizePaginate = require('sequelize-paginate') 21 | 22 | module.exports = (sequelize, DataTypes) => { 23 | const MyModel = sequelize.define( 24 | 'MyModel', 25 | { 26 | name: { type: DataTypes.STRING(255) } 27 | } 28 | ) 29 | sequelizePaginate.paginate(MyModel) 30 | return MyModel 31 | } 32 | 33 | // controller.js 34 | const { Op } = db.sequelize 35 | // Default page = 1 and paginate = 25 36 | const { docs, pages, total } = await db.MyModel.paginate() 37 | // Or with extra options 38 | const options = { 39 | attributes: ['id', 'name'], 40 | page: 1, // Default 1 41 | paginate: 25, // Default 25 42 | order: [['name', 'DESC']], 43 | where: { name: { [Op.like]: `%elliot%` } } 44 | } 45 | const { docs, pages, total } = await db.MyModel.paginate(options) 46 | ``` 47 | 48 | **NOTE:** _If **options** include **limit** or **offset** are ignored._ 49 | 50 | ## License 51 | 52 | [MIT](https://tldrlegal.com/license/mit-license) 53 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | db: 5 | image: mysql:5.7.34 6 | environment: 7 | MYSQL_DATABASE: test 8 | MYSQL_ROOT_PASSWORD: root 9 | ports: 10 | - 3306:3306 11 | mycli: 12 | image: lgatica/mycli 13 | links: 14 | - db 15 | depends_on: 16 | - db 17 | environment: 18 | DATABASE: mysql://root:root@db/test 19 | entrypoint: /bin/sh 20 | tty: true 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sequelize-paginate", 3 | "version": "1.1.6", 4 | "description": "Sequelize model plugin for add paginate method", 5 | "main": "src", 6 | "types": "src/types.d.ts", 7 | "scripts": { 8 | "lint:js": "eslint . --fix", 9 | "format:js": "prettier-standard '{src,test}/**/*.js'", 10 | "lint:ts": "tslint --project . --fix '{src,test}/**/*.ts'", 11 | "format:ts": "prettier-standard '{src,test}/**/*.ts'", 12 | "lint": "npm run lint:js && npm run lint:ts", 13 | "format": "npm run format:js && npm run format:ts", 14 | "test": "nyc mocha test --exit --no-timeouts", 15 | "commit": "commit", 16 | "travis-deploy-once": "travis-deploy-once", 17 | "semantic-release": "semantic-release", 18 | "ts-compile-check": "tsc -p tsconfig.json --noEmit" 19 | }, 20 | "engines": { 21 | "node": ">=8" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/eclass/sequelize-paginate.git" 26 | }, 27 | "keywords": [ 28 | "sequelize", 29 | "paginate" 30 | ], 31 | "author": "Leonardo Gatica (https://about.me/lgatica)", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/eclass/sequelize-paginate/issues" 35 | }, 36 | "homepage": "https://github.com/eclass/sequelize-paginate#readme", 37 | "dependencies": {}, 38 | "devDependencies": { 39 | "@commitlint/cli": "8.3.5", 40 | "@commitlint/config-conventional": "8.3.4", 41 | "@commitlint/prompt-cli": "8.3.5", 42 | "@semantic-release/changelog": "3.0.6", 43 | "@semantic-release/git": "7.0.18", 44 | "@semantic-release/github": "5.5.8", 45 | "@semantic-release/npm": "5.3.5", 46 | "@types/sequelize": "4.28.9", 47 | "chai": "4.3.4", 48 | "eslint": "5.16.0", 49 | "eslint-config-jsdoc-strict": "2.0.0", 50 | "eslint-config-prettier": "6.15.0", 51 | "eslint-config-standard": "12.0.0", 52 | "eslint-plugin-import": "2.22.1", 53 | "eslint-plugin-jsdoc": "4.8.4", 54 | "eslint-plugin-node": "10.0.0", 55 | "eslint-plugin-promise": "4.3.1", 56 | "eslint-plugin-security": "1.4.0", 57 | "eslint-plugin-standard": "4.1.0", 58 | "husky": "3.1.0", 59 | "jsdoc": "3.6.6", 60 | "lint-staged": "8.2.1", 61 | "lodash.range": "3.2.0", 62 | "mocha": "6.2.3", 63 | "mysql2": "1.7.0", 64 | "npm-github-config": "2.0.1", 65 | "nyc": "14.1.1", 66 | "nyc-config-common": "1.0.1", 67 | "prettier-standard": "15.0.1", 68 | "promise-sequential": "1.1.1", 69 | "semantic-release": "15.14.0", 70 | "sequelize": "5.22.4", 71 | "travis-deploy-once": "5.0.11", 72 | "tsd-jsdoc": "2.5.0", 73 | "tslint": "5.20.1", 74 | "tslint-config-prettier": "1.18.0", 75 | "tslint-config-standard": "8.0.1", 76 | "typescript": "3.9.9" 77 | }, 78 | "eslintConfig": { 79 | "extends": [ 80 | "eslint:recommended", 81 | "standard", 82 | "jsdoc-strict", 83 | "plugin:promise/recommended", 84 | "plugin:security/recommended" 85 | ], 86 | "plugins": [ 87 | "promise", 88 | "security" 89 | ], 90 | "rules": { 91 | "no-console": [ 92 | "error" 93 | ], 94 | "require-await": [ 95 | "error" 96 | ] 97 | } 98 | }, 99 | "eslintIgnore": [ 100 | "coverage" 101 | ], 102 | "husky": { 103 | "hooks": { 104 | "pre-commit": "npm run ts-compile-check && lint-staged", 105 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 106 | } 107 | }, 108 | "lint-staged": { 109 | "linters": { 110 | "{src,test}/**/*.js": [ 111 | "eslint --fix", 112 | "prettier-standard", 113 | "git add" 114 | ], 115 | "src/**/*.ts": [ 116 | "tslint --project . --fix", 117 | "prettier-standard", 118 | "git add" 119 | ] 120 | } 121 | }, 122 | "commitlint": { 123 | "extends": [ 124 | "@commitlint/config-conventional" 125 | ] 126 | }, 127 | "renovate": { 128 | "automerge": "minor", 129 | "extends": [ 130 | "config:js-lib" 131 | ] 132 | }, 133 | "release": { 134 | "extends": "npm-github-config" 135 | }, 136 | "nyc": { 137 | "extends": "nyc-config-common" 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Class to paginate sequelite results. 5 | */ 6 | class SequelizePaginate { 7 | /** @typedef {import('sequelize').Model} Model */ 8 | /** 9 | * Method to append paginate method to Model. 10 | * 11 | * @param {Model} Model - Sequelize Model. 12 | * @returns {*} - 13 | * @example 14 | * const sequelizePaginate = require('sequelize-paginate') 15 | * 16 | * sequelizePaginate.paginate(MyModel) 17 | */ 18 | paginate (Model) { 19 | /** 20 | * @typedef {Object} Paginate Sequelize query options 21 | * @property {number} [paginate=25] Results per page 22 | * @property {number} [page=1] Number of page 23 | */ 24 | /** 25 | * @typedef {import('sequelize').FindOptions & Paginate} paginateOptions 26 | */ 27 | /** 28 | * The paginate result 29 | * @typedef {Object} PaginateResult 30 | * @property {Array} docs Docs 31 | * @property {number} pages Number of page 32 | * @property {number} total Total of docs 33 | */ 34 | /** 35 | * Pagination. 36 | * 37 | * @param {paginateOptions} [params] - Options to filter query. 38 | * @returns {Promise} Total pages and docs. 39 | * @example 40 | * const { docs, pages, total } = await MyModel.paginate({ page: 1, paginate: 25 }) 41 | * @memberof Model 42 | */ 43 | const pagination = async function ({ 44 | page = 1, 45 | paginate = 25, 46 | ...params 47 | } = {}) { 48 | const options = Object.assign({}, params) 49 | const countOptions = Object.keys(options).reduce((acc, key) => { 50 | if (!['order', 'attributes', 'include'].includes(key)) { 51 | // eslint-disable-next-line security/detect-object-injection 52 | acc[key] = options[key] 53 | } 54 | return acc 55 | }, {}) 56 | 57 | let total = await this.count(countOptions) 58 | 59 | if (options.group !== undefined) { 60 | // @ts-ignore 61 | total = total.length 62 | } 63 | 64 | const pages = Math.ceil(total / paginate) 65 | options.limit = paginate 66 | options.offset = paginate * (page - 1) 67 | /* eslint-disable no-console */ 68 | if (params.limit) { 69 | console.warn(`(sequelize-pagination) Warning: limit option is ignored.`) 70 | } 71 | if (params.offset) { 72 | console.warn( 73 | `(sequelize-pagination) Warning: offset option is ignored.` 74 | ) 75 | } 76 | /* eslint-enable no-console */ 77 | if (params.order) options.order = params.order 78 | const docs = await this.findAll(options) 79 | return { docs, pages, total } 80 | } 81 | const instanceOrModel = Model.Instance || Model 82 | // @ts-ignore 83 | instanceOrModel.paginate = pagination 84 | } 85 | } 86 | 87 | module.exports = new SequelizePaginate() 88 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | import { FindOptions, Model } from 'sequelize' 2 | 3 | export class SequelizePaginate { 4 | public paginate(Model: Model): void 5 | } 6 | 7 | export interface Paginate { 8 | paginate?: number 9 | page?: number 10 | } 11 | 12 | export interface PaginateResult { 13 | docs: Array 14 | pages: number 15 | total: number 16 | } 17 | 18 | export function paginate( 19 | Model: Model 20 | ): void 21 | export function pagination( 22 | params: FindOptions & Paginate 23 | ): Promise 24 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { describe, before, it } = require('mocha') 4 | const { expect } = require('chai') 5 | const Sequelize = require('sequelize') 6 | const range = require('lodash.range') 7 | const sequential = require('promise-sequential') 8 | const sequelizePaginate = require('../src') 9 | 10 | describe('sequelizePaginate', () => { 11 | let Author 12 | let Book 13 | before(async () => { 14 | const DATABASE = process.env.DATABASE || 'mysql://root:root@localhost/test' 15 | const sequelize = new Sequelize(DATABASE, { 16 | logging: false, 17 | operatorsAliases: false 18 | }) 19 | await sequelize.authenticate() 20 | Author = sequelize.define('author', { 21 | name: Sequelize.STRING 22 | }) 23 | Book = sequelize.define('book', { 24 | name: Sequelize.STRING, 25 | authorId: Sequelize.INTEGER 26 | }) 27 | Author.hasMany(Book, { foreignKey: 'authorId' }) 28 | sequelizePaginate.paginate(Author) 29 | await Book.drop() 30 | await Author.drop() 31 | await Author.sync({ force: true }) 32 | await Book.sync({ force: true }) 33 | await sequential( 34 | range(1, 100).map(authorId => { 35 | return () => 36 | Author.create( 37 | { 38 | name: `author${authorId}`, 39 | books: range(1, 100).map(bookId => ({ name: `book${bookId}` })) 40 | }, 41 | { 42 | include: [Book] 43 | } 44 | ) 45 | }) 46 | ) 47 | }) 48 | describe('', () => { 49 | it('should paginate with defaults', async () => { 50 | const { docs, pages, total } = await Author.paginate() 51 | expect(docs).to.be.an('array') 52 | expect(docs.length).to.equal(25) 53 | expect(pages).to.equal(4) 54 | expect(total).to.equal(99) 55 | }) 56 | 57 | it('should paginate with page and paginate', async () => { 58 | const { docs, pages, total } = await Author.paginate({ 59 | page: 2, 60 | paginate: 50 61 | }) 62 | expect(docs).to.be.an('array') 63 | expect(docs.length).to.equal(49) 64 | expect(pages).to.equal(2) 65 | expect(total).to.equal(99) 66 | }) 67 | 68 | it('should paginate and ignore limit and offset', async () => { 69 | const { docs, pages, total } = await Author.paginate({ 70 | limit: 2, 71 | offset: 50 72 | }) 73 | expect(docs).to.be.an('array') 74 | expect(docs.length).to.equal(25) 75 | expect(pages).to.equal(4) 76 | expect(total).to.equal(99) 77 | }) 78 | 79 | it('should paginate with extras', async () => { 80 | const { docs, pages, total } = await Author.paginate({ 81 | include: [{ model: Book }], 82 | order: [['id']] 83 | }) 84 | expect(docs).to.be.an('array') 85 | expect(docs.length).to.equal(25) 86 | expect(pages).to.equal(4) 87 | expect(total).to.equal(99) 88 | expect(docs[0].books).to.be.an('array') 89 | expect(docs[0].books.length).to.equal(99) 90 | }) 91 | 92 | it('should paginate with defaults and group by statement', async () => { 93 | const group = ['id'] 94 | const { docs, pages, total } = await Author.paginate({ group }) 95 | expect(docs).to.be.an('array') 96 | expect(docs.length).to.equal(25) 97 | expect(pages).to.equal(4) 98 | expect(total).to.equal(99) 99 | }) 100 | 101 | it('should paginate with filters, order and paginate', async () => { 102 | const { docs, pages, total } = await Author.paginate({ 103 | order: [['name', 'DESC']], 104 | where: { name: { [Sequelize.Op.like]: 'author1%' } }, 105 | paginate: 5 106 | }) 107 | expect(docs).to.be.an('array') 108 | expect(docs.length).to.equal(5) 109 | expect(pages).to.equal(3) 110 | expect(total).to.equal(11) 111 | }) 112 | 113 | it('should paginate with custom scope', async () => { 114 | Author.addScope('author1', { 115 | where: { name: { [Sequelize.Op.like]: 'author1%' } } 116 | }) 117 | const { docs, pages, total } = await Author.scope('author1').paginate({ 118 | order: [['name', 'DESC']], 119 | paginate: 5 120 | }) 121 | expect(docs).to.be.an('array') 122 | expect(docs.length).to.equal(5) 123 | expect(pages).to.equal(3) 124 | expect(total).to.equal(11) 125 | }) 126 | }) 127 | }) 128 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "outDir": "dist", 7 | "strict": true, 8 | "target": "ES2018", 9 | "esModuleInterop": true 10 | }, 11 | "include": [ 12 | "src/**/*.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-config-standard", 5 | "tslint-config-prettier" 6 | ], 7 | "rules": { 8 | "object-literal-sort-keys": false, 9 | "interface-name": [true, "never-prefix"] 10 | } 11 | } 12 | --------------------------------------------------------------------------------