const Promise = require ( 'bluebird' ) ; const _ = require ( 'lodash' ) ; const remove = _ . remove ; const assign = _ . assign ; const omit = _ . omit ; const DEFAULT_LIMIT = 10 ; const DEFAULT_OFFSET = 0 ; const DEFAULT_PAGE = 1 ; /**
 * Exports a plugin to pass into the bookshelf instance, i.e.:
 *
 *      import config from './knexfile';
 *      import knex from 'knex';
 *      import bookshelf from 'bookshelf';
 *
 *      const ORM = bookshelf(knex(config));
 *
 *      ORM.plugin('bookshelf-pagination-plugin');
 *
 *      export default ORM;
 *
 * The plugin attaches two instance methods to the bookshelf
 * Model object: orderBy and fetchPage.
 *
 * Model#orderBy calls the underlying query builder's orderBy method, and
 * is useful for ordering the paginated results.
 *
 * Model#fetchPage works like Model#fetchAll, but returns a single page of
 * results instead of all results, as well as the pagination information
 *
 * See methods below for details.
 */ module . exports = function paginationPlugin ( bookshelf ) { method belongsTo Model
     *
     * Similar to  Model , but fetches a single page of results
     * as specified by the limit (page size) and offset or page number.
     *
     * Any options that may be passed to  Model may also be passed
     * in the options to this method.
     *
     * To perform pagination, you may include *either* an `offset` and `limit`, **or**
     * a `page` and `pageSize`.
     *
     * By default, with no parameters or missing parameters, `fetchPage` will use an
     * options object of `{page: 1, pageSize: 10}`
     *
     *
     * Below is an example showing the user of a JOIN query with sort/ordering,
     * pagination, and related models.
     *
     *  example param options { object } *    The pagination options, plus any additional options that will be passed to
     *     Model returns { Promise < Model | null > } function fetchPage ( options = { } ) { const { page , pageSize , limit , offset } = options ; const fetchOptions = omit ( options , [ 'page' , 'pageSize' , 'limit' , 'offset' ] ) ; let usingPageSize = false ; let _page ; let _pageSize ; let _limit ; let _offset ; function ensureIntWithDefault ( val , def ) { if ( ! val ) { return def ; } const _val = parseInt ( val ) ; if ( Number . isNaN ( _val ) ) { return def ; } return _val ; } if ( ! limit && ! offset ) { usingPageSize = true ; _pageSize = ensureIntWithDefault ( pageSize , DEFAULT_LIMIT ) ; _page = ensureIntWithDefault ( page , DEFAULT_PAGE ) ; _limit = _pageSize ; _offset = _limit * ( _page - 1 ) ; } else { _pageSize = _limit ; _limit = ensureIntWithDefault ( limit , DEFAULT_LIMIT ) ; _offset = ensureIntWithDefault ( offset , DEFAULT_OFFSET ) ; } const tableName = this . constructor . prototype . tableName ; const idAttribute = this . constructor . prototype . idAttribute ? this . constructor . prototype . idAttribute : 'id' ; const paginate = ( ) => { const pager = this . constructor . forge ( ) ; return pager . query ( qb => { assign ( qb , this . query ( ) . clone ( ) ) ; qb . limit . apply ( qb , [ _limit ] ) ; qb . offset . apply ( qb , [ _offset ] ) ; return null ; } ) . fetchAll ( fetchOptions ) ; } ; const count = ( ) => { const notNeededQueries = [ 'orderByBasic' , 'orderByRaw' , 'groupByBasic' , 'groupByRaw' ] ; const counter = this . constructor . forge ( ) ; return counter . query ( qb => { assign ( qb , this . query ( ) . clone ( ) ) ; remove ( qb . _statements , statement => { return ( notNeededQueries . indexOf ( statement . type ) > - 1 ) || statement . grouping === 'columns' ; } ) ; qb . countDistinct . apply ( qb , [ `${ tableName }.${ idAttribute }` ] ) ; } ) . fetchAll ( ) . then ( result => { const metadata = usingPageSize ? { page : _page , pageSize : _limit } : { offset : _offset , limit : _limit } ; if ( result && result . length == 1 ) { const count = result . models [ 0 ] ; const keys = Object . keys ( count . attributes ) ; if ( keys . length === 1 ) { const key = Object . keys ( count . attributes ) [ 0 ] ; metadata . rowCount = parseInt ( count . attributes [ key ] ) ; } } return metadata ; } ) ; } ; return Promise . join ( paginate ( ) , count ( ) ) . then ( ( [ rows , metadata ] ) => { const pageCount = Math . ceil ( metadata . rowCount / _limit ) ; const pageData = assign ( metadata , { pageCount } ) ; return assign ( rows , { pagination : pageData } ) ; } ) ; } bookshelf . Model . prototype . fetchPage = fetchPage ; bookshelf . Model . fetchPage = function ( ... args ) { return this . forge ( ) . fetchPage ( ... args ) ; } ; bookshelf . Collection . prototype . fetchPage = function ( ... args ) { return fetchPage . apply ( this . model . forge ( ) , ... args ) ; } ; } ;