UNPKG

@decaf-ts/core

Version:

Core persistence module for the decaf framework

191 lines 7.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Paginator = void 0; const errors_1 = require("./errors.cjs"); const persistence_1 = require("./../persistence/index.cjs"); const logging_1 = require("@decaf-ts/logging"); const constants_1 = require("./constants.cjs"); const Repository_1 = require("./../repository/Repository.cjs"); const db_decorators_1 = require("@decaf-ts/db-decorators"); /** * @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 */ class Paginator extends logging_1.LoggedClass { get current() { return this._currentPage; } get total() { return this._totalPages; } get count() { return this._recordCount; } get statement() { if (!this._statement) this._statement = this.prepare(this.query); return this._statement; } constructor(adapter, query, size, clazz) { super(); this.adapter = adapter; this.query = query; this.size = size; this.clazz = clazz; (0, persistence_1.prefixMethod)(this, this.page, this.pagePrefix, this.page.name); } isPreparedStatement() { const query = this.query; return (query.method && query.method.match(new RegExp(`${constants_1.PreparedStatementKeys.FIND_BY}|${constants_1.PreparedStatementKeys.LIST_BY}`, "gi"))); } async pagePrefix(page, ...args) { const contextArgs = await persistence_1.Context.args(persistence_1.PersistenceKeys.QUERY, this.clazz, args, this.adapter); return [page, ...contextArgs.args]; } async pagePrepared(page, ...argz) { const repo = Repository_1.Repository.forModel(this.clazz, this.adapter.alias); const statement = this.query; const { method, args, params } = statement; const regexp = new RegExp(`^${constants_1.PreparedStatementKeys.FIND_BY}|${constants_1.PreparedStatementKeys.LIST_BY}`, "gi"); if (!method.match(regexp)) throw new persistence_1.UnsupportedError(`Method ${method} is not supported for pagination`); regexp.lastIndex = 0; const pagedMethod = method.replace(regexp, constants_1.PreparedStatementKeys.PAGE_BY); const preparedArgs = [pagedMethod, ...args]; let preparedParams = { limit: this.size, offset: page, bookmark: this._bookmark, }; if (pagedMethod === constants_1.PreparedStatementKeys.PAGE_BY && preparedArgs.length <= 2) { preparedArgs.push(params.direction); } else { preparedParams = { direction: params.direction, limit: this.size, offset: page, bookmark: this._bookmark, }; } preparedArgs.push(preparedParams); const result = await repo.statement(...preparedArgs, ...argz); return this.apply(result); } async next(...args) { return this.page(this.current + 1, ...args); } async previous(...args) { return this.page(this.current - 1, ...args); } validatePage(page) { if (page < 1 || !Number.isInteger(page)) throw new errors_1.PagingError("Page number cannot be under 1 and must be an integer"); if (typeof this._totalPages !== "undefined" && page > this._totalPages) throw new errors_1.PagingError(`Only ${this._totalPages} are available. Cannot go to page ${page}`); return page; } async page(page = 1, ...args) { const { ctxArgs } = this.adapter["logCtx"](args, this.page); if (this.isPreparedStatement()) return (await this.pagePrepared(page, ...ctxArgs)); throw new persistence_1.UnsupportedError("Raw support not available without subclassing this"); } serialize(data, toString = false) { const serialization = { data: data, current: this.current, total: this.total, count: this.count, bookmark: this._bookmark, }; try { return toString ? JSON.stringify(serialization) : serialization; } catch (e) { throw new db_decorators_1.SerializationError(e); } } apply(serialization) { const ser = typeof serialization === "string" ? Paginator.deserialize(serialization) : serialization; this._currentPage = ser.current; this._totalPages = ser.total; this._recordCount = ser.count; this._bookmark = ser.bookmark; return ser.data; } static deserialize(str) { try { return JSON.parse(str); } catch (e) { throw new db_decorators_1.SerializationError(e); } } static isSerializedPage(obj) { return (obj && typeof obj === "object" && Array.isArray(obj.data) && typeof obj.total === "number" && typeof obj.current === "number" && typeof obj.count === "number"); } } exports.Paginator = Paginator; //# sourceMappingURL=Paginator.js.map