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
JavaScript
/**
* @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);
},
};
;