diff --git a/.vscode/launch.json b/.vscode/launch.json index 0f5d418d..a6bc496f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "runtimeArgs": [ "--preserve-symlinks", "run", - "test", + "test:watch", "--", "--inspect-brk", ], diff --git a/README.md b/README.md index d0703f92..ea81049c 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ const paginateConfig: PaginateConfig { * 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. + * Set it to -1, in conjunction with limit=-1 on query param, to disable pagination. */ maxLimit: 20, diff --git a/package.json b/package.json index a22dc97d..4b19f19e 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "format:ci": "prettier --list-different \"src/**/*.ts\"", "lint": "eslint -c .eslintrc.json --ext .ts --max-warnings 0 src", "test": "jest", - "test:watch": "jest --watch", + "test:watch": "jest --watch ", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand" }, diff --git a/src/paginate.spec.ts b/src/paginate.spec.ts index 237c9cfb..53b7642c 100644 --- a/src/paginate.spec.ts +++ b/src/paginate.spec.ts @@ -20,7 +20,7 @@ import { isSuffix, parseFilterToken, } from './filter' -import { NO_PAGINATION, PaginateConfig, Paginated, paginate } from './paginate' +import { PaginateConfig, Paginated, PaginationLimit, paginate } from './paginate' const isoStringToDate = (isoString) => new Date(isoString) @@ -317,7 +317,7 @@ describe('paginate', () => { } const query: PaginateQuery = { path: '', - limit: NO_PAGINATION, + limit: PaginationLimit.NO_PAGINATION, } const result = await paginate(query, catRepo, config) @@ -327,12 +327,12 @@ describe('paginate', () => { it('should return all cats', async () => { const config: PaginateConfig = { sortableColumns: ['id'], - maxLimit: NO_PAGINATION, + maxLimit: PaginationLimit.NO_PAGINATION, defaultLimit: 1, } const query: PaginateQuery = { path: '', - limit: NO_PAGINATION, + limit: PaginationLimit.NO_PAGINATION, } const result = await paginate(query, catRepo, config) @@ -340,15 +340,15 @@ describe('paginate', () => { expect(result.data).toStrictEqual(cats) }) - it('should limit to defaultLimit, if limit is negative', async () => { + it('should limit to defaultLimit, if limit is differt FROM NO_PAGINATION ecc....', async () => { const config: PaginateConfig = { sortableColumns: ['id'], - maxLimit: NO_PAGINATION, + maxLimit: PaginationLimit.NO_PAGINATION, defaultLimit: 1, } const query: PaginateQuery = { path: '', - limit: -1, + limit: -2, } const result = await paginate(query, catRepo, config) @@ -356,10 +356,10 @@ describe('paginate', () => { expect(result.data).toStrictEqual(cats.slice(0, 1)) }) - it('should default to limit defaultLimit, if maxLimit is 0', async () => { + it('should default to limit defaultLimit, if maxLimit is NO_PAGINATION', async () => { const config: PaginateConfig = { sortableColumns: ['id'], - maxLimit: NO_PAGINATION, + maxLimit: PaginationLimit.NO_PAGINATION, defaultLimit: 1, } const query: PaginateQuery = { @@ -404,6 +404,66 @@ describe('paginate', () => { expect(result.data).toStrictEqual(cats.slice(0, 2)) }) + it('maxLimit should limit defaultLimit', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + maxLimit: 1, + defaultLimit: 2, + } + const query: PaginateQuery = { + path: '', + } + + const result = await paginate(query, catRepo, config) + + expect(result.data).toStrictEqual(cats.slice(0, 1)) + }) + + it('limit should bypass defaultLimit', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + defaultLimit: 1, + } + const query: PaginateQuery = { + path: '', + limit: 2, + } + + const result = await paginate(query, catRepo, config) + + expect(result.data).toStrictEqual(cats.slice(0, 2)) + }) + + it('DEFAULT_LIMIT should be used as the limit if limit is set to NO_PAGINATION and maxLimit is not specified.', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + } + const query: PaginateQuery = { + path: '', + limit: PaginationLimit.NO_PAGINATION, + } + + const result = await paginate(query, catRepo, config) + + expect(result.data).toStrictEqual(cats.slice(0, PaginationLimit.DEFAULT_LIMIT)) + }) + + it('should return the count without data ignoring maxLimit if limit is COUNTER_ONLY', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + maxLimit: PaginationLimit.NO_PAGINATION, + } + const query: PaginateQuery = { + path: '', + limit: 0, + } + + const result = await paginate(query, catRepo, config) + + expect(result.data).toStrictEqual([]) + expect(result.meta.totalItems).toBe(5) + }) + it('should return correct result for limited one-to-many relations', async () => { const config: PaginateConfig = { relations: ['toys'], diff --git a/src/paginate.ts b/src/paginate.ts index 46f061e4..b82c0142 100644 --- a/src/paginate.ts +++ b/src/paginate.ts @@ -91,9 +91,12 @@ export interface PaginateConfig { ignoreSelectInQueryParam?: boolean } -export const DEFAULT_MAX_LIMIT = 100 -export const DEFAULT_LIMIT = 20 -export const NO_PAGINATION = 0 +export enum PaginationLimit { + NO_PAGINATION = -1, + COUNTER_ONLY = 0, + DEFAULT_LIMIT = 20, + DEFAULT_MAX_LIMIT = 100, +} function generateWhereStatement( queryBuilder: SelectQueryBuilder, @@ -183,13 +186,22 @@ export async function paginate( ): Promise> { 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 defaultLimit = config.defaultLimit || PaginationLimit.DEFAULT_LIMIT + const maxLimit = config.maxLimit || PaginationLimit.DEFAULT_MAX_LIMIT - const isPaginated = !(queryLimit === NO_PAGINATION && maxLimit === NO_PAGINATION) + const isPaginated = !( + query.limit === PaginationLimit.COUNTER_ONLY || + (query.limit === PaginationLimit.NO_PAGINATION && maxLimit === PaginationLimit.NO_PAGINATION) + ) - const limit = isPaginated ? Math.min(queryLimit || defaultLimit, maxLimit || DEFAULT_MAX_LIMIT) : NO_PAGINATION + const limit = + query.limit === PaginationLimit.COUNTER_ONLY + ? PaginationLimit.COUNTER_ONLY + : isPaginated + ? query.limit === PaginationLimit.NO_PAGINATION || maxLimit === PaginationLimit.NO_PAGINATION + ? defaultLimit + : Math.min(query.limit ?? defaultLimit, maxLimit) + : defaultLimit const sortBy = [] as SortBy const searchBy: Column[] = [] @@ -372,7 +384,9 @@ export async function paginate( addFilter(queryBuilder, query, config.filterableColumns) } - if (isPaginated) { + if (query.limit === PaginationLimit.COUNTER_ONLY) { + totalItems = await queryBuilder.getCount() + } else if (isPaginated) { ;[items, totalItems] = await queryBuilder.getManyAndCount() } else { items = await queryBuilder.getMany() @@ -419,8 +433,8 @@ export async function paginate( const results: Paginated = { data: items, meta: { - itemsPerPage: isPaginated ? limit : items.length, - totalItems: isPaginated ? totalItems : items.length, + itemsPerPage: limit === PaginationLimit.COUNTER_ONLY ? totalItems : isPaginated ? limit : items.length, + totalItems: limit === PaginationLimit.COUNTER_ONLY || isPaginated ? totalItems : items.length, currentPage: page, totalPages, sortBy, diff --git a/src/swagger/api-paginated-query.decorator.ts b/src/swagger/api-paginated-query.decorator.ts index 094b7cc7..902c0c84 100644 --- a/src/swagger/api-paginated-query.decorator.ts +++ b/src/swagger/api-paginated-query.decorator.ts @@ -1,7 +1,7 @@ -import { DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, FilterOperator, FilterSuffix, PaginateConfig } from '../paginate' +import { applyDecorators } from '@nestjs/common' import { ApiQuery } from '@nestjs/swagger' import { FilterComparator } from '../filter' -import { applyDecorators } from '@nestjs/common' +import { FilterOperator, FilterSuffix, PaginateConfig, PaginationLimit } from '../paginate' const DEFAULT_VALUE_KEY = 'Default Value' @@ -45,8 +45,8 @@ function Limit(paginationConfig: PaginateConfig) { name: 'limit', description: `Number of records per page. ${p('Example', '20')} - ${p(DEFAULT_VALUE_KEY, paginationConfig?.defaultLimit?.toString() || DEFAULT_LIMIT.toString())} - ${p('Max Value', paginationConfig.maxLimit?.toString() || DEFAULT_MAX_LIMIT.toString())} + ${p(DEFAULT_VALUE_KEY, paginationConfig?.defaultLimit?.toString() || PaginationLimit.DEFAULT_LIMIT.toString())} + ${p('Max Value', paginationConfig.maxLimit?.toString() || PaginationLimit.DEFAULT_MAX_LIMIT.toString())} If provided value is greater than max value, max value will be applied. `,