UNPKG

github-pr-automation

Version:

MCP server and CLI for automated GitHub PR management, review resolution, and workflow optimization

131 lines 4.65 kB
/** * 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