@aashari/mcp-server-atlassian-confluence
Version:
Node.js/TypeScript MCP server for Atlassian Confluence. Provides tools enabling AI systems (LLMs) to list/get spaces & pages (content formatted as Markdown) and search via CQL. Connects AI seamlessly to Confluence knowledge bases using the standard MCP in
169 lines (168 loc) • 6.53 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PaginationType = void 0;
exports.adaptResponseForPagination = adaptResponseForPagination;
exports.extractPaginationInfo = extractPaginationInfo;
const logger_util_js_1 = require("./logger.util.js");
const paginationLogger = logger_util_js_1.Logger.forContext('utils/pagination.util.ts');
/**
* Available pagination types supported by the application
*/
var PaginationType;
(function (PaginationType) {
/**
* Offset-based pagination (startAt, maxResults, total)
* Used by Jira APIs
*/
PaginationType["OFFSET"] = "offset";
/**
* Cursor-based pagination (cursor in URL)
* Used by Confluence APIs
*/
PaginationType["CURSOR"] = "cursor";
/**
* Page-based pagination (page parameter in URL)
* Used by Bitbucket APIs
*/
PaginationType["PAGE"] = "page";
})(PaginationType || (exports.PaginationType = PaginationType = {}));
/**
* Adapter function to convert Zod-validated API responses to pagination data
* @param data Any API response with potential pagination properties
* @returns A PaginationData compatible object
*/
function adaptResponseForPagination(data) {
// Create a basic pagination data structure
const paginationData = {};
// Handle _links for cursor-based pagination
if (data && typeof data === 'object' && '_links' in data) {
const typedData = data;
paginationData._links = { next: typedData._links?.next };
}
// For offset-based pagination
if (data && typeof data === 'object' && 'startAt' in data) {
paginationData.startAt = data.startAt;
}
if (data && typeof data === 'object' && 'maxResults' in data) {
paginationData.maxResults = data.maxResults;
}
if (data && typeof data === 'object' && 'total' in data) {
paginationData.total = data.total;
}
if (data && typeof data === 'object' && 'isLast' in data) {
paginationData.isLast = data.isLast;
}
// For page-based pagination
if (data && typeof data === 'object' && 'page' in data) {
paginationData.page = data.page;
}
if (data && typeof data === 'object' && 'size' in data) {
paginationData.size = data.size;
}
if (data && typeof data === 'object' && 'totalElements' in data) {
paginationData.totalElements = data.totalElements;
}
if (data && typeof data === 'object' && 'totalPages' in data) {
paginationData.totalPages = data.totalPages;
}
if (data && typeof data === 'object' && 'last' in data) {
paginationData.last = data.last;
}
return paginationData;
}
/**
* Extract pagination information from an API response and convert it to a standardized format
* @param data The API response data containing pagination information
* @param type The type of pagination used in the response
* @param entityType Optional entity type for logging
* @returns Standardized pagination object or undefined if no pagination info is available
*/
function extractPaginationInfo(data, type, entityType) {
// First adapt the response to ensure it has a compatible structure
const adaptedData = adaptResponseForPagination(data);
// Apply normal extraction logic to the adapted data
const logger = paginationLogger.forMethod('extractPaginationInfo');
// Extract the results array for counting items
let resultsArray = [];
if (data &&
typeof data === 'object' &&
'results' in data &&
Array.isArray(data.results)) {
resultsArray = data.results;
}
else if (data && Array.isArray(data)) {
resultsArray = data;
}
// Count of items in the current response
const count = resultsArray.length;
// Default to undefined - will be populated based on pagination type
let hasMore = false;
let nextCursor = undefined;
let total = undefined;
try {
switch (type) {
case PaginationType.CURSOR:
// Cursor-based pagination (Confluence API v2 style)
if (adaptedData._links?.next) {
hasMore = true;
// Extract cursor from next link if it exists
const cursorMatch = adaptedData._links.next.match(/cursor=([^&]+)/);
if (cursorMatch && cursorMatch[1]) {
nextCursor = cursorMatch[1];
}
}
else {
hasMore = false;
}
break;
case PaginationType.OFFSET: {
// Offset-based pagination (Jira API style)
const offsetData = adaptedData;
if (offsetData.startAt !== undefined &&
offsetData.maxResults !== undefined &&
offsetData.total !== undefined) {
const endAt = offsetData.startAt + count;
hasMore = endAt < offsetData.total;
nextCursor = hasMore ? endAt.toString() : undefined;
total = offsetData.total;
}
else if (offsetData.isLast !== undefined) {
hasMore = !offsetData.isLast;
}
break;
}
case PaginationType.PAGE: {
// Page-based pagination
const pageData = adaptedData;
if (pageData.last !== undefined) {
hasMore = !pageData.last;
if (hasMore && pageData.page !== undefined) {
nextCursor = (pageData.page + 1).toString();
}
}
if (pageData.totalElements !== undefined) {
total = pageData.totalElements;
}
break;
}
default:
logger.warn(`Unknown pagination type: ${type} for ${entityType || 'entity'}`);
return undefined;
}
// Return the standardized pagination object
return {
count,
hasMore,
...(nextCursor && { nextCursor }),
...(total !== undefined && { total }),
};
}
catch (error) {
logger.warn(`Error extracting pagination info for ${entityType || 'entity'}: ${String(error)}`);
// If error occurred, return basic info without next cursor
return {
count,
hasMore: false,
};
}
}