import { SelectQueryBuilder, ObjectType } from 'typeorm' import { OrderBy, PromisePagePagination, PagePagination, Nullable, Take } from './interfaces/paginator' import { normalizeOrderBy } from './utils/normalizeOrderBy' export interface PagePaginatorParams> { columnNames?: TColumnNames | null take?: Nullable | number | null orderBy: OrderBy | OrderBy[] } export interface PagePaginatorPaginateParams> { page?: number | null take?: number | null orderBy?: OrderBy | OrderBy[] } export class PagePaginator> { orderBy: OrderBy | OrderBy[] columnNames: Record takeOptions: Take constructor( public entity: ObjectType, { orderBy, columnNames, take, }: PagePaginatorParams, ) { this.orderBy = orderBy this.columnNames = columnNames ?? {} this.takeOptions = typeof take === 'number' ? { default: take, min: 0, max: Infinity, } : { default: take?.default ?? 20, min: Math.max(0, take?.min ?? 0), // never negative max: take?.max ?? Infinity, } } async paginate(qb: SelectQueryBuilder, params: PagePaginatorPaginateParams = {}): Promise> { const page = Math.max(params.page ?? 1, 1) const take = Math.max(this.takeOptions.min, Math.min(params.take || this.takeOptions.default, this.takeOptions.max)) const qbForCount = qb.clone() for (const [key, value] of normalizeOrderBy(params.orderBy ?? this.orderBy)) { qb.addOrderBy(this.columnNames[key] ?? `${qb.alias}.${key}`, value ? 'ASC' : 'DESC') } let hasNext = false const nodes = await qb.clone().offset((page - 1) * take).limit(take + 1).getMany().then(nodes => { if (nodes.length > take) { hasNext = true } return nodes.slice(0, take) }) return { count: await qbForCount.getCount(), nodes, hasNext, } } promisePaginate(qb: SelectQueryBuilder, params: PagePaginatorPaginateParams = {}): PromisePagePagination { const page = Math.max(params.page ?? 1, 1) const take = Math.max(this.takeOptions.min, Math.min(params.take || this.takeOptions.default, this.takeOptions.max)) const qbForCount = qb.clone() for (const [key, value] of normalizeOrderBy(params.orderBy ?? this.orderBy)) { qb.addOrderBy(this.columnNames[key] ?? `${qb.alias}.${key}`, value ? 'ASC' : 'DESC') } let cachePromiseNodes = null as Promise, 'count'>> | null const promiseNodes = () => { if (!cachePromiseNodes) { cachePromiseNodes = qb.clone().offset((page - 1) * take).limit(take + 1).getMany().then(nodes => { let hasNext = false if (nodes.length > take) { hasNext = true } return { hasNext, nodes: nodes.slice(0, take), } }) } return cachePromiseNodes } return { get count() { return qbForCount.getCount() }, get nodes() { return promiseNodes().then(({ nodes }) => nodes) }, get hasNext() { return promiseNodes().then(({ hasNext }) => hasNext) }, } } }