@snehal96/unimail
Version:
Unified email fetching & document extraction layer for modern web apps
261 lines (260 loc) • 8.25 kB
JavaScript
/**
* Pagination helper utility class that provides convenient methods for managing
* email pagination state and fetching emails across multiple pages.
*/
export class PaginationHelper {
constructor(adapter, initialOptions = {}) {
this.adapter = adapter;
this.state = {
currentPageToken: undefined,
previousPageTokens: [],
currentPage: 1,
pageSize: initialOptions.pageSize || 20,
totalFetched: 0,
query: initialOptions.query,
options: initialOptions
};
}
/**
* Fetch the current page of emails
*/
async fetchCurrentPage() {
const startTime = new Date();
const response = await this.adapter.fetchEmails({
...this.state.options,
pageToken: this.state.currentPageToken,
pageSize: this.state.pageSize
});
const pagination = this.createPaginationMetadata(response);
return {
data: response.emails,
pagination,
query: this.state.query,
totalFetched: response.emails.length,
fetchTime: startTime
};
}
/**
* Fetch the next page of emails
*/
async fetchNextPage() {
const response = await this.fetchCurrentPage();
if (!response.pagination.hasNextPage) {
return null;
}
return this.goToNextPage();
}
/**
* Fetch the previous page of emails
*/
async fetchPreviousPage() {
if (!this.state.previousPageTokens.length) {
return null;
}
return this.goToPreviousPage();
}
/**
* Navigate to the next page
*/
async goToNextPage() {
// Save current page token to history
if (this.state.currentPageToken) {
this.state.previousPageTokens.push(this.state.currentPageToken);
}
// Get next page info
const currentResponse = await this.adapter.fetchEmails({
...this.state.options,
pageToken: this.state.currentPageToken,
pageSize: this.state.pageSize
});
if (!currentResponse.nextPageToken) {
throw new Error('No next page available');
}
// Update state
this.state.currentPageToken = currentResponse.nextPageToken;
this.state.currentPage++;
this.state.totalFetched += currentResponse.emails.length;
return this.fetchCurrentPage();
}
/**
* Navigate to the previous page
*/
async goToPreviousPage() {
if (this.state.previousPageTokens.length === 0) {
throw new Error('No previous page available');
}
// Get previous page token
const previousToken = this.state.previousPageTokens.pop();
this.state.currentPageToken = previousToken;
this.state.currentPage--;
return this.fetchCurrentPage();
}
/**
* Reset pagination to the first page
*/
async goToFirstPage() {
this.state.currentPageToken = undefined;
this.state.previousPageTokens = [];
this.state.currentPage = 1;
this.state.totalFetched = 0;
return this.fetchCurrentPage();
}
/**
* Fetch all pages up to a specified limit
*/
async fetchAllPages(maxEmails) {
const allEmails = [];
const startTime = new Date();
let totalPages = 0;
// Reset to first page
await this.goToFirstPage();
do {
const response = await this.fetchCurrentPage();
allEmails.push(...response.data);
totalPages++;
// Check if we've reached the limit
if (maxEmails && allEmails.length >= maxEmails) {
break;
}
// Try to go to next page
if (response.pagination.hasNextPage) {
await this.goToNextPage();
}
else {
break;
}
} while (true);
// Trim to exact limit if specified
const finalEmails = maxEmails ? allEmails.slice(0, maxEmails) : allEmails;
return {
data: finalEmails,
pagination: {
currentPage: totalPages,
pageSize: this.state.pageSize,
totalCount: undefined,
hasNextPage: false,
hasPreviousPage: false,
isFirstPage: true,
isLastPage: true,
estimatedTotalPages: totalPages
},
query: this.state.query,
totalFetched: finalEmails.length,
fetchTime: startTime
};
}
/**
* Create an async generator for iterating through all pages
*/
async *iterateAllPages() {
// Reset to first page
await this.goToFirstPage();
do {
const response = await this.fetchCurrentPage();
yield response;
if (response.pagination.hasNextPage) {
await this.goToNextPage();
}
else {
break;
}
} while (true);
}
/**
* Update pagination options
*/
updateOptions(newOptions) {
this.state.options = { ...this.state.options, ...newOptions };
// Update relevant state
if (newOptions.pageSize) {
this.state.pageSize = newOptions.pageSize;
}
if (newOptions.query !== undefined) {
this.state.query = newOptions.query;
}
}
/**
* Get current pagination state
*/
getCurrentState() {
return { ...this.state };
}
/**
* Create pagination metadata from adapter response
*/
createPaginationMetadata(response) {
const hasNextPage = !!response.nextPageToken;
const hasPreviousPage = this.state.previousPageTokens.length > 0;
const isFirstPage = this.state.currentPage === 1;
const isLastPage = !hasNextPage;
let estimatedTotalPages;
if (response.totalCount && this.state.pageSize) {
estimatedTotalPages = Math.ceil(response.totalCount / this.state.pageSize);
}
return {
currentPage: this.state.currentPage,
pageSize: this.state.pageSize,
totalCount: response.totalCount,
estimatedTotalPages,
nextPageToken: response.nextPageToken,
previousPageToken: this.state.previousPageTokens[this.state.previousPageTokens.length - 1],
hasNextPage,
hasPreviousPage,
isFirstPage,
isLastPage
};
}
}
/**
* Static utility functions for pagination
*/
export class PaginationUtils {
/**
* Calculate pagination metadata from basic parameters
*/
static calculatePaginationMetadata(currentPage, pageSize, totalCount, hasNextPage, hasPreviousPage) {
const isFirstPage = currentPage === 1;
const estimatedTotalPages = totalCount ? Math.ceil(totalCount / pageSize) : undefined;
const isLastPage = estimatedTotalPages ? currentPage >= estimatedTotalPages : !hasNextPage;
return {
currentPage,
pageSize,
totalCount,
estimatedTotalPages,
hasNextPage: hasNextPage ?? false,
hasPreviousPage: hasPreviousPage ?? false,
isFirstPage,
isLastPage
};
}
/**
* Create a simple paginated response wrapper
*/
static createPaginatedResponse(data, pagination, query) {
return {
data,
pagination,
query,
totalFetched: data.length,
fetchTime: new Date()
};
}
/**
* Extract page number from page token (if it's numeric)
*/
static extractPageFromToken(token) {
try {
const parsed = parseInt(token, 10);
return isNaN(parsed) ? null : parsed;
}
catch {
return null;
}
}
}
/**
* Convenient factory function to create a pagination helper
*/
export function createPaginationHelper(adapter, options = {}) {
return new PaginationHelper(adapter, options);
}