Documentation Index
Fetch the complete documentation index at: https://mintlify.com/adelpro/quran-search-engine/llms.txt
Use this file to discover all available pages before exploring further.
The search() function includes built-in pagination support. Results are sliced server-side (or client-side) and returned with metadata about total results, pages, and current position.
Overview
Pagination is controlled by the PaginationOptions parameter passed to search():
import { search, type PaginationOptions } from 'quran-search-engine';
const response = search(
'الله الرحمن',
quranData,
morphologyMap,
wordMap,
{ lemma: true, root: true },
{ page: 1, limit: 10 }
);
From src/types/index.ts:62-65:
export type PaginationOptions = {
page?: number;
limit?: number;
};
Fields:
page?: number - Page number (1-indexed, defaults to 1)
limit?: number - Results per page (defaults to 20)
Both fields are optional. If omitted, defaults are applied: { page: 1, limit: 20 }
Default behavior
From src/core/search.ts:309:
export const search = <TVerse extends VerseInput>(
query: string,
quranData: TVerse[],
morphologyMap: Map<number, MorphologyAya>,
wordMap: WordMap,
options: AdvancedSearchOptions = { lemma: true, root: true },
pagination: PaginationOptions = { page: 1, limit: 20 },
): SearchResponse<TVerse> => {
When no pagination is provided:
- Returns first 20 results
- Current page is 1
- Total pages calculated based on total results
The SearchResponse includes detailed pagination metadata:
const response = search(
'الله الرحمن',
quranData,
morphologyMap,
wordMap,
{ lemma: true, root: true },
{ page: 1, limit: 10 },
);
// Example output:
// response.pagination => {
// totalResults: 42,
// totalPages: 5,
// currentPage: 1,
// limit: 10
// }
From src/types/index.ts:67-76:
export type SearchResponse<TVerse extends VerseInput = QuranText> = {
results: ScoredVerse<TVerse>[];
counts: SearchCounts;
pagination: {
totalResults: number;
totalPages: number;
currentPage: number;
limit: number;
};
};
Implementation details
From src/core/search.ts:366-374:
// 6. Pagination & Metadata
const page = Math.max(1, pagination.page || 1);
const limit = Math.max(1, pagination.limit || 20);
const offset = (page - 1) * limit;
const results = combined.slice(offset, offset + limit);
const totalResults = combined.length;
const totalPages = Math.ceil(totalResults / limit);
Normalize page and limit
Both values are clamped to minimum of 1:const page = Math.max(1, pagination.page || 1);
const limit = Math.max(1, pagination.limit || 20);
Calculate offset
Convert page number to array offset (0-indexed):const offset = (page - 1) * limit;
Slice results
Extract the requested page from all sorted results:const results = combined.slice(offset, offset + limit);
Calculate metadata
Determine total pages and include in response:const totalResults = combined.length;
const totalPages = Math.ceil(totalResults / limit);
Usage examples
First page (default)
const response = search(
'الله',
quranData,
morphologyMap,
wordMap
);
console.log(response.pagination);
// {
// totalResults: 2800,
// totalPages: 140,
// currentPage: 1,
// limit: 20
// }
Custom page size
const response = search(
'الله',
quranData,
morphologyMap,
wordMap,
{ lemma: true, root: true },
{ page: 1, limit: 50 }
);
console.log(response.pagination);
// {
// totalResults: 2800,
// totalPages: 56,
// currentPage: 1,
// limit: 50
// }
Navigate to specific page
const response = search(
'الله',
quranData,
morphologyMap,
wordMap,
{ lemma: true, root: true },
{ page: 5, limit: 20 }
);
console.log(response.pagination);
// {
// totalResults: 2800,
// totalPages: 140,
// currentPage: 5,
// limit: 20
// }
// Results are from index 80 to 99 (zero-indexed)
Get all results
const response = search(
'الله',
quranData,
morphologyMap,
wordMap,
{ lemma: true, root: true },
{ page: 1, limit: 10000 }
);
// All results on a single page
Requesting very large page sizes (e.g., limit: 10000) may impact performance. Consider using reasonable page sizes (10-100) for better UX and performance.
React example
import { search, type SearchResponse } from 'quran-search-engine';
import { useState } from 'react';
export function SearchResults() {
const [currentPage, setCurrentPage] = useState(1);
const [response, setResponse] = useState<SearchResponse | null>(null);
const performSearch = (query: string, page: number) => {
const result = search(
query,
quranData,
morphologyMap,
wordMap,
{ lemma: true, root: true },
{ page, limit: 20 }
);
setResponse(result);
};
if (!response) return null;
const { pagination } = response;
return (
<div>
{/* Results */}
<div>
{response.results.map((verse) => (
<div key={verse.gid}>{verse.uthmani}</div>
))}
</div>
{/* Pagination controls */}
<div className="pagination">
<button
disabled={pagination.currentPage === 1}
onClick={() => performSearch(query, currentPage - 1)}
>
Previous
</button>
<span>
Page {pagination.currentPage} of {pagination.totalPages}
({pagination.totalResults} results)
</span>
<button
disabled={pagination.currentPage === pagination.totalPages}
onClick={() => performSearch(query, currentPage + 1)}
>
Next
</button>
</div>
</div>
);
}
Page number buttons
function PageButtons({ pagination, onPageChange }: {
pagination: SearchResponse['pagination'];
onPageChange: (page: number) => void;
}) {
const { currentPage, totalPages } = pagination;
const maxButtons = 5;
// Calculate range of page buttons to show
let startPage = Math.max(1, currentPage - Math.floor(maxButtons / 2));
let endPage = Math.min(totalPages, startPage + maxButtons - 1);
if (endPage - startPage + 1 < maxButtons) {
startPage = Math.max(1, endPage - maxButtons + 1);
}
const pages = Array.from(
{ length: endPage - startPage + 1 },
(_, i) => startPage + i
);
return (
<div className="page-buttons">
{startPage > 1 && (
<>
<button onClick={() => onPageChange(1)}>1</button>
{startPage > 2 && <span>...</span>}
</>
)}
{pages.map((page) => (
<button
key={page}
className={page === currentPage ? 'active' : ''}
onClick={() => onPageChange(page)}
>
{page}
</button>
))}
{endPage < totalPages && (
<>
{endPage < totalPages - 1 && <span>...</span>}
<button onClick={() => onPageChange(totalPages)}>{totalPages}</button>
</>
)}
</div>
);
}
Edge cases
Invalid page numbers
// Page 0 or negative → clamped to 1
const response = search(
'الله',
quranData,
morphologyMap,
wordMap,
{ lemma: true, root: true },
{ page: 0, limit: 20 }
);
// response.pagination.currentPage => 1
// Page beyond total pages → returns empty results
const response2 = search(
'الله',
quranData,
morphologyMap,
wordMap,
{ lemma: true, root: true },
{ page: 9999, limit: 20 }
);
// response2.results => []
// response2.pagination.currentPage => 9999
Check if currentPage > totalPages to detect out-of-range requests and redirect to the last valid page.
Zero or negative limit
// Limit 0 or negative → clamped to 1
const response = search(
'الله',
quranData,
morphologyMap,
wordMap,
{ lemma: true, root: true },
{ page: 1, limit: 0 }
);
// response.pagination.limit => 1
// response.results.length => 1
No results
const response = search(
'xyz123',
quranData,
morphologyMap,
wordMap
);
// response.pagination => {
// totalResults: 0,
// totalPages: 0,
// currentPage: 1,
// limit: 20
// }
// response.results => []
Pagination happens after search and scoring. All results are computed first, then sliced. For very large result sets, consider implementing server-side pagination if performance becomes an issue.
Search flow
- Search runs across all verses (6,236 verses)
- Results are scored and sorted
- Then pagination slices the sorted results
- Only the requested page is returned
This means:
- ✓ Consistent scoring and ordering across pages
- ✓ Accurate total counts and page numbers
- ✗ All results must be computed even if only viewing page 1
Optimization tips
// Cache the full result set and paginate client-side
let cachedResults: ScoredVerse[] = [];
function searchWithCache(query: string, page: number) {
if (cachedResults.length === 0) {
// First search: get ALL results
const fullResponse = search(
query,
quranData,
morphologyMap,
wordMap,
{ lemma: true, root: true },
{ page: 1, limit: 10000 }
);
cachedResults = fullResponse.results;
}
// Paginate cached results
const limit = 20;
const offset = (page - 1) * limit;
const results = cachedResults.slice(offset, offset + limit);
return {
results,
pagination: {
totalResults: cachedResults.length,
totalPages: Math.ceil(cachedResults.length / limit),
currentPage: page,
limit,
},
};
}