subscription/src/server/utils/pagination.util.ts
2025-09-21 21:06:02 +01:00

193 lines
5.7 KiB
TypeScript

// import { count, SQL } from "drizzle-orm";
// import { PgDatabase, PgTable, PgTableWithColumns } from "drizzle-orm/pg-core";
// // import { AnyTable } from 'drizzle-orm';
// // Adjust if you use SQLite or another database
// type DrizzleDB = PgDatabase<any, any>;
// /**
// * Generic pagination utility for Drizzle ORM.
// *
// * @template T - The Drizzle ORM schema table type.
// * @param {DrizzleDB} db - The Drizzle ORM database instance.
// * @param {T} table - The Drizzle ORM schema table to paginate.
// * @param {number} page - The current page number (1-indexed).
// * @param {number} limit - The number of items per page.
// * @param {SQL | undefined} whereClause - Optional Drizzle SQL condition for filtering.
// * @returns {Promise<{ data: T[], totalRecords: number, totalPages: number, currentPage: number, pageSize: number }>}
// */
// export async function paginate<T extends PgTable>({ // Adjusted for PgTable
// db,
// table,
// page = 1,
// limit = 10,
// whereClause,
// }: {
// db: DrizzleDB;
// table: PgTableWithColumns<any>;
// page: number;
// limit: number;
// whereClause?: SQL | undefined; // Add optional where clause for filtering
// }): Promise<{
// data: (typeof table.$inferSelect)[]; // Use $inferSelect for proper type inference
// totalRecords: number;
// totalPages: number;
// currentPage: number;
// pageSize: number;
// }> {
// if (page < 1) page = 1;
// if (limit < 1) limit = 10;
// const offset = (page - 1) * limit;
// // Build the query
// let query = db.select().from(table).$dynamic(); // Use $dynamic to conditionally add clauses
// if (whereClause) {
// query = query.where(whereClause);
// }
// const dataPromise = query.limit(limit).offset(offset).execute();
// // Get the total count, potentially with the same where clause
// let countQuery = db
// .select({
// count: count(),
// })
// .from(table)
// .$dynamic();
// if (whereClause) {
// countQuery = countQuery.where(whereClause);
// }
// const countPromise = countQuery.execute();
// const [data, [countResult]] = await Promise.all([dataPromise, countPromise]);
// const totalRecords = countResult?.count || 0;
// const totalPages = Math.ceil(totalRecords / limit);
// return {
// data: data,
// totalRecords,
// totalPages,
// currentPage: page,
// pageSize: limit,
// };
// }
import { and, count, SQL } from "drizzle-orm";
import { PgDatabase, PgTable, PgTableWithColumns } from "drizzle-orm/pg-core";
import { FilterCondition } from "../../lib/types";
type DrizzleDB = PgDatabase<any, any>;
/**
* Generic pagination utility for Drizzle ORM.
*
* @template T - The Drizzle ORM schema table type.
* @param {DrizzleDB} db - The Drizzle ORM database instance.
* @param {T} table - The Drizzle ORM schema table to paginate.
* @param {number} page - The current page number (1-indexed).
* @param {number} limit - The number of items per page.
* @param {FilterCondition[] | undefined} filters - An array of filter conditions, which can be single SQL expressions or nested 'and'/'or' groups.
* @returns {Promise<{ data: T[], totalRecords: number, totalPages: number, currentPage: number, pageSize: number }>}
*/
export async function paginate<T extends PgTable>({
db,
table,
page = 1,
limit = 10,
filters, // Now using FilterCondition[]
}: {
db: DrizzleDB;
table: PgTableWithColumns<any>;
page?: number;
limit?: number;
filters?: FilterCondition[]; // Accepts the new structured filters
}): Promise<{
data: (typeof table.$inferSelect)[];
totalRecords: number;
totalPages: number;
currentPage: number;
pageSize: number;
}> {
if (page < 1) page = 1;
if (limit < 1) limit = 10;
const offset = (page - 1) * limit;
// Helper function to process the nested filter conditions
const buildWhereClause = (conditions?: FilterCondition[]): SQL | undefined => {
if (!conditions || conditions.length === 0) {
return undefined;
}
const builtConditions: SQL[] = [];
for (const condition of conditions) {
if (condition instanceof SQL) {
builtConditions.push(condition);
} else if ('and' in condition && Array.isArray(condition.and)) {
const nestedAnd = buildWhereClause(condition.and);
if (nestedAnd) {
builtConditions.push(nestedAnd);
}
} else if ('or' in condition && Array.isArray(condition.or)) {
const nestedOr = buildWhereClause(condition.or);
if (nestedOr) {
builtConditions.push(nestedOr);
}
}
}
// By default, if multiple top-level conditions are provided without an explicit operator,
// we'll combine them with 'AND'. If you want a different default, adjust here.
if (builtConditions.length === 1) {
return builtConditions[0];
} else if (builtConditions.length > 1) {
return and(...builtConditions);
}
return undefined;
};
const finalWhereClause = buildWhereClause(filters);
// Build the query
let query = db.select().from(table).$dynamic();
if (finalWhereClause) {
query = query.where(finalWhereClause);
}
const dataPromise = query.limit(limit).offset(offset).execute();
// Get the total count, potentially with the same where clause
let countQuery = db
.select({
count: count(),
})
.from(table)
.$dynamic();
if (finalWhereClause) {
countQuery = countQuery.where(finalWhereClause);
}
const countPromise = countQuery.execute();
const [data, [countResult]] = await Promise.all([dataPromise, countPromise]);
const totalRecords = countResult?.count || 0;
const totalPages = Math.ceil(totalRecords / limit);
return {
data: data.reverse(),
totalRecords,
totalPages,
currentPage: page,
pageSize: limit,
};
}