typeorm-paginator
Version:
TypeORM query builder pagination library.
110 lines (93 loc) • 3.57 kB
text/typescript
import { SelectQueryBuilder, ObjectType } from 'typeorm'
import { OrderBy, PromisePagePagination, PagePagination, Nullable, Take } from './interfaces/paginator'
import { normalizeOrderBy } from './utils/normalizeOrderBy'
export interface PagePaginatorParams<TEntity, TColumnNames extends Record<string, string>> {
columnNames?: TColumnNames | null
take?: Nullable<Take> | number | null
orderBy: OrderBy<TEntity & TColumnNames> | OrderBy<TEntity & TColumnNames>[]
}
export interface PagePaginatorPaginateParams<TEntity, TColumnNames extends Record<string, string>> {
page?: number | null
take?: number | null
orderBy?: OrderBy<TEntity & TColumnNames> | OrderBy<TEntity & TColumnNames>[]
}
export class PagePaginator<TEntity, TColumnNames extends Record<string, string>> {
orderBy: OrderBy<TEntity> | OrderBy<TEntity>[]
columnNames: Record<string, string>
takeOptions: Take
constructor(
public entity: ObjectType<TEntity>,
{
orderBy,
columnNames,
take,
}: PagePaginatorParams<TEntity, TColumnNames>,
) {
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<TEntity>, params: PagePaginatorPaginateParams<TEntity, TColumnNames> = {}): Promise<PagePagination<TEntity>> {
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<TEntity>, params: PagePaginatorPaginateParams<TEntity, TColumnNames> = {}): PromisePagePagination<TEntity> {
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<Omit<PagePagination<any>, '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)
},
}
}
}