Skip to content

Commit 8f2fa19

Browse files
committed
refactor: refactor createPaginator
1 parent 215edac commit 8f2fa19

File tree

9 files changed

+161
-64
lines changed

9 files changed

+161
-64
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"ts-loader": "^9.4.3",
9696
"ts-node": "^10.9.1",
9797
"tsconfig-paths": "^4.2.0",
98+
"type-fest": "^4.25.0",
9899
"typescript": "^5.1.3"
99100
},
100101
"prisma": {

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/author/repositories/prisma-author.repository.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import type {
1010
} from '@app/author/dtos/author-repository-dtos';
1111
import type { Author } from '@app/author/entities/author.entity';
1212
import type { PaginatedResult } from '@app/lib/prisma/helpers/pagination';
13-
import { createPaginator } from '@app/lib/prisma/helpers/pagination';
13+
import { paginate } from '@app/lib/prisma/helpers/pagination';
1414
import { PrismaManagerService } from '@app/lib/prisma/services/prisma-manager.service';
1515
import { Injectable } from '@nestjs/common';
16-
import type { Prisma, Author as PrismaAuthor } from '@prisma/client';
16+
import type { Author as PrismaAuthor } from '@prisma/client';
1717

1818
type FindManyReturn = PrismaAuthor;
1919

@@ -33,17 +33,15 @@ export class PrismaAuthorRepository implements AuthorRepositoryContract {
3333
const { ...where } = input.where || {};
3434
const { perPage = 20, page = 1 } = input.options || {};
3535

36-
const paginate = createPaginator({ perPage });
37-
38-
const result = await paginate<FindManyReturn, Prisma.AuthorFindManyArgs>(
36+
const result = await paginate<FindManyReturn, 'Author'>(
3937
this.prismaManager.getClient().author,
4038
{
4139
where: {
4240
...where,
4341
},
4442
orderBy: [{ createdAt: 'desc' }],
4543
},
46-
{ page }
44+
{ page, perPage }
4745
);
4846

4947
return {

src/category/repositories/prisma-category.repository.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ import type {
99
CategoryRepositoryUpdateInput,
1010
} from '@app/category/dtos/category-repository-dtos';
1111
import type { Category } from '@app/category/entities/category.entity';
12-
import type { PaginatedResult } from '@app/lib/prisma/helpers/pagination';
13-
import { createPaginator } from '@app/lib/prisma/helpers/pagination';
12+
import { paginate, type PaginatedResult } from '@app/lib/prisma/helpers/pagination';
1413
import { PrismaManagerService } from '@app/lib/prisma/services/prisma-manager.service';
1514
import { Injectable } from '@nestjs/common';
16-
import type { Prisma, Category as PrismaCategory } from '@prisma/client';
15+
import type { Category as PrismaCategory } from '@prisma/client';
1716

1817
type FindManyReturn = PrismaCategory;
1918

@@ -33,17 +32,15 @@ export class PrismaCategoryRepository implements CategoryRepositoryContract {
3332
const { ...where } = input.where || {};
3433
const { perPage = 20, page = 1 } = input.options || {};
3534

36-
const paginate = createPaginator({ perPage });
37-
38-
const result = await paginate<FindManyReturn, Prisma.CategoryFindManyArgs>(
35+
const result = await paginate<FindManyReturn, 'Category'>(
3936
this.prismaManager.getClient().category,
4037
{
4138
where: {
4239
...where,
4340
},
4441
orderBy: [{ createdAt: 'desc' }],
4542
},
46-
{ page }
43+
{ page, perPage }
4744
);
4845

4946
return {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { calcSkip, getMeta } from '@app/lib/prisma/helpers/pagination';
2+
3+
describe('calcSkip', () => {
4+
it('should return 0 when page is 1', () => {
5+
expect(calcSkip(1, 10)).toBe(0);
6+
});
7+
8+
it('should return correct skip value when page is greater than 1', () => {
9+
expect(calcSkip(2, 10)).toBe(10);
10+
expect(calcSkip(3, 20)).toBe(40);
11+
});
12+
13+
it('should return 0 when page is 0 or negative', () => {
14+
expect(calcSkip(0, 10)).toBe(0);
15+
expect(calcSkip(-1, 10)).toBe(0);
16+
});
17+
});
18+
19+
describe('getMeta', () => {
20+
it('should return correct metadata for the first page', () => {
21+
const meta = getMeta(100, 1, 10);
22+
23+
expect(meta).toEqual({
24+
total: 100,
25+
lastPage: 10,
26+
currentPage: 1,
27+
perPage: 10,
28+
prev: null,
29+
next: 2,
30+
});
31+
});
32+
33+
it('should return correct metadata for an intermediate page', () => {
34+
const meta = getMeta(100, 5, 10);
35+
36+
expect(meta).toEqual({
37+
total: 100,
38+
lastPage: 10,
39+
currentPage: 5,
40+
perPage: 10,
41+
prev: 4,
42+
next: 6,
43+
});
44+
});
45+
46+
it('should return correct metadata for the last page', () => {
47+
const meta = getMeta(100, 10, 10);
48+
49+
expect(meta).toEqual({
50+
total: 100,
51+
lastPage: 10,
52+
currentPage: 10,
53+
perPage: 10,
54+
prev: 9,
55+
next: null,
56+
});
57+
});
58+
59+
it('should return correct metadata for a single page', () => {
60+
const meta = getMeta(5, 1, 10);
61+
62+
expect(meta).toEqual({
63+
total: 5,
64+
lastPage: 1,
65+
currentPage: 1,
66+
perPage: 10,
67+
prev: null,
68+
next: null,
69+
});
70+
});
71+
72+
it('should handle page greater than lastPage', () => {
73+
const meta = getMeta(50, 6, 10);
74+
75+
expect(meta).toEqual({
76+
total: 50,
77+
lastPage: 5,
78+
currentPage: 6,
79+
perPage: 10,
80+
prev: 5,
81+
next: null,
82+
});
83+
});
84+
});
Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
/* eslint-disable */
1+
/* eslint-disable @typescript-eslint/ban-ts-comment */
2+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
3+
import type { Prisma, PrismaClient } from '@prisma/client';
4+
25
export interface PaginatedResult<T> {
36
data: T[];
47
meta: {
@@ -11,35 +14,48 @@ export interface PaginatedResult<T> {
1114
};
1215
}
1316

14-
export type PaginateOptions = { page?: number | string; perPage?: number | string };
15-
export type PaginateFunction = <T, K>(model: any, args?: K, options?: PaginateOptions) => Promise<PaginatedResult<T>>;
16-
17-
export const createPaginator = (defaultOptions: PaginateOptions): PaginateFunction => {
18-
return async (model, args: any = { where: undefined }, options) => {
19-
const page = Number(options?.page || defaultOptions?.page) || 1;
20-
const perPage = Number(options?.perPage || defaultOptions?.perPage) || 10;
21-
22-
const skip = page > 0 ? perPage * (page - 1) : 0;
23-
const [total, data] = await Promise.all([
24-
model.count({ where: args.where }),
25-
model.findMany({
26-
...args,
27-
take: perPage,
28-
skip,
29-
}),
30-
]);
31-
const lastPage = Math.ceil(total / perPage);
32-
33-
return {
34-
data,
35-
meta: {
36-
total,
37-
lastPage,
38-
currentPage: page,
39-
perPage,
40-
prev: page > 1 ? page - 1 : null,
41-
next: page < lastPage ? page + 1 : null,
42-
},
43-
};
17+
// see: https://github.com/prisma/prisma/issues/6980
18+
type ModelDelegates = {
19+
[K in Prisma.ModelName]: PrismaClient[Uncapitalize<K>];
20+
};
21+
22+
export type PaginateOptions = { page?: number; perPage?: number };
23+
export type PaginateFunction = <T, K extends Prisma.ModelName>(
24+
model: ModelDelegates[K],
25+
args?: Parameters<ModelDelegates[K]['findMany']>[0],
26+
options?: PaginateOptions
27+
) => Promise<PaginatedResult<T>>;
28+
29+
export const calcSkip = (page: number, perPage: number): number => {
30+
return page > 0 ? perPage * (page - 1) : 0;
31+
};
32+
33+
export const getMeta = (total: number, page: number, perPage: number) => {
34+
const lastPage = Math.ceil(total / perPage);
35+
36+
return {
37+
total,
38+
lastPage,
39+
currentPage: page,
40+
perPage,
41+
prev: page > 1 ? page - 1 : null,
42+
next: page < lastPage ? page + 1 : null,
4443
};
4544
};
45+
46+
export const paginate: PaginateFunction = async (model, args, options) => {
47+
const { page = 1, perPage = 20 } = options || {};
48+
49+
const [total, data] = await Promise.all([
50+
// @ts-expect-error
51+
model.count({ where: args?.where }),
52+
// @ts-expect-error
53+
model.findMany({
54+
...args,
55+
take: perPage,
56+
skip: calcSkip(page, perPage),
57+
}),
58+
]);
59+
60+
return { data, meta: getMeta(total as number, page, perPage) };
61+
};

src/quote/repositories/prisma-quote.repository.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { PaginatedResult } from '@app/lib/prisma/helpers/pagination';
2-
import { createPaginator } from '@app/lib/prisma/helpers/pagination';
1+
import { paginate, type PaginatedResult } from '@app/lib/prisma/helpers/pagination';
32
import { PrismaManagerService } from '@app/lib/prisma/services/prisma-manager.service';
43
import { prismaQuoteToQuoteAdapter } from '@app/quote/adapters';
54
import type { QuoteRepositoryContract } from '@app/quote/contracts/quote-repository.contract';
@@ -13,7 +12,7 @@ import type {
1312
} from '@app/quote/dtos/quote-repository-dtos';
1413
import type { Quote } from '@app/quote/entities/quote.entity';
1514
import { Injectable } from '@nestjs/common';
16-
import type { Prisma, Quote as PrismaQuote } from '@prisma/client';
15+
import type { Quote as PrismaQuote } from '@prisma/client';
1716

1817
type FindManyReturn = PrismaQuote;
1918

@@ -33,17 +32,15 @@ export class PrismaQuoteRepository implements QuoteRepositoryContract {
3332
const { ...where } = input.where || {};
3433
const { perPage = 20, page = 1 } = input.options || {};
3534

36-
const paginate = createPaginator({ perPage });
37-
38-
const result = await paginate<FindManyReturn, Prisma.QuoteFindManyArgs>(
35+
const result = await paginate<FindManyReturn, 'Quote'>(
3936
this.prismaManager.getClient().quote,
4037
{
4138
where: {
4239
...where,
4340
},
4441
orderBy: [{ createdAt: 'desc' }],
4542
},
46-
{ page }
43+
{ page, perPage }
4744
);
4845

4946
return {

src/source/repositories/prisma-source.repository.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { PaginatedResult } from '@app/lib/prisma/helpers/pagination';
2-
import { createPaginator } from '@app/lib/prisma/helpers/pagination';
2+
import { paginate } from '@app/lib/prisma/helpers/pagination';
33
import { PrismaManagerService } from '@app/lib/prisma/services/prisma-manager.service';
44
import { prismaSourceToSourceAdapter } from '@app/source/adapters';
55
import type { SourceRepositoryContract } from '@app/source/contracts/source-repository.contract';
@@ -12,7 +12,7 @@ import type {
1212
} from '@app/source/dtos/source-repository-dtos';
1313
import type { Source } from '@app/source/entities/source.entity';
1414
import { Injectable } from '@nestjs/common';
15-
import type { Prisma, Source as PrismaSource } from '@prisma/client';
15+
import type { Source as PrismaSource } from '@prisma/client';
1616

1717
type FindManyReturn = PrismaSource;
1818

@@ -32,17 +32,15 @@ export class PrismaSourceRepository implements SourceRepositoryContract {
3232
const { ...where } = input.where || {};
3333
const { perPage = 20, page = 1 } = input.options || {};
3434

35-
const paginate = createPaginator({ perPage });
36-
37-
const result = await paginate<FindManyReturn, Prisma.SourceFindManyArgs>(
35+
const result = await paginate<FindManyReturn, 'Source'>(
3836
this.prismaManager.getClient().source,
3937
{
4038
where: {
4139
...where,
4240
},
4341
orderBy: [{ createdAt: 'desc' }],
4442
},
45-
{ page }
43+
{ page, perPage }
4644
);
4745

4846
return {

src/tag/repositories/prisma-tag.repository.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { PaginatedResult } from '@app/lib/prisma/helpers/pagination';
2-
import { createPaginator } from '@app/lib/prisma/helpers/pagination';
1+
import { paginate, type PaginatedResult } from '@app/lib/prisma/helpers/pagination';
32
import { PrismaManagerService } from '@app/lib/prisma/services/prisma-manager.service';
43
import { prismaTagToTagAdapter } from '@app/tag/adapters';
54
import type { TagRepositoryContract } from '@app/tag/contracts/tag-repository.contract';
@@ -13,7 +12,7 @@ import type {
1312
} from '@app/tag/dtos/tag-repository-dtos';
1413
import type { Tag } from '@app/tag/entities/tag.entity';
1514
import { Injectable } from '@nestjs/common';
16-
import type { Prisma, Tag as PrismaTag } from '@prisma/client';
15+
import type { Tag as PrismaTag } from '@prisma/client';
1716

1817
type FindManyReturn = PrismaTag;
1918

@@ -33,17 +32,15 @@ export class PrismaTagRepository implements TagRepositoryContract {
3332
const { ...where } = input.where || {};
3433
const { perPage = 20, page = 1 } = input.options || {};
3534

36-
const paginate = createPaginator({ perPage });
37-
38-
const result = await paginate<FindManyReturn, Prisma.TagFindManyArgs>(
35+
const result = await paginate<FindManyReturn, 'Tag'>(
3936
this.prismaManager.getClient().tag,
4037
{
4138
where: {
4239
...where,
4340
},
4441
orderBy: [{ createdAt: 'desc' }],
4542
},
46-
{ page }
43+
{ page, perPage }
4744
);
4845

4946
return {

0 commit comments

Comments
 (0)