feat: allow unpaginated request (#406)
This commit is contained in:
parent
2a3dc9573c
commit
0cd2c6afa1
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ lib/
|
||||
coverage/
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
.history
|
||||
|
@ -200,6 +200,7 @@ const paginateConfig: PaginateConfig<CatEntity> {
|
||||
* Type: number
|
||||
* Default: 100
|
||||
* Description: The maximum amount of entities to return per page.
|
||||
* Set it to 0, in conjunction with limit=0 on query param, to disable pagination.
|
||||
*/
|
||||
maxLimit: 20,
|
||||
|
||||
|
@ -2,6 +2,7 @@ type Join<K, P> = K extends string ? (P extends string ? `${K}${'' extends P ? '
|
||||
|
||||
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...0[]]
|
||||
|
||||
// TODO: puts some comments here, in this ternary of doom
|
||||
export type Column<T, D extends number = 2> = [D] extends [never]
|
||||
? never
|
||||
: T extends Record<string, any>
|
||||
@ -25,3 +26,6 @@ export type RelationColumn<T> = Extract<
|
||||
|
||||
export type Order<T> = [Column<T>, 'ASC' | 'DESC']
|
||||
export type SortBy<T> = Order<T>[]
|
||||
|
||||
export const positiveNumberOrDefault = (value: number | undefined, defaultValue: number, minValue: 0 | 1 = 0) =>
|
||||
value === undefined || value < minValue ? defaultValue : value
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
isOperator,
|
||||
getFilterTokens,
|
||||
OperatorSymbolToFunction,
|
||||
NO_PAGINATION,
|
||||
} from './paginate'
|
||||
import { PaginateQuery } from './decorator'
|
||||
import { HttpException } from '@nestjs/common'
|
||||
@ -124,6 +125,68 @@ describe('paginate', () => {
|
||||
expect(result.data).toStrictEqual(cats.slice(0, 1))
|
||||
})
|
||||
|
||||
it('should default to limit maxLimit, if maxLimit is not 0', async () => {
|
||||
const config: PaginateConfig<CatEntity> = {
|
||||
sortableColumns: ['id'],
|
||||
maxLimit: 1,
|
||||
defaultLimit: 1,
|
||||
}
|
||||
const query: PaginateQuery = {
|
||||
path: '',
|
||||
limit: NO_PAGINATION,
|
||||
}
|
||||
|
||||
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||
expect(result.data).toStrictEqual(cats.slice(0, 1))
|
||||
})
|
||||
|
||||
it('should return all cats', async () => {
|
||||
const config: PaginateConfig<CatEntity> = {
|
||||
sortableColumns: ['id'],
|
||||
maxLimit: NO_PAGINATION,
|
||||
defaultLimit: 1,
|
||||
}
|
||||
const query: PaginateQuery = {
|
||||
path: '',
|
||||
limit: NO_PAGINATION,
|
||||
}
|
||||
|
||||
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||
|
||||
expect(result.data).toStrictEqual(cats)
|
||||
})
|
||||
|
||||
it('should limit to defaultLimit, if limit is negative', async () => {
|
||||
const config: PaginateConfig<CatEntity> = {
|
||||
sortableColumns: ['id'],
|
||||
maxLimit: NO_PAGINATION,
|
||||
defaultLimit: 1,
|
||||
}
|
||||
const query: PaginateQuery = {
|
||||
path: '',
|
||||
limit: -1,
|
||||
}
|
||||
|
||||
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||
|
||||
expect(result.data).toStrictEqual(cats.slice(0, 1))
|
||||
})
|
||||
|
||||
it('should default to limit defaultLimit, if maxLimit is 0', async () => {
|
||||
const config: PaginateConfig<CatEntity> = {
|
||||
sortableColumns: ['id'],
|
||||
maxLimit: NO_PAGINATION,
|
||||
defaultLimit: 1,
|
||||
}
|
||||
const query: PaginateQuery = {
|
||||
path: '',
|
||||
}
|
||||
|
||||
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||
|
||||
expect(result.data).toStrictEqual(cats.slice(0, 1))
|
||||
})
|
||||
|
||||
it('should default to limit maxLimit, if more than maxLimit is given', async () => {
|
||||
const config: PaginateConfig<CatEntity> = {
|
||||
sortableColumns: ['id'],
|
||||
|
@ -21,7 +21,7 @@ import { ServiceUnavailableException, Logger } from '@nestjs/common'
|
||||
import { values, mapKeys } from 'lodash'
|
||||
import { stringify } from 'querystring'
|
||||
import { WherePredicateOperator } from 'typeorm/query-builder/WhereClause'
|
||||
import { Column, Order, RelationColumn, SortBy } from './helper'
|
||||
import { Column, Order, positiveNumberOrDefault, RelationColumn, SortBy } from './helper'
|
||||
|
||||
const logger: Logger = new Logger('nestjs-paginate')
|
||||
|
||||
@ -171,16 +171,28 @@ function parseFilter<T>(query: PaginateQuery, config: PaginateConfig<T>) {
|
||||
return filter
|
||||
}
|
||||
|
||||
export const DEFAULT_MAX_LIMIT = 100
|
||||
export const DEFAULT_LIMIT = 20
|
||||
export const NO_PAGINATION = 0
|
||||
|
||||
export async function paginate<T extends ObjectLiteral>(
|
||||
query: PaginateQuery,
|
||||
repo: Repository<T> | SelectQueryBuilder<T>,
|
||||
config: PaginateConfig<T>
|
||||
): Promise<Paginated<T>> {
|
||||
let page = query.page || 1
|
||||
const limit = Math.min(query.limit || config.defaultLimit || 20, config.maxLimit || 100)
|
||||
const page = positiveNumberOrDefault(query.page, 1, 1)
|
||||
|
||||
const defaultLimit = config.defaultLimit || DEFAULT_LIMIT
|
||||
const maxLimit = positiveNumberOrDefault(config.maxLimit, DEFAULT_MAX_LIMIT)
|
||||
const queryLimit = positiveNumberOrDefault(query.limit, defaultLimit)
|
||||
|
||||
const isPaginated = !(queryLimit === NO_PAGINATION && maxLimit === NO_PAGINATION)
|
||||
|
||||
const limit = isPaginated ? Math.min(queryLimit || defaultLimit, maxLimit || DEFAULT_MAX_LIMIT) : NO_PAGINATION
|
||||
|
||||
const sortBy = [] as SortBy<T>
|
||||
const searchBy: Column<T>[] = []
|
||||
let path
|
||||
let path: string
|
||||
|
||||
const r = new RegExp('^(?:[a-z+]+:)?//', 'i')
|
||||
let queryOrigin = ''
|
||||
@ -234,19 +246,12 @@ export async function paginate<T extends ObjectLiteral>(
|
||||
}
|
||||
}
|
||||
|
||||
if (page < 1) page = 1
|
||||
|
||||
let [items, totalItems]: [T[], number] = [[], 0]
|
||||
|
||||
let queryBuilder: SelectQueryBuilder<T>
|
||||
const queryBuilder = repo instanceof Repository ? repo.createQueryBuilder('e') : repo
|
||||
|
||||
if (repo instanceof Repository) {
|
||||
queryBuilder = repo
|
||||
.createQueryBuilder('e')
|
||||
.take(limit)
|
||||
.skip((page - 1) * limit)
|
||||
} else {
|
||||
queryBuilder = repo.take(limit).skip((page - 1) * limit)
|
||||
if (isPaginated) {
|
||||
queryBuilder.take(limit).skip((page - 1) * limit)
|
||||
}
|
||||
|
||||
if (config.relations?.length) {
|
||||
@ -360,10 +365,11 @@ export async function paginate<T extends ObjectLiteral>(
|
||||
)
|
||||
}
|
||||
|
||||
if (isPaginated) {
|
||||
;[items, totalItems] = await queryBuilder.getManyAndCount()
|
||||
|
||||
let totalPages = totalItems / limit
|
||||
if (totalItems % limit) totalPages = Math.ceil(totalPages)
|
||||
} else {
|
||||
items = await queryBuilder.getMany()
|
||||
}
|
||||
|
||||
const sortByQuery = sortBy.map((order) => `&sortBy=${order.join(':')}`).join('')
|
||||
const searchQuery = query.search ? `&search=${query.search}` : ''
|
||||
@ -385,13 +391,15 @@ export async function paginate<T extends ObjectLiteral>(
|
||||
|
||||
const buildLink = (p: number): string => path + '?page=' + p + options
|
||||
|
||||
const totalPages = isPaginated ? Math.ceil(totalItems / limit) : 1
|
||||
|
||||
const results: Paginated<T> = {
|
||||
data: items,
|
||||
meta: {
|
||||
itemsPerPage: limit,
|
||||
itemsPerPage: isPaginated ? limit : items.length,
|
||||
totalItems,
|
||||
currentPage: page,
|
||||
totalPages: totalPages,
|
||||
totalPages,
|
||||
sortBy,
|
||||
search: query.search,
|
||||
searchBy: query.search ? searchBy : undefined,
|
||||
|
Loading…
Reference in New Issue
Block a user