Skip to content

Commit 095b3a2

Browse files
authored
feat: custom count query (#1082)
1 parent 9cc6c1f commit 095b3a2

File tree

3 files changed

+50
-3
lines changed

3 files changed

+50
-3
lines changed

README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ export class CatsController {
211211

212212
### Config
213213

214-
```ts
214+
````ts
215215
const paginateConfig: PaginateConfig<CatEntity> {
216216
/**
217217
* Required: true (must have a minimum of one column)
@@ -376,8 +376,31 @@ const paginateConfig: PaginateConfig<CatEntity> {
376376
* will be treated as a separate search term, allowing for more flexible matching.
377377
*/
378378
multiWordSearch: false,
379+
380+
/**
381+
* Required: false
382+
* Type: (qb: SelectQueryBuilder<T>) => SelectQueryBuilder<any>
383+
* Default: undefined
384+
* Description: Callback that lets you override the COUNT query executed by
385+
* paginate(). The function receives a **clone** of the original QueryBuilder,
386+
* so it already contains every WHERE clause and parameter parsed by
387+
* nestjs-paginate.
388+
*
389+
* Typical use-case: remove expensive LEFT JOINs or build a lighter DISTINCT
390+
* count when getManyAndCount() becomes a bottleneck.
391+
*
392+
* Example:
393+
* ```ts
394+
* buildCountQuery: qb => {
395+
* qb.expressionMap.joinAttributes = []; // drop all joins
396+
* qb.select('p.id').distinct(true); // keep DISTINCT on primary key
397+
* return qb; // paginate() will call .getCount()
398+
* }
399+
* ```
400+
*/
401+
buildCountQuery: (qb: SelectQueryBuilder<T>) => SelectQueryBuilder<any>,
379402
}
380-
```
403+
````
381404

382405
## Usage with Query Builder
383406

src/paginate.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2993,6 +2993,24 @@ describe('paginate', () => {
29932993
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC')
29942994
})
29952995

2996+
it('uses custom count builder when provided', async () => {
2997+
const fakeQB = { getCount: jest.fn().mockResolvedValue(42) } as any
2998+
const config: PaginateConfig<CatEntity> = {
2999+
sortableColumns: ['id'],
3000+
select: ['id', 'name', 'color'],
3001+
buildCountQuery: () => fakeQB,
3002+
}
3003+
const query: PaginateQuery = {
3004+
path: '',
3005+
select: ['id', 'color'],
3006+
}
3007+
3008+
const page = await paginate<CatEntity>(query, catRepo, config)
3009+
3010+
expect(fakeQB.getCount).toHaveBeenCalledTimes(1)
3011+
expect(page.meta.totalItems).toBe(42)
3012+
})
3013+
29963014
describe('should return result based on date column filter', () => {
29973015
it('with $not and $null operators', async () => {
29983016
const config: PaginateConfig<CatEntity> = {

src/paginate.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export interface PaginateConfig<T> {
9090
multiWordSearch?: boolean
9191
defaultJoinMethod?: JoinMethod
9292
joinMethods?: Partial<MappedColumns<T, JoinMethod>>
93+
buildCountQuery?: (qb: SelectQueryBuilder<T>) => SelectQueryBuilder<any>
9394
}
9495

9596
export enum PaginationLimit {
@@ -819,7 +820,12 @@ export async function paginate<T extends ObjectLiteral>(
819820
if (query.limit === PaginationLimit.COUNTER_ONLY) {
820821
totalItems = await queryBuilder.getCount()
821822
} else if (isPaginated && config.paginationType !== PaginationType.CURSOR) {
822-
;[items, totalItems] = await queryBuilder.getManyAndCount()
823+
if (config.buildCountQuery) {
824+
items = await queryBuilder.getMany()
825+
totalItems = await config.buildCountQuery(queryBuilder.clone()).getCount()
826+
} else {
827+
;[items, totalItems] = await queryBuilder.getManyAndCount()
828+
}
823829
} else {
824830
items = await queryBuilder.getMany()
825831
}

0 commit comments

Comments
 (0)