import { type SelectQueryBuilder, type ReferenceExpression, type OperandValueExpression, sql } from 'kysely' type WhereInput = Partial<{ [C in keyof DB[TB]]: | OperandValueExpression | readonly OperandValueExpression[] | null }> export function applyWhere( builder: SelectQueryBuilder, where: WhereInput, ): SelectQueryBuilder { return Object.entries(where).reduce((builder, [key, value]) => { const column = key as ReferenceExpression if (value === null) { return builder.where(column, 'is', null) } else if (Array.isArray(value)) { return builder.where(column, 'in', value) } return builder.where(column, '=', value) }, builder) } interface PaginationInput { where: WhereInput limit?: number offset?: number orderBy?: { column: ReferenceExpression direction?: 'asc' | 'desc' } } export function applyPagination( builder: SelectQueryBuilder, { limit, offset, orderBy }: PaginationInput, ): SelectQueryBuilder { let qb = builder if (orderBy) { qb = qb.orderBy(orderBy.column, orderBy.direction ?? 'asc') } if (limit !== undefined) { qb = qb.limit(limit) } if (offset !== undefined) { qb = qb.offset(offset) } return qb } export async function paginate( baseDataQuery: SelectQueryBuilder, baseCountQuery: SelectQueryBuilder, input: PaginationInput, ) { const dataQuery = applyPagination(input.where ? applyWhere(baseDataQuery, input.where) : baseDataQuery, input) let countQuery = baseCountQuery.select(sql`count(*)::int`.as('totalCount')) if (input.where) { countQuery = applyWhere(countQuery, input.where) } const [countRow, items] = await Promise.all([countQuery.executeTakeFirstOrThrow(), dataQuery.execute()]) return { items, totalCount: (countRow as { totalCount: number }).totalCount, limit: input.limit, offset: input.offset, count: items.length, } }