Skip to main content

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 }
);

PaginationOptions type

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

Pagination metadata

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);
1

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);
2

Calculate offset

Convert page number to array offset (0-indexed):
const offset = (page - 1) * limit;
3

Slice results

Extract the requested page from all sorted results:
const results = combined.slice(offset, offset + limit);
4

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
// }
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.

Building pagination UI

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 => []

Performance considerations

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

  1. Search runs across all verses (6,236 verses)
  2. Results are scored and sorted
  3. Then pagination slices the sorted results
  4. 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,
    },
  };
}

Build docs developers (and LLMs) love