| |
|
|
| import { unwrap, type RequestOptions, type Result } from './types.js' |
|
|
| |
| |
| |
| |
| |
| const MAX_PAGINATION_PAGES = 10_000 |
|
|
| export class PaginationLimitExceededError extends Error { |
| constructor(limit: number) { |
| super( |
| `pagination exceeded the maximum of ${limit} pages; the server may be returning inconsistent pagination metadata`, |
| ) |
| this.name = 'PaginationLimitExceededError' |
| } |
| } |
|
|
| interface PageNumberEnvelope<TItem> { |
| data: TItem[] |
| meta: { page: { number: number; size: number; total: number } } |
| } |
|
|
| interface PageNumberRequest { |
| page?: { number?: number; size?: number } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function* paginatePages<TItem, TReq extends PageNumberRequest>( |
| fetchPage: ( |
| req: TReq, |
| options?: RequestOptions, |
| ) => Promise<Result<PageNumberEnvelope<TItem>>>, |
| request: TReq, |
| options?: RequestOptions, |
| ): AsyncGenerator<TItem, void, undefined> { |
| let req = request |
| let seen = 0 |
| for (let page = 0; page < MAX_PAGINATION_PAGES; page++) { |
| const res = unwrap(await fetchPage(req, options)) |
| yield* res.data |
| seen += res.data.length |
| const { number, size, total } = res.meta.page |
| if (res.data.length === 0 || res.data.length < size || seen >= total) { |
| return |
| } |
| req = { ...req, page: { ...req.page, number: number + 1 } } as TReq |
| } |
| throw new PaginationLimitExceededError(MAX_PAGINATION_PAGES) |
| } |
|
|
| interface CursorEnvelope<TItem> { |
| data: TItem[] |
| meta: { page: { next: string | null } } |
| } |
|
|
| interface CursorRequest { |
| page?: { after?: string; before?: string; size?: number } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function* paginateCursor<TItem, TReq extends CursorRequest>( |
| fetchPage: ( |
| req: TReq, |
| options?: RequestOptions, |
| ) => Promise<Result<CursorEnvelope<TItem>>>, |
| request: TReq, |
| options?: RequestOptions, |
| ): AsyncGenerator<TItem, void, undefined> { |
| let req = request |
| for (let page = 0; page < MAX_PAGINATION_PAGES; page++) { |
| const res = unwrap(await fetchPage(req, options)) |
| yield* res.data |
| const next = res.meta.page.next |
| if (!next) { |
| return |
| } |
| req = { ...req, page: { ...req.page, after: next } } as TReq |
| } |
| throw new PaginationLimitExceededError(MAX_PAGINATION_PAGES) |
| } |
|
|