Core Type Reference

Full public TypeScript types in @evensurface/paginate, with field-by-field explanations.

This page shows the public types exported by @evensurface/paginate. You can import them from the package root:

import type {
  PaginationAdapter,
  PaginationOptions,
  PaginatedResult,
  PaginationMetadata,
} from "@evensurface/paginate"

Each type below has its actual TypeScript shape and an explanation of what its fields mean. The names TQuery, TCountQuery, and TItem are generic type parameters: placeholders you replace with your own query/filter and row types. They are not special classes provided by Paginate.

Common inputs

PaginationInput

type PaginationInput = string | number

This is the type accepted by optional page, offset, and limit inputs. Strings are useful because URL query parameters normally arrive as text.

Examples: 2, "2", and " 2 " can be parsed as valid integers. An invalid value such as "abc" uses the relevant fallback. This type does not include undefined, but the function properties accepting it are optional (page?: PaginationInput), so omitting them is allowed.

See Input Helpers for the exact parsing rules.

PageSizeOptions

interface PageSizeOptions {
  defaultLimit?: number
  minLimit?: number
  maxLimit?: number
}
FieldTypeDefaultExplanation
defaultLimitnumber20Used if the caller doesn't supply a valid limit.
minLimitnumber1Smallest valid limit; smaller valid values are raised to this.
maxLimitnumber100Largest valid limit; larger valid values are reduced to this.

All values, when provided, must be positive safe integers, and minLimit <= defaultLimit <= maxLimit. For example:

const pageSizeOptions: PageSizeOptions = {
  defaultLimit: 25,
  minLimit: 5,
  maxLimit: 200,
}

Adapter types

PaginationAdapter

interface PaginationAdapter<TQuery, TCountQuery, TItem> {
  fetch(query: TQuery, options: { limit: number; offset: number }): Promise<TItem[]>
  count(query: TCountQuery): Promise<number>
}

This is the adapter for numbered pages and native offsets.

PartMeaning
TQueryType of the filters/options passed to fetch().
TCountQueryType of the filters passed to count().
TItemType of each returned row.
fetch(query, { limit, offset })Gets one slice of matching items and returns an array inside a Promise.
count(query)Counts all matching rows and returns a non-negative safe integer inside a Promise.

Example: PaginationAdapter<{ active: boolean }, { active: boolean }, User> represents an adapter that filters users and returns User[]. paginate() calls count() first and then fetch() if rows exist. See Build Your Own Adapter for a full implementation.

CursorPaginationAdapter

interface CursorPaginationAdapter<TQuery, TItem> {
  fetch(
    query: TQuery,
    options: { position: CursorPosition | undefined; limit: number },
  ): Promise<TItem[]>

  getPosition(item: TItem): CursorPosition
}

This adapter is for forward-only cursor pagination. It does not have a count() method.

PartMeaning
TQueryFilter/options type used on each request.
TItemType of each returned item.
positionA decoded CursorPosition, or undefined on the first request.
limitMaximum number of rows the adapter must fetch. The core passes requested limit + 1 for lookahead.
fetch()Fetches rows after the supplied position.
getPosition()Reads the sort keys from a returned row so Paginate can generate the next cursor.

The adapter must enforce a stable order that matches getPosition(). See the cursor guide.

Pagination function option types

PaginationOptions

interface PaginationOptions<TQuery, TCountQuery, TItem> {
  adapter: PaginationAdapter<TQuery, TCountQuery, TItem>
  query: TQuery
  countQuery: TCountQuery
  page?: PaginationInput
  limit?: PaginationInput
  pageSizeOptions?: PageSizeOptions
}

Passed to paginate().

FieldRequired?Meaning
adapterYesThe count-and-fetch adapter.
queryYesInput to adapter.fetch()—your filters/sort options.
countQueryYesInput to adapter.count()—matching filters.
pageNoOne-based page number; defaults to 1.
limitNoMaximum rows per page; defaults to 20 and is bounded by page-size settings.
pageSizeOptionsNoOptional limit policy.

It returns a PaginatedResult<TItem>.

OffsetPaginationOptions

interface OffsetPaginationOptions<TQuery, TCountQuery, TItem> {
  adapter: PaginationAdapter<TQuery, TCountQuery, TItem>
  query: TQuery
  countQuery: TCountQuery
  offset?: PaginationInput
  limit?: PaginationInput
  pageSizeOptions?: PageSizeOptions
}

Passed to paginateOffset(). It has the same adapter, query, count query, and limit options as PaginationOptions. The only main difference is offset instead of page.

FieldRequired?Meaning
adapterYesThe count-and-fetch adapter.
queryYesFilters and options sent to fetch().
countQueryYesFilters sent to count().
offsetNoNumber of matching rows to skip; defaults to 0 and is not clamped to the total.
limitNoMaximum rows to fetch, with the same default and boundaries.
pageSizeOptionsNoOptional limit policy.

It returns an OffsetPaginatedResult<TItem>.

CursorPaginationOptions

interface CursorPaginationOptions<TQuery, TItem> {
  adapter: CursorPaginationAdapter<TQuery, TItem>
  query: TQuery
  cursor?: string
  limit?: PaginationInput
  pageSizeOptions?: PageSizeOptions
}

Passed to paginateCursor().

FieldRequired?Meaning
adapterYesYour cursor fetch-and-position adapter.
queryYesFilters passed to fetch() on every request.
cursorNoThe previous response's nextCursor string. Omit for the first page.
limitNoMaximum rows returned; the adapter fetches one extra to check for another batch.
pageSizeOptionsNoOptional limit policy.

An invalid supplied cursor throws an error; it doesn't use a fallback. There is no count query and no numeric offset in this type. It returns a CursorPaginatedResult<TItem>.

Result types

PaginatedResult

interface PaginatedResult<TItem> {
  items: TItem[]
  pagination: PaginationMetadata
}
FieldTypeMeaning
itemsTItem[]Selected rows for the requested numbered page.
paginationPaginationMetadataPage, total, page count, row positions, and navigation flags.

If TItem is User, items is User[].

OffsetPaginatedResult

interface OffsetPaginatedResult<TItem> {
  items: TItem[]
  pagination: OffsetPaginationMetadata
}
FieldTypeMeaning
itemsTItem[]Rows returned starting after the requested skip amount.
paginationOffsetPaginationMetadataOffset, limit, total, and navigation flags.

CursorPaginatedResult

interface CursorPaginatedResult<TItem> {
  items: TItem[]
  pagination: CursorPaginationMetadata
}
FieldTypeMeaning
itemsTItem[]The next batch, with the lookahead row removed.
paginationCursorPaginationMetadataNormalized limit, next cursor, and whether more rows exist.

Metadata types

PaginationMetadata

interface PaginationMetadata {
  page: number
  limit: number
  offset: number
  total: number
  totalPages: number
  hasNextPage: boolean
  hasPreviousPage: boolean
  firstItem: number
  lastItem: number
}

Used by numbered page results. For 95 rows, page 2, limit 20:

FieldTypeExampleMeaning
pagenumber2The resolved one-based page (may be clamped to the last page).
limitnumber20Maximum rows requested for the page.
offsetnumber20Number of matching rows skipped.
totalnumber95Total number of matching rows across all pages.
totalPagesnumber5Math.ceil(total / limit); 0 for an empty list.
hasNextPagebooleantrueWhether a later numbered page exists.
hasPreviousPagebooleantrueWhether an earlier numbered page exists.
firstItemnumber21One-based position of the first expected item. 0 when total is zero.
lastItemnumber40One-based position of the last possible item on this page. 0 when total is zero.

These are positions, not database IDs. They're calculated from total, page, and limit, not by inspecting the returned array.

OffsetPaginationMetadata

interface OffsetPaginationMetadata {
  total: number
  limit: number
  offset: number
  hasNextPage: boolean
  hasPreviousPage: boolean
}

Used by native offset results. For total: 95, offset: 40, limit: 20:

FieldTypeExampleMeaning
totalnumber95Total matching rows.
limitnumber20Maximum rows requested.
offsetnumber40Rows skipped; kept even when beyond the total.
hasNextPagebooleantrueWhether the count indicates rows beyond this requested batch.
hasPreviousPagebooleantrueWhether offset > 0; not necessarily proof the immediately preceding batch is non-empty.

CursorPaginationMetadata

interface CursorPaginationMetadata {
  limit: number
  nextCursor: string | null
  hasNextPage: boolean
}
FieldTypeMeaning
limitnumberMaximum rows returned (after page-size normalization).
nextCursorstring | nullToken to supply to the next request. null if no further batch was found.
hasNextPagebooleanWhether a lookahead row exists beyond limit rows.

Cursor results do not have total, totalPages, or a previous cursor.

Cursor building blocks

CursorDirection

type CursorDirection = "asc" | "desc"

"asc" means ascending order (smallest/oldest first); "desc" means descending (largest/newest first). Your keyset comparison must match this direction.

CursorValue

type CursorValue = string | number | bigint | Date

The allowed value of one cursor key. At runtime, numeric values must be finite, dates must be valid, and keys cannot be null. Bigints and Dates are serialized into the cursor token and recovered with their types when decoded.

CursorKey

interface CursorKey {
  key: string
  direction: CursorDirection
  value: CursorValue
}
FieldTypeMeaning
keystringStable, unique key name, such as "createdAt".
directionCursorDirectionAscending or descending sort direction.
valueCursorValueThis row's actual value for that sort key.

CursorPosition

type CursorPosition = readonly [CursorKey, ...CursorKey[]]

A non-empty ordered tuple of CursorKey values. The readonly keyword means the tuple isn't meant to be mutated after construction. Key names must not repeat.

Example for a post with createdAt DESC, id DESC ordering:

const position: CursorPosition = [
  { key: "createdAt", direction: "desc", value: new Date("2026-11-10T00:00:00Z") },
  { key: "id", direction: "desc", value: 42 },
]

Your adapter returns this from getPosition(item). The core turns it into an opaque token; the adapter receives a decoded position on the next request. See Cursor Pagination.

Standalone helper types

PaginationMetadataOptions

interface PaginationMetadataOptions {
  page: number
  limit: number
  total: number
}

Passed to createPaginationMetadata(). All three fields are required. page and limit must be positive safe integers; total must be a non-negative safe integer. A page beyond the last page is clamped.

OffsetPaginationMetadataOptions

interface OffsetPaginationMetadataOptions {
  offset: number
  limit: number
  total: number
}

Passed to createOffsetPaginationMetadata(). All fields are required. offset and total must be non-negative safe integers; limit must be positive. The offset is not clamped.

PageWindowOptions

interface PageWindowOptions {
  page: number
  totalPages: number
  siblings?: number
  boundaries?: number
}

Passed to createPageWindow().

FieldTypeRequired?DefaultMeaning
pagenumberYes—Current positive page number.
totalPagesnumberYes—Total non-negative page count.
siblingsnumberNo1Number of page buttons on either side of current page; may be 0.
boundariesnumberNo1Number of page buttons always shown at each end; at least 1.

PaginationPageItem

type PaginationPageItem = number | "ellipsis"

The type of each value returned by createPageWindow(). A number means a page link; "ellipsis" means a visual gap, not a page to navigate to.

Core error types

PaginationError

type PaginationError = RangeError & {
  readonly code: PaginationErrorCode
}

A normal JavaScript RangeError with a read-only code property. Use isPaginationError(value) to narrow an unknown caught error to this type.

PaginationErrorCode

This type is a union of the core error codes. In the implementation it's derived from the core error-definition object; the following union shows all its possible values:

type PaginationErrorCode =
  | "INVALID_PAGE_SIZE_BOUNDS"
  | "INVALID_DEFAULT_LIMIT"
  | "INVALID_PAGE"
  | "INVALID_LIMIT"
  | "INVALID_OFFSET"
  | "OFFSET_OVERFLOW"
  | "INVALID_TOTAL"
  | "INVALID_TOTAL_PAGES"
  | "INVALID_SIBLINGS"
  | "INVALID_BOUNDARIES"
  | "INVALID_CURSOR"
  | "UNSUPPORTED_CURSOR_VERSION"
  | "INVALID_CURSOR_POSITION"
  | "CURSOR_LIMIT_OVERFLOW"

See Errors for when each code is raised and how to handle it.


Looking for the adapter-specific types, including Drizzle query builders and DrizzleCursorOrderEntry? See Drizzle Type Reference.

On this page