Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/mobile/v1/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions apps/mobile/v1/src/services/api/folders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,30 @@
* Handles all folder-related API operations
*/

import { createHttpClient, AuthTokenGetter } from './client';
import { AuthTokenGetter, createHttpClient } from './client';
import { Folder, FoldersResponse } from './types';
import { fetchAllPages, createPaginationParams } from './utils/pagination';
import { createPaginationParams, fetchAllPagesParallel } from './utils/pagination';
import { handleApiError } from './utils/errors';

export function createFoldersApi(getToken: AuthTokenGetter) {
const { makeRequest } = createHttpClient(getToken);

return {
/**
* Get all folders with pagination
* Get all folders with parallel pagination (optimized for performance)
* Fetches pages in parallel instead of sequentially, eliminating delays
*/
async getFolders(): Promise<Folder[]> {
try {
const allFolders = await fetchAllPages<FoldersResponse, Folder>(
return await fetchAllPagesParallel<FoldersResponse, Folder>(
async (page) => {
const params = createPaginationParams(page);
return await makeRequest<FoldersResponse>(`/folders?${params.toString()}`);
return await makeRequest<FoldersResponse>(
`/folders?${params.toString()}`
);
},
(response) => response.folders || []
);

return allFolders;
} catch (error) {
return handleApiError(error, 'getFolders');
}
Expand Down
52 changes: 52 additions & 0 deletions apps/mobile/v1/src/services/api/utils/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,58 @@ function determineHasMorePages<T>(
return pageItemsCount >= DEFAULT_PAGE_LIMIT;
}

/**
* Fetches all pages in parallel (after fetching page 1 to determine total)
* Much faster than sequential pagination - eliminates artificial delays and waits
* @param fetchPage - Function that fetches a single page of data
* @param extractData - Function that extracts the data array from the response
* @returns Array of all items across all pages
*/
export async function fetchAllPagesParallel<TResponse, TItem>(
fetchPage: (page: number) => Promise<TResponse>,
extractData: (response: TResponse) => TItem[]
): Promise<TItem[]> {
// Step 1: Fetch first page to get metadata
const firstPageResponse = await fetchPage(1);
const firstPageItems = extractData(firstPageResponse);

// Step 2: Determine how many total pages exist
const resp = firstPageResponse as PaginationResponse<unknown>;
let totalPages = 1;

if (resp.pagination) {
totalPages = resp.pagination.totalPages ?? resp.pagination.pages ?? 1;
} else if (resp.total !== undefined && resp.limit !== undefined) {
totalPages = Math.ceil(resp.total / resp.limit);
} else if (firstPageItems.length >= DEFAULT_PAGE_LIMIT) {
// Got a full page, assume there might be more
// But we can't know for sure, so we'll just return what we got
return firstPageItems;
}

// Step 3: If only one page, return immediately
if (totalPages <= 1) {
return firstPageItems;
}

// Step 4: Create promises for ALL remaining pages (in parallel!)
const remainingPagePromises: Promise<TItem[]>[] = [];
for (let page = 2; page <= Math.min(totalPages, MAX_PAGES); page++) {
remainingPagePromises.push(
fetchPage(page).then(response => extractData(response))
);
}

// Step 5: Execute ALL requests in parallel
const remainingPages = await Promise.all(remainingPagePromises);

// Step 6: Flatten and combine with first page
return [
...firstPageItems,
...remainingPages.flat()
];
}

/**
* Creates URL search params with pagination
*/
Expand Down