github-pr-automation
Version:
MCP server and CLI for automated GitHub PR management, review resolution, and workflow optimization
131 lines • 4.65 kB
JavaScript
/**
* Encode pagination cursor from offset and page size
* @param offset - Starting offset for pagination
* @param pageSize - Number of items per page
* @returns Base64-encoded cursor string
*/
export function encodeCursor(offset, pageSize) {
const data = { offset, pageSize };
return Buffer.from(JSON.stringify(data)).toString("base64");
}
/**
* Decode pagination cursor to offset and page size
* @param cursor - Base64-encoded cursor string
* @returns Decoded cursor data with offset and pageSize
* @throws Error if cursor format is invalid
*/
export function decodeCursor(cursor) {
try {
const json = Buffer.from(cursor, "base64").toString("utf-8");
const data = JSON.parse(json);
// Validate cursor data - ensure integers
if (!Number.isInteger(data.offset) || data.offset < 0) {
throw new Error("Invalid cursor: offset must be non-negative integer");
}
if (!Number.isInteger(data.pageSize) ||
data.pageSize < 1 ||
data.pageSize > 100) {
throw new Error("Invalid cursor: pageSize must be positive integer (1-100)");
}
return data;
}
catch (error) {
// MCP error code -32602 for invalid params
const err = new Error(`Invalid cursor: ${error instanceof Error ? error.message : "malformed cursor"}`);
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
err.code = -32602;
throw err;
}
}
/**
* Convert MCP cursor to GitHub API pagination parameters
* @param cursor - Optional MCP cursor string
* @param defaultPageSize - Default page size to use if no cursor provided
* @returns GitHub API pagination parameters (page, per_page)
*/
export function cursorToGitHubPagination(cursor, defaultPageSize) {
// Validate page size
if (!Number.isFinite(defaultPageSize) || defaultPageSize < 1) {
throw new RangeError("defaultPageSize must be a positive finite integer");
}
// Decode cursor or start from beginning
let offset;
let pageSize;
if (cursor) {
const decoded = decodeCursor(cursor);
offset = decoded.offset;
// Clamp page size to server-controlled default to prevent abuse
pageSize = Math.min(decoded.pageSize, defaultPageSize);
}
else {
offset = 0;
pageSize = defaultPageSize;
}
// Convert offset to GitHub page number (1-based)
const page = Math.floor(offset / pageSize) + 1;
return {
page,
per_page: pageSize,
};
}
/**
* Create next cursor for pagination if more results exist
* @param currentCursor - Current cursor string
* @param pageSize - Size of the current page
* @param hasMore - Whether more results exist
* @returns Next cursor string or undefined if no more results
*/
export function createNextCursor(currentCursor, pageSize, hasMore) {
if (!hasMore) {
return undefined;
}
// Calculate next offset
let nextOffset;
if (currentCursor) {
const decoded = decodeCursor(currentCursor);
nextOffset = decoded.offset + pageSize;
}
else {
nextOffset = pageSize;
}
return encodeCursor(nextOffset, pageSize);
}
/**
* Paginate an array of items using MCP cursor-based pagination
* @param items - Array of items to paginate
* @param cursor - Optional cursor string for pagination
* @param defaultPageSize - Number of items per page
* @returns Paginated result with items and optional next cursor
*/
export function paginateResults(items, cursor, defaultPageSize) {
// Validate page size
if (!Number.isFinite(defaultPageSize) || defaultPageSize < 1) {
throw new RangeError("pageSize must be a positive finite integer");
}
// Decode cursor or start from beginning
let offset;
let pageSize;
if (cursor) {
const decoded = decodeCursor(cursor);
offset = decoded.offset;
// Clamp page size to server-controlled default to prevent abuse
pageSize = Math.min(decoded.pageSize, defaultPageSize);
}
else {
offset = 0;
pageSize = defaultPageSize;
}
// Calculate slice indices
const startIndex = offset;
const endIndex = offset + pageSize;
// Get page items
const pageItems = items.slice(startIndex, endIndex);
// Generate nextCursor only if more results exist
const hasMore = endIndex < items.length;
const nextCursor = hasMore ? encodeCursor(endIndex, pageSize) : undefined;
return {
items: pageItems,
nextCursor,
};
}
//# sourceMappingURL=pagination.js.map