pagination

package module
v0.0.0-...-082af60 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 7, 2026 License: MIT Imports: 6 Imported by: 0

README

pagination Go Reference

The pagination package allows construct the simple and safe cursor-based pagination for services.

Cursor-based pagination is a highly efficient, high-performance technique for large datasets that uses a pointer (the "cursor") to mark the last retrieved record. Unlike offset pagination, it avoids expensive, slow database scans on deep pages, providing consistent performance by directly querying records after a specific identifier, such as a timestamp or ID.

How It Works

Instead of using page numbers, the client requests a specific number of items after a unique marker (the cursor).

  • Initial Request: GET /items?limit=10
  • Subsequent Request: GET /items?limit=10&cursor=dGltZXN0YW1wOjE2MjI1NDg4MDA=
  • Response: The server returns the next 10 items and a next cursor value

Installation

go get github.com/sevlyar/pagination

Documentation

Full go doc style documentation for the package can be viewed online by using the GoDoc site: https://pkg.go.dev/github.com/sevlyar/pagination

Documentation

Overview

Package pagination allows construct the simple and safe cursor-based pagination for services.

Pagination parameter should be the last in repository method parameters:

type ItemFilterer interface {
	FilterItems(ctx context.Context, params FilterParams, span *pagination.Span) ([]Items, error)
}

Basic repository implementation:

func (s *Storage) FilterItems(ctx context.Context, params FilterParams, span *pagination.Span) ([]Items, error) {
	var id int64
	if err := span.Pos.Bind(&id); err != nil {
		return nil, ErrInvalidCursor
	}

	const spanSizeLimit = 100
	limit := span.LimitSize(spanSizeLimit)

	// use id and limit in database query, fetch items
	// ...

	if len(items) > 0 {
		// advance cursor
		id = items[len(items)-1].Id
	}

	span.SetLast(len(items) < limit)

	return items, nil
}

Repository method using:

func handleGetItems(resp http.ResponseWriter, req *http.Request) {
	// GET /items?limit=10&cursor=dGltZXN0YW1wOjE2MjI1NDg4MDA=

	q := req.URL.Query()
	cursor := q.Get("cursor")
	limitStr := q.Get("limit")
	limit, _ := strconv.Atoi(limitStr)

	span, _ := NewSpan(cursor, limit)
	items, _ := stor.FilterItems(req.Context(), FilterParams{}, span)

	resp.Header().Add("X-Pagination-Cursor", span.Pos.EncodeToString())

	// marshal response data
	// ...
}

Compound cursor allows to use any sorting fields and order:

type Cursor struct {
	T int64 // time
	R int64 // rowid
}
cursor := Cursor {
	T: time.Now().UnixMilli(),
	R: math.MaxInt64,
}
span.Pos.Bind(&cursor)

The package supports only comparable values as cursor state. For example, unable to use slice as cursor or it's part. But you can use arrays.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cursor

type Cursor struct {
	// contains filtered or unexported fields
}

Cursor keeps position of a collection between calls of repository method and hides the repository implementation details. Cursor allow transfer state across process boundaries. Zero value means starting position.

func ParseCursor

func ParseCursor(str string) (*Cursor, error)

ParseCursor parses string representation of the Cursor. Empty string treats as starting position.

Returns en errors in case invalid cursor.

func (*Cursor) Bind

func (cur *Cursor) Bind(v any) error

Bind binds state of the cursor with variable pointed by v. The variable value defines internal structure of the cursor. Changes of the variable will be change cursor state. v should be pointer of comparable value: struct, scalar.

Bind panics in case cursor misuse - if cursor state was not changed on previous iteration.

Don't use with Get/Set methods.

func (*Cursor) EncodeToString

func (cur *Cursor) EncodeToString() string

EncodeToString encodes cursor state to string. The string url-encoded and safe to use in url address. Empty string means cursor reach end of collection.

EncodeToString panics in case cursor misuse - if cursor state was not changed between iterations.

func (*Cursor) Get

func (cur *Cursor) Get(v any) error

Get puts state of the cursor into variable pointed by v. v should be pointer of comparable value: struct, scalar.

Get panics in case cursor misuse - if cursor state was not changed on previous iteration.

Don't use with Bind method.

func (*Cursor) IsOutOfScope

func (cur *Cursor) IsOutOfScope() bool

IsOutOfScope returns true in case the cursor reach end of collection.

func (*Cursor) MarkOutOfScope

func (cur *Cursor) MarkOutOfScope()

MarkOutOfScope sets flag the cursor reach end of collection.

func (*Cursor) Set

func (cur *Cursor) Set(v any)

Set writes state of the cursor from v. v should be comparable value: struct, scalar.

Don't use with Bind method.

type Span

type Span struct {
	Pos *Cursor
	// contains filtered or unexported fields
}

Span presents position and size of collection items chunk in the collection.

func NewFirstSpan

func NewFirstSpan(size int) *Span

NewFirstSpan returns a new span of the defined size and in the starting position.

func NewSpan

func NewSpan(cursor string, size int) (*Span, error)

NewSpan returns a new span of the defined size and in position defined by the cursor value.

func (*Span) IsLast

func (p *Span) IsLast() bool

IsLast returns true in case the span is last in the collection.

func (*Span) LimitSize

func (p *Span) LimitSize(maxSize int) int

LimitSize limits size of the span by the maxSize value and returns the size.

func (*Span) LimitSize64

func (p *Span) LimitSize64(maxSize int64) int64

LimitSize64 limits size of the span by the maxSize value and returns the size.

func (*Span) SetLast

func (p *Span) SetLast(f bool)

SetLast maks the span as the last in case f is true.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL