Skip to content

feat: pagination helpers#105

Merged
lorenzopantano merged 5 commits intomainfrom
feat/pagination
Apr 15, 2026
Merged

feat: pagination helpers#105
lorenzopantano merged 5 commits intomainfrom
feat/pagination

Conversation

@lorenzopantano
Copy link
Copy Markdown
Contributor

Add pagination utilities

Introduces four standalone helper functions for working with paginated TMDB responses, exported from the main package entry point.

New helpers (utils/pagination.ts)

  • paginate(fetcher, startPage?) — async generator that lazily yields one PaginatedResponse<T> at a time. Supports early break and resuming from a checkpoint via startPage.
  • fetchAllPages(fetcher, options?) — collects all pages into a flat T[]. Accepts maxPages (defaults to TMDB's hard cap of 500) and deduplicateBy to handle cross-page duplicates caused by live popularity re-ranking on endpoints like now_playing.
  • hasNextPage(response) / hasPreviousPage(response) — boolean checks for UI navigation; accept minimal Pick types so they work with any paginated response shape.
  • getPageInfo(response) — returns structured metadata { current, total, totalResults, isFirst, isLast } for driving pagination controls.

Tests

22 unit tests covering all helpers, including edge cases: empty results, early generator exit, startPage, maxPages enforcement, the 500-page TMDB cap, deduplication key collision behaviour, and the no-deduplication default.

Docs

New Pagination guide covering all four helpers with runnable examples: lazy iteration, DB batch sync, checkpoint resuming, React pagination buttons, infinite scroll sentinel, and the cross-page duplicate caveat with mitigation.

@lorenzopantano lorenzopantano self-assigned this Apr 13, 2026
@lorenzopantano lorenzopantano added the enhancement New feature or request label Apr 13, 2026
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lorenzopant-tmdb-docs Ready Ready Preview, Comment Apr 15, 2026 7:37pm

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add pagination helpers and comprehensive documentation

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add four pagination helper functions for TMDB paginated responses
• Implement paginate() async generator for lazy page iteration
• Implement fetchAllPages() to collect all pages with deduplication support
• Implement hasNextPage() and hasPreviousPage() for UI navigation checks
• Implement getPageInfo() to extract structured pagination metadata
• Add comprehensive test suite with 22 unit tests covering edge cases
• Add detailed pagination guide with runnable examples and best practices
Diagram
flowchart LR
  A["PaginatedResponse<T>"] -->|"paginate()"| B["AsyncGenerator<br/>lazy iteration"]
  A -->|"fetchAllPages()"| C["T[] flat array<br/>with deduplication"]
  A -->|"hasNextPage()<br/>hasPreviousPage()"| D["boolean<br/>UI checks"]
  A -->|"getPageInfo()"| E["PageInfo<br/>structured metadata"]
  B --> F["Early exit<br/>checkpoint resume"]
  C --> G["maxPages cap<br/>deduplicateBy key"]
  E --> H["React pagination<br/>infinite scroll"]
Loading

Grey Divider

File Changes

1. packages/tmdb/src/utils/pagination.ts ✨ Enhancement +157/-0

Core pagination utility functions implementation

• Implement paginate() async generator for lazy sequential page fetching with optional startPage
 parameter
• Implement fetchAllPages() function to collect all pages into flat array with maxPages cap
 (default 500) and optional deduplication
• Implement hasNextPage() and hasPreviousPage() boolean helpers accepting minimal Pick types
 for UI navigation
• Implement getPageInfo() function returning structured PageInfo metadata with current,
 total, totalResults, isFirst, isLast fields
• Define FetchAllPagesOptions<T> type with maxPages and deduplicateBy configuration options
• Include comprehensive JSDoc documentation with usage examples for all functions

packages/tmdb/src/utils/pagination.ts


2. packages/tmdb/src/tests/utils/pagination.test.ts 🧪 Tests +277/-0

Comprehensive pagination utilities test suite

• Add 22 unit tests covering all pagination helpers with comprehensive edge case coverage
• Test paginate() for sequential page yielding, early break, custom startPage, and empty results
• Test fetchAllPages() for flat array collection, maxPages enforcement, TMDB 500-page cap, and
 deduplication behavior
• Test hasNextPage() and hasPreviousPage() for correct boolean returns across page boundaries
• Test getPageInfo() for accurate metadata extraction including isFirst and isLast flags
• Include helper functions makePage() and mockFetcher() for test setup

packages/tmdb/src/tests/utils/pagination.test.ts


3. packages/tmdb/src/utils/index.ts ✨ Enhancement +1/-0

Export pagination helpers from utils

• Export pagination utilities from main utils barrel file

packages/tmdb/src/utils/index.ts


View more (2)
4. apps/docs/content/docs/getting-started/pagination.mdx 📝 Documentation +245/-0

Pagination guide with examples and best practices

• Add comprehensive pagination guide documenting all four helper functions with runnable examples
• Include paginate() section with basic usage, early exit, checkpoint resuming, and database batch
 sync examples
• Include fetchAllPages() section with options table, maxPages capping, and deduplication for
 popularity-sorted endpoints
• Include hasNextPage() and hasPreviousPage() section with React pagination button example
• Include getPageInfo() section with PageInfo type documentation and infinite scroll sentinel
 example
• Add section on combining helpers for progress logging and conditional stopping
• Document TMDB server-side duplicate behavior on popularity-sorted endpoints with mitigation
 strategy

apps/docs/content/docs/getting-started/pagination.mdx


5. apps/docs/content/docs/getting-started/meta.json 📝 Documentation +1/-1

Add pagination to docs navigation

• Add pagination page to documentation navigation between features and options

apps/docs/content/docs/getting-started/meta.json


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 13, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Remediation recommended

1. Unvalidated startPage forwarded🐞
Description
paginate() passes startPage directly to the fetcher without validating it is a positive integer,
so invalid values (e.g. 0/NaN/501) will be requested and can fail when used with TMDB endpoints.
Code

packages/tmdb/src/utils/pagination.ts[R23-33]

+export async function* paginate<T>(
+	fetcher: (page: number) => Promise<PaginatedResponse<T>>,
+	startPage = 1,
+): AsyncGenerator<PaginatedResponse<T>> {
+	let page = startPage;
+	while (true) {
+		const response = await fetcher(page);
+		yield response;
+		if (response.page >= response.total_pages) break;
+		page++;
+	}
Evidence
The generator initializes page from startPage and immediately calls fetcher(page) with no
bounds/type checks. TMDB explicitly rejects out-of-range pages (1..500), and the docs encourage
deriving startPage from checkpoints (lastSyncedPage + 1), which can drift into invalid ranges
without a guard.

packages/tmdb/src/utils/pagination.ts[23-33]
packages/tmdb/src/errors/messages.ts[61-67]
apps/docs/content/docs/getting-started/pagination.mdx[67-79]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`paginate()` forwards `startPage` straight into the provided `fetcher(page)` without validating that it is a positive integer and within TMDB’s supported range. This can lead to avoidable runtime failures when callers pass checkpoint-derived or user-derived values.
## Issue Context
TMDB pages start at 1 and max out at 500 (the repo even maps TMDB error code 22 for invalid pages). The docs show resuming via `lastSyncedPage + 1`, which can exceed bounds without guardrails.
## Fix Focus Areas
- packages/tmdb/src/utils/pagination.ts[23-33]
- packages/tmdb/src/errors/messages.ts[61-67]
## Proposed fix
- Add a fast-fail validation at the start of `paginate()`:
- `Number.isInteger(startPage)`
- `startPage >= 1`
- (optionally TMDB-specific) `startPage <= TMDB_MAX_PAGES`
- Consider also guarding the loop from ever incrementing beyond `TMDB_MAX_PAGES` if you want this helper to be TMDB-safe by default (either `break` or throw a `RangeError`).
- Add a unit test covering invalid `startPage` (e.g. 0 and 501) asserting it throws a clear error.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Dedup position claim wrong🐞
Description
fetchAllPages() deduplicates via a Map which keeps the first-seen insertion position for a key,
so the returned array does not actually reflect the “most recently fetched position” claimed in the
docs when duplicates reappear later.
Code

packages/tmdb/src/utils/pagination.ts[R91-93]

+	if (deduplicateBy) {
+		return [...new Map(results.map((item) => [deduplicateBy(item), item])).values()];
+	}
Evidence
The implementation builds a Map from [key, item] pairs and returns map.values(). Updating an
existing Map key replaces the value but does not move its iteration position, which contradicts
the docs’ promise that the last occurrence preserves the most recently fetched position.

packages/tmdb/src/utils/pagination.ts[91-93]
apps/docs/content/docs/getting-started/pagination.mdx[148-149]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Docs state that when deduplicating, the "last occurrence wins" while also "preserving the most recently fetched position". The current `Map`-based implementation keeps the last value but retains the key’s original insertion position, so ordering can disagree with the documentation when duplicates appear again after other unique items.
## Issue Context
Current code:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@lorenzopantano lorenzopantano merged commit 171b007 into main Apr 15, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant