import { PagingError } from "./errors";
import { Adapter } from "../persistence";
import { Constructor, Model } from "@decaf-ts/decorator-validation";
/**
* @description Handles pagination for database queries
* @summary Provides functionality for navigating through paginated query results
*
* This abstract class manages the state and navigation of paginated database query results.
* It tracks the current page, total pages, and record count, and provides methods for
* moving between pages.
*
* @template M - The model type this paginator operates on
* @template R - The return type of the paginated query (defaults to M[])
* @template Q - The query type (defaults to any)
* @param {Adapter<any, Q, any, any>} adapter - The database adapter to use for executing queries
* @param {Q} query - The query to paginate
* @param {number} size - The number of records per page
* @param {Constructor<M>} clazz - The constructor for the model type
* @class Paginator
* @example
* // Create a paginator for a user query
* const userQuery = db.select().from(User);
* const paginator = await userQuery.paginate(10); // 10 users per page
*
* // Get the first page of results
* const firstPage = await paginator.page(1);
*
* // Navigate to the next page
* const secondPage = await paginator.next();
*
* // Get information about the pagination
* console.log(`Page ${paginator.current} of ${paginator.total}, ${paginator.count} total records`);
*
* @mermaid
* sequenceDiagram
* participant Client
* participant Paginator
* participant Adapter
* participant Database
*
* Client->>Paginator: new Paginator(adapter, query, size, clazz)
* Client->>Paginator: page(1)
* Paginator->>Paginator: validatePage(1)
* Paginator->>Paginator: prepare(query)
* Paginator->>Adapter: execute query with pagination
* Adapter->>Database: execute query
* Database-->>Adapter: return results
* Adapter-->>Paginator: return results
* Paginator-->>Client: return page results
*
* Client->>Paginator: next()
* Paginator->>Paginator: page(current + 1)
* Paginator->>Paginator: validatePage(current + 1)
* Paginator->>Adapter: execute query with pagination
* Adapter->>Database: execute query
* Database-->>Adapter: return results
* Adapter-->>Paginator: return results
* Paginator-->>Client: return page results
*/
export abstract class Paginator<M extends Model, R = M[], Q = any> {
protected _currentPage!: number;
protected _totalPages!: number;
protected _recordCount!: number;
protected limit!: number;
private _statement?: Q;
get current() {
return this._currentPage;
}
get total() {
return this._totalPages;
}
get count(): number {
return this._recordCount;
}
protected get statement() {
if (!this._statement) this._statement = this.prepare(this.query);
return this._statement;
}
protected constructor(
protected readonly adapter: Adapter<any, Q, any, any>,
protected readonly query: Q,
readonly size: number,
protected readonly clazz: Constructor<M>
) {}
protected abstract prepare(rawStatement: Q): Q;
async next() {
return this.page(this.current + 1);
}
async previous() {
return this.page(this.current - 1);
}
protected validatePage(page: number) {
if (page < 1 || !Number.isInteger(page))
throw new PagingError(
"Page number cannot be under 1 and must be an integer"
);
if (typeof this._totalPages !== "undefined" && page > this._totalPages)
throw new PagingError(
`Only ${this._totalPages} are available. Cannot go to page ${page}`
);
return page;
}
abstract page(page?: number): Promise<R[]>;
}
Source