UNPKG

hikma-engine

Version:

Code Knowledge Graph Indexer - A sophisticated TypeScript-based indexer that transforms Git repositories into multi-dimensional knowledge stores for AI agents

307 lines (306 loc) 10.2 kB
"use strict"; /** * @file Pagination utilities for API responses. * Provides comprehensive pagination support for large result sets. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.paginationUtils = exports.searchPagination = exports.pagination = exports.PaginationLinkGenerator = exports.SearchPagination = exports.CursorPagination = exports.PaginationUtil = void 0; /** * Default pagination configuration. */ const DEFAULT_CONFIG = { defaultLimit: 10, maxLimit: 100, defaultPage: 1, }; /** * Pagination utility class for handling different pagination strategies. */ class PaginationUtil { constructor(config = {}) { this.config = { ...DEFAULT_CONFIG, ...config }; } /** * Extracts pagination parameters from request query. */ extractParams(req) { const query = req.query; // Parse page number let page = this.config.defaultPage; if (query.page) { const parsedPage = parseInt(query.page, 10); if (!isNaN(parsedPage) && parsedPage > 0) { page = parsedPage; } } // Parse limit let limit = this.config.defaultLimit; if (query.limit) { const parsedLimit = parseInt(query.limit, 10); if (!isNaN(parsedLimit) && parsedLimit > 0) { limit = Math.min(parsedLimit, this.config.maxLimit); } } // Handle offset parameter (alternative to page) let offset = (page - 1) * limit; if (query.offset) { const parsedOffset = parseInt(query.offset, 10); if (!isNaN(parsedOffset) && parsedOffset >= 0) { offset = parsedOffset; page = Math.floor(offset / limit) + 1; } } return { page, limit, offset }; } /** * Creates pagination metadata. */ createMetadata(params, totalResults) { const { page, limit, offset } = params; const totalPages = totalResults ? Math.ceil(totalResults / limit) : undefined; const hasNextPage = totalPages ? page < totalPages : false; const hasPreviousPage = page > 1; return { currentPage: page, totalPages, pageSize: limit, totalResults, hasNextPage, hasPreviousPage, nextPage: hasNextPage ? page + 1 : undefined, previousPage: hasPreviousPage ? page - 1 : undefined, offset, }; } /** * Paginates an array of items. */ paginate(items, params, totalCount) { const { offset, limit } = params; const paginatedItems = items.slice(offset, offset + limit); const total = totalCount || items.length; const metadata = this.createMetadata(params, total); return { items: paginatedItems, metadata, hasMore: offset + limit < total, }; } /** * Creates pagination info for database queries. */ getDatabaseParams(params) { return { limit: params.limit, offset: params.offset, }; } /** * Validates pagination parameters. */ validateParams(params) { const errors = []; if (params.page !== undefined) { if (!Number.isInteger(params.page) || params.page < 1) { errors.push('Page must be a positive integer'); } } if (params.limit !== undefined) { if (!Number.isInteger(params.limit) || params.limit < 1) { errors.push('Limit must be a positive integer'); } else if (params.limit > this.config.maxLimit) { errors.push(`Limit cannot exceed ${this.config.maxLimit}`); } } if (params.offset !== undefined) { if (!Number.isInteger(params.offset) || params.offset < 0) { errors.push('Offset must be a non-negative integer'); } } return { valid: errors.length === 0, errors }; } } exports.PaginationUtil = PaginationUtil; /** * Cursor-based pagination utility for large datasets. */ class CursorPagination { /** * Extracts cursor pagination parameters from request. */ static extractParams(req, defaultLimit = 10) { const query = req.query; const cursor = query.cursor; let limit = defaultLimit; if (query.limit) { const parsedLimit = parseInt(query.limit, 10); if (!isNaN(parsedLimit) && parsedLimit > 0 && parsedLimit <= 100) { limit = parsedLimit; } } const direction = query.direction === 'backward' ? 'backward' : 'forward'; return { cursor, limit, direction }; } /** * Encodes a cursor from an object. */ static encodeCursor(data) { return Buffer.from(JSON.stringify(data)).toString('base64'); } /** * Decodes a cursor back to an object. */ static decodeCursor(cursor) { try { return JSON.parse(Buffer.from(cursor, 'base64').toString('utf8')); } catch { return null; } } /** * Creates a cursor pagination result. */ static createResult(items, limit, getCursorData) { const hasNext = items.length > limit; const hasPrevious = false; // This would be determined by your query logic // Remove extra item if we have more than requested const resultItems = hasNext ? items.slice(0, limit) : items; let nextCursor; let previousCursor; if (hasNext && resultItems.length > 0) { const lastItem = resultItems[resultItems.length - 1]; nextCursor = this.encodeCursor(getCursorData(lastItem)); } if (hasPrevious && resultItems.length > 0) { const firstItem = resultItems[0]; previousCursor = this.encodeCursor(getCursorData(firstItem)); } return { items: resultItems, nextCursor, previousCursor, hasNext, hasPrevious, }; } } exports.CursorPagination = CursorPagination; /** * Search-specific pagination utility. */ class SearchPagination extends PaginationUtil { constructor() { super({ defaultLimit: 20, maxLimit: 100, defaultPage: 1, }); } /** * Creates pagination for search results with relevance sorting. */ paginateSearchResults(results, params, totalCount, searchMetadata) { const paginationResult = this.paginate(results, params, totalCount); return { ...paginationResult, searchMetadata, }; } /** * Calculates relevance-based pagination offsets. */ getRelevanceParams(params, minRelevanceScore = 0.1) { return { ...this.getDatabaseParams(params), minScore: minRelevanceScore, }; } } exports.SearchPagination = SearchPagination; /** * Pagination link generator for HAL-style pagination. */ class PaginationLinkGenerator { constructor(baseUrl) { this.baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; } /** * Generates pagination links for HAL responses. */ generateLinks(req, metadata, preserveQuery = true) { const links = {}; const baseParams = new URLSearchParams(); // Preserve existing query parameters if (preserveQuery) { Object.entries(req.query).forEach(([key, value]) => { if (key !== 'page' && key !== 'offset' && value !== undefined) { baseParams.set(key, String(value)); } }); } const path = req.path; // Self link baseParams.set('page', metadata.currentPage.toString()); links.self = { href: `${this.baseUrl}${path}?${baseParams.toString()}` }; // First page link baseParams.set('page', '1'); links.first = { href: `${this.baseUrl}${path}?${baseParams.toString()}` }; // Last page link (if total pages is known) if (metadata.totalPages) { baseParams.set('page', metadata.totalPages.toString()); links.last = { href: `${this.baseUrl}${path}?${baseParams.toString()}` }; } // Previous page link if (metadata.hasPreviousPage && metadata.previousPage) { baseParams.set('page', metadata.previousPage.toString()); links.prev = { href: `${this.baseUrl}${path}?${baseParams.toString()}` }; } // Next page link if (metadata.hasNextPage && metadata.nextPage) { baseParams.set('page', metadata.nextPage.toString()); links.next = { href: `${this.baseUrl}${path}?${baseParams.toString()}` }; } return links; } } exports.PaginationLinkGenerator = PaginationLinkGenerator; /** * Default pagination utility instance. */ exports.pagination = new PaginationUtil(); /** * Default search pagination utility instance. */ exports.searchPagination = new SearchPagination(); /** * Utility functions for common pagination operations. */ exports.paginationUtils = { /** * Quick pagination parameter extraction. */ getParams: (req) => exports.pagination.extractParams(req), /** * Quick pagination for arrays. */ paginate: (items, req, total) => { const params = exports.pagination.extractParams(req); return exports.pagination.paginate(items, params, total); }, /** * Quick search pagination. */ paginateSearch: (items, req, total) => { const params = exports.searchPagination.extractParams(req); return exports.searchPagination.paginate(items, params, total); }, /** * Validates pagination query parameters. */ validate: (req) => { const params = exports.pagination.extractParams(req); return exports.pagination.validateParams(params); }, };