tiny-essentials
Version:
Collection of small, essential scripts designed to be used across various projects. These simple utilities are crafted for speed, ease of use, and versatility.
133 lines (118 loc) • 4.52 kB
JavaScript
;
/**
* A predicate function used to determine whether an item should be included in the filtered results.
* Works similarly to the callback function of `Array.prototype.filter`.
*
* @callback GetFilter
* @param {any} value - The current element being processed in the array.
* @param {number} index - The index of the current element within the array.
* @param {any[]} array - The full array being processed.
* @returns {boolean} Returns `true` to include the element in the results, or `false` to exclude it.
*/
/**
* A encapsulated wrapper for array pagination.
* Provides methods to retrieve paginated results with metadata,
* while keeping the source array safe from direct modifications.
*/
class TinyArrayPaginator {
/**
* Internal storage for the paginated data source.
* @type {any[]|Set<any>}
*/
#data;
/**
* Gets current stored array.
* @returns {any[]|Set<any>}
*/
get data() {
return this.#data;
}
/**
* Replaces the current data array.
* @param {any[]|Set<any>} value - The new array to be used as the data source.
* @throws {TypeError} If the provided value is not an array.
*/
set data(value) {
if (!Array.isArray(value) && !(value instanceof Set))
throw new TypeError('Paginator expects an array or Set as data source.');
this.#data = value;
}
/**
* Gets the total number of items in the current data array.
* @returns {number} Total number of stored items.
*/
get size() {
return Array.isArray(this.#data) ? this.#data.length : this.#data.size;
}
/**
* Creates a new paginator instance for the given data array.
* @param {any[]|Set<any>} data - The array to be paginated.
* @throws {TypeError} If the provided data is not an array.
*/
constructor(data) {
if (!Array.isArray(data) && !(data instanceof Set))
throw new TypeError('Paginator expects an array or Set as data source.');
this.#data = data;
}
/**
* Filters data according to a search query and returns paginated results.
* @param {Object} settings
* @param {number} settings.page - The page number (1-based index).
* @param {number} settings.perPage - Items per page.
* @param {Record<string, any> | GetFilter} [settings.filter=null] - Filtering criteria:
* - Object: key-value pairs for exact match, substring match, or RegExp
* - Function: custom filter function returning true for items to include
* @returns {{
* items: any[], // The subset of items for the requested page.
* page: number, // The current (validated) page number.
* perPage: number, // Number of items per page used in the calculation.
* totalItems: number, // Total number of items in the filtered data.
* totalPages: number, // Total number of pages available.
* hasPrev: boolean, // Whether a previous page exists.
* hasNext: boolean // Whether a next page exists.
* }}
*/
get({ page, perPage, filter }) {
if (!Number.isInteger(page) || page < 1)
throw new RangeError('Page number must be a positive integer.');
if (!Number.isInteger(perPage) || perPage < 1)
throw new RangeError('Items per page must be a positive integer.');
const data = Array.isArray(this.#data) ? this.#data : Array.from(this.#data);
let dataToUse = data;
if (filter) {
if (typeof filter === 'function') {
dataToUse = data.filter((item, idx, arr) => filter(item, idx, arr));
} else if (typeof filter === 'object') {
dataToUse = data.filter((item) =>
Object.entries(filter).every(([key, value]) => {
const v = item[key];
if (value instanceof RegExp) return value.test(v);
return v === value;
}),
);
} else {
throw new TypeError('Filter must be an object or a function');
}
} else {
dataToUse = data;
}
const totalItems = dataToUse.length;
const totalPages = Math.max(1, Math.ceil(totalItems / perPage));
// Ensure page is within valid range
const safePage = Math.min(page, totalPages);
const start = (safePage - 1) * perPage;
const end = start + perPage;
const hasPrev = safePage > 1;
const hasNext = safePage < totalPages;
return {
items: dataToUse.slice(start, end),
page: safePage,
perPage,
totalItems,
totalPages,
hasPrev,
hasNext,
};
}
}
module.exports = TinyArrayPaginator;