UNPKG

@js-ak/db-manager

Version:
420 lines (419 loc) 17.4 kB
import * as Helpers from "../helpers/index.js"; import * as connection from "../connection.js"; import { QueryBuilder } from "../query-builder/index.js"; import queries from "./queries.js"; /** * @experimental * The `BaseMaterializedView` class provides methods to interact with and manage materialized views * in a PostgreSQL database. It includes functionality for querying, counting, and refreshing materialized views. */ export class BaseMaterializedView { #sortingOrders = new Set(["ASC", "DESC"]); #coreFieldsSet; #isLoggerEnabled; #logger; #executeSql; #executeSqlStream; #initialArgs; /** * The PostgreSQL executor. * - pg.Pool * - pg.PoolClient * - pg.Client */ #executor; /** * The name of the materialized view. */ name; /** * @type The core fields of the materialized view. */ coreFields; /** * Creates an instance of `BaseMaterializedView`. * * @param data - Data for initializing the materialized view. * @param data.coreFields - The core fields of the materialized view. * @param data.name - The name of the materialized view. * @param [data.additionalSortingFields] - Additional fields allowed for sorting. * @param [dbCreds] - Database credentials. * @param [options] - Additional options. */ constructor(data, dbCreds, options) { const { client } = options || {}; if (client) { this.#executor = client; } else if (dbCreds) { this.#executor = connection.getStandardPool(dbCreds); } else { throw new Error("No client or dbCreds provided"); } this.name = data.name; this.coreFields = data.coreFields; this.#coreFieldsSet = new Set([ ...this.coreFields, ...(data.additionalSortingFields || []), ]); this.#initialArgs = { data, dbCreds, options }; const { isLoggerEnabled, logger } = options || {}; const preparedOptions = Helpers.setLoggerAndExecutor(this.#executor, { isLoggerEnabled, logger }); const { executeSqlStream } = Helpers.setStreamExecutor(this.#executor, { isLoggerEnabled, logger }); this.#executeSql = preparedOptions.executeSql; this.#executeSqlStream = executeSqlStream; this.#isLoggerEnabled = preparedOptions.isLoggerEnabled; this.#logger = preparedOptions.logger; } /** * Gets the database client for the materialized view. * * @returns The database client for the materialized view. */ get pool() { return this.#executor; } /** * Gets the PostgreSQL executor for the materialized view. * * @returns The PostgreSQL executor for the materialized view. */ get executor() { return this.#executor; } /** * Sets the logger for the materialized view. * * @param logger - The logger to use for the materialized view. */ setLogger(logger) { const preparedOptions = Helpers.setLoggerAndExecutor(this.#executor, { isLoggerEnabled: true, logger }); const { executeSqlStream } = Helpers.setStreamExecutor(this.#executor, { isLoggerEnabled: true, logger }); this.#executeSql = preparedOptions.executeSql; this.#executeSqlStream = executeSqlStream; this.#isLoggerEnabled = preparedOptions.isLoggerEnabled; this.#logger = preparedOptions.logger; } /** * Sets the executor for the materialized view. * * @param executor - The executor to use for the materialized view. */ setExecutor(executor) { const preparedOptions = Helpers.setLoggerAndExecutor(executor, { isLoggerEnabled: this.#isLoggerEnabled, logger: this.#logger }); const { executeSqlStream } = Helpers.setStreamExecutor(executor, { isLoggerEnabled: this.#isLoggerEnabled, logger: this.#logger }); this.#executeSql = preparedOptions.executeSql; this.#executeSqlStream = executeSqlStream; this.#isLoggerEnabled = preparedOptions.isLoggerEnabled; this.#logger = preparedOptions.logger; this.#executor = executor; } get isLoggerEnabled() { return this.#isLoggerEnabled; } get executeSql() { return this.#executeSql; } get executeSqlStream() { return this.#executeSqlStream; } /** * Sets the client in the current class. * * @experimental * * @param client - The client connection to set. * * @returns The current instance with the new connection client. */ setClientInCurrentClass(client) { return new this.constructor({ ...this.#initialArgs.data }, this.#initialArgs.dbCreds ? { ...this.#initialArgs.dbCreds } : undefined, { ...this.#initialArgs.options, client }); } /** * Sets the client in the base class. * * @experimental * * @param client - The client connection to set. * * @returns A new instance of the base class with the new connection client. */ setClientInBaseClass(client) { return new BaseMaterializedView({ ...this.#initialArgs.data }, this.#initialArgs.dbCreds ? { ...this.#initialArgs.dbCreds } : undefined, { ...this.#initialArgs.options, client }); } /** * Compare fields for queries. */ compareFields = Helpers.compareFields; /** * Get fields to search in queries. */ getFieldsToSearch = Helpers.getFieldsToSearch; /** * Set of methods for generating comparison queries. */ compareQuery = { /** * Generates a SQL query and values for selecting an array of records based on search parameters. * * @param params - Search parameters. * @param params.$and - AND conditions for the search. * @param [params.$or] - OR conditions for the search. * @param [selected=["*"]] - Fields to be selected. * @param [pagination] - Pagination details. * @param [order] - Order by details. * @param order.orderBy - Field to order by. * @param order.ordering - Ordering direction ("ASC" or "DESC"). * * @returns An object containing the query string and values array. */ getArrByParams: ({ $and = {}, $or }, selected = ["*"], pagination, order) => { if (order?.length) { for (const o of order) { if (!this.#coreFieldsSet.has(o.orderBy)) { const allowedFields = Array.from(this.#coreFieldsSet).join(", "); throw new Error(`Invalid orderBy: ${o.orderBy}. Allowed fields are: ${allowedFields}`); } if (!this.#sortingOrders.has(o.ordering)) { throw new Error("Invalid ordering"); } } } if (!selected.length) selected.push("*"); const { queryArray, queryOrArray, values } = this.compareFields($and, $or); const { orderByFields, paginationFields, searchFields, selectedFields } = this.getFieldsToSearch({ queryArray, queryOrArray }, selected, pagination, order); return { query: queries.getByParams(this.name, selectedFields, searchFields, orderByFields, paginationFields), values, }; }, /** * Generates a SQL query and values for counting records based on search parameters. * * @param params - Search parameters. * @param params.$and - AND conditions for the search. * @param [params.$or] - OR conditions for the search. * * @returns An object containing the query string and values array. */ getCountByParams: ({ $and = {}, $or }) => { const { queryArray, queryOrArray, values } = this.compareFields($and, $or); const { searchFields } = this.getFieldsToSearch({ queryArray, queryOrArray }); return { query: queries.getCountByParams(this.name, searchFields), values, }; }, /** * Generates a SQL query and values for selecting a single record based on search parameters. * * @param params - Search parameters. * @param params.$and - AND conditions for the search. * @param [params.$or] - OR conditions for the search. * @param [selected=["*"]] - Fields to be selected. * * @returns An object containing the query string and values array. */ getOneByParams: ({ $and = {}, $or }, selected = ["*"]) => { if (!selected.length) selected.push("*"); const { queryArray, queryOrArray, values } = this.compareFields($and, $or); const { orderByFields, paginationFields, searchFields, selectedFields } = this.getFieldsToSearch({ queryArray, queryOrArray }, selected, { limit: 1, offset: 0 }); return { query: queries.getByParams(this.name, selectedFields, searchFields, orderByFields, paginationFields), values, }; }, /** * Generates a SQL query and values for selecting an array of records based on search parameters. * * @param params - Search parameters. * @param params.$and - AND conditions for the search. * @param [params.$or] - OR conditions for the search. * @param [selected=["*"]] - Fields to be selected. * @param [pagination] - Pagination details. * @param [order] - Order by details. * @param order.orderBy - Field to order by. * @param order.ordering - Ordering direction ("ASC" or "DESC"). * * @returns An object containing the query string and values array. */ streamArrByParams: ({ $and = {}, $or }, selected = ["*"], pagination, order) => { if (order?.length) { for (const o of order) { if (!this.#coreFieldsSet.has(o.orderBy)) { const allowedFields = Array.from(this.#coreFieldsSet).join(", "); throw new Error(`Invalid orderBy: ${o.orderBy}. Allowed fields are: ${allowedFields}`); } if (!this.#sortingOrders.has(o.ordering)) { throw new Error("Invalid ordering"); } } } if (!selected.length) selected.push("*"); const { queryArray, queryOrArray, values } = this.compareFields($and, $or); const { orderByFields, paginationFields, searchFields, selectedFields } = this.getFieldsToSearch({ queryArray, queryOrArray }, selected, pagination, order); return { query: queries.getByParams(this.name, selectedFields, searchFields, orderByFields, paginationFields), values, }; }, }; /** * Executes a query to get an array of records based on provided parameters. * * @param params - Search parameters. * @param params.$and - AND conditions for the search. * @param [params.$or] - OR conditions for the search. * @param [selected=["*"]] - Fields to be selected. * @param [pagination] - Pagination details. * @param [order] - Order by details. * @param order.orderBy - Field to order by. * @param order.ordering - Ordering direction ("ASC" or "DESC"). * * @returns A promise that resolves to an array of records. */ async getArrByParams(params, selected = ["*"], pagination, order) { const sql = this.compareQuery.getArrByParams(params, selected, pagination, order); const { rows } = await this.#executeSql(sql); return rows; } /** * Executes a query to count records based on provided parameters. * * @param params - Search parameters. * @param params.$and - AND conditions for the search. * @param [params.$or] - OR conditions for the search. * * @returns A promise that resolves to the count of records. */ async getCountByParams(params) { const sql = this.compareQuery.getCountByParams(params); const { rows } = await this.executeSql(sql); return Number(rows[0]?.count) || 0; } /** * Executes a query to get a single record based on provided parameters. * * @param params - Search parameters. * @param params.$and - AND conditions for the search. * @param [params.$or] - OR conditions for the search. * @param [selected=["*"]] - Fields to be selected. * * @returns A promise that resolves to a single record or undefined if no record is found. */ async getOneByParams(params, selected = ["*"]) { const sql = this.compareQuery.getOneByParams(params, selected); const { rows } = await this.#executeSql(sql); return rows[0]; } /** * Refreshes the materialized view. * * @param [concurrently=false] - Whether to refresh the view concurrently. * * @returns A promise that resolves when the view is refreshed. */ async refresh(concurrently = false) { const query = `REFRESH MATERIALIZED VIEW ${concurrently ? "CONCURRENTLY" : ""} ${this.name}`; await this.#executeSql({ query }); } /** * Returns a stream of records from the database based on the provided search parameters. * Useful for handling large result sets efficiently without loading all data into memory. * * @param params - The filter conditions for the query. * @param params.$and - Required set of conditions combined with AND. * @param [params.$or] - Optional set of conditions combined with OR. * * @param [selected=["*"]] - List of field names to select from each record. * * @param [pagination] - Controls the offset and limit of the query. * * @param [order] - Specifies sorting rules for the result set. * @param order[].orderBy - Field to sort the results by. * @param order[].ordering - Sorting direction: `"ASC"` or `"DESC"`. * * @param [streamOptions] - Optional configuration for stream behavior: * - `batchSize`: Number of rows fetched from the database per batch. * - `highWaterMark`: Maximum number of rows buffered internally before pausing reads. * - `rowMode`: If set to `"array"`, rows are returned as arrays instead of objects. * - `types`: Custom type parser map for PostgreSQL data types. * * @returns A readable stream that emits rows of type `T` on the `"data"` event. */ async streamArrByParams(params, selected = ["*"], pagination, order, streamOptions) { const sql = this.compareQuery.streamArrByParams(params, selected, pagination, order); return this.#executeSqlStream(sql, streamOptions); } /** * Returns a new QueryBuilder instance for building SQL queries. * * @param [options] - Optional settings for the query builder. * @param [options.client] - Optional database client or pool to use for query execution. * @param [options.isLoggerEnabled] - Whether to enable logging for this query builder. * @param [options.logger] - Optional custom logger instance. * @param [options.name] - Optional materialized name to use (defaults to this.name). * * @returns A configured QueryBuilder instance. */ queryBuilder(options) { const { client, isLoggerEnabled, logger, name } = options || {}; return new QueryBuilder(name ?? this.name, client ?? this.#executor, { isLoggerEnabled: isLoggerEnabled ?? this.#isLoggerEnabled, logger: logger ?? this.#logger, }); } // STATIC METHODS /** * Gets a standard connection pool. * * @static * @param creds - Database credentials. * @param [poolName] - Optional pool name. * * @returns A new PostgreSQL connection pool. */ static getStandardPool(creds, poolName) { return connection.getStandardPool(creds, poolName); } /** * Removes a standard connection pool. * * @static * @param creds - Database credentials. * @param [poolName] - Optional pool name. * * @returns A promise that resolves when the pool is removed. */ static async removeStandardPool(creds, poolName) { return connection.removeStandardPool(creds, poolName); } /** * Gets a transaction connection pool. * * @static * @param creds - Database credentials. * @param [poolName] - Optional pool name. * * @returns A new PostgreSQL transaction connection pool. */ static getTransactionPool(creds, poolName) { return connection.getTransactionPool(creds, poolName); } /** * Removes a transaction connection pool. * * @static * @param creds - Database credentials. * @param [poolName] - Optional pool name. * * @returns A promise that resolves when the pool is removed. */ static async removeTransactionPool(creds, poolName) { return connection.removeTransactionPool(creds, poolName); } }