relay

package module
v0.0.0-...-7bb74a7 Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2024 License: MIT Imports: 3 Imported by: 0

README

gorelay

Move to http://github.com/theplant/gorelay

gorelay is a library designed to simplify Relay-style pagination in Go applications, supporting both cursor-based and offset-based pagination. It helps developers efficiently implement GraphQL pagination queries while offering optimization options, such as skipping TotalCount queries and encrypting cursors.

Currently, only the GORM adapter is provided by default. For other adapters, refer to gormrelay to implement them yourself.

Features

  • Supports cursor-based and offset-based pagination: You can freely choose high-performance cursor pagination based on multiple indexed columns, or use offset pagination.
  • Optional cursor encryption: Supports encrypting cursors using AES or Base64 to ensure the security of pagination information.
  • Flexible query strategies: Optionally skip the TotalCount query to improve performance, especially in large datasets.
  • Non-generic support: Even without using Go generics, you can paginate using the any type for flexible use cases.

Usage

Basic Usage
p := relay.New(
    true, // nodesOnly, default returns nodes and pageInfo
    10, 10, // maxLimit / limitIfNotSet
    []relay.OrderBy{
        {Field: "ID", Desc: false}, // default order if not provided
    },
    func(ctx context.Context, req *relay.ApplyCursorsRequest) (*relay.ApplyCursorsResponse[*User], error) {
        // Offset-based pagination
        // return gormrelay.NewOffsetAdapter[*User](db)(ctx, req)
        // Cursor-based pagination
        return gormrelay.NewKeysetAdapter[*User](db)(ctx, req)
    },
)
resp, err := p.Paginate(context.Background(), &relay.PaginateRequest[*User]{
    First: lo.ToPtr(10), // query first 10 records
})
Encrypting Cursors

If you need to encrypt cursors, you can use WrapBase64 or WrapAES wrappers:

// Encrypt cursors with Base64
cursor.WrapBase64(gormrelay.NewOffsetAdapter[*User](db))

// Encrypt cursors with AES
cursor.WrapAES(gormrelay.NewKeysetAdapter[*User](db), encryptionKey)
Skipping TotalCount Query for Optimization

To improve performance, you can skip querying TotalCount, especially useful for large datasets:

// Cursor-based pagination without querying TotalCount
cursor.NewKeysetAdapter(gormrelay.NewKeysetFinder[any](db))

// Note: For offset-based pagination, if you can't query TotalCount, 
// using `Last != nil && Before == nil` is not possible.
cursor.NewOffsetAdapter(gormrelay.NewOffsetFinder[any](db))
Non-Generic Usage

If you do not use generics, you can create a paginator with the any type and combine it with the db.Model method:

p := relay.New(
    false, // nodesOnly
    10, 10,
    []relay.OrderBy{
        {Field: "ID", Desc: false},
    },
    func(ctx context.Context, req *relay.ApplyCursorsRequest) (*relay.ApplyCursorsResponse[any], error) {
        // Since this is a generic function (T: any), we must call db.Model(x)
        return gormrelay.NewKeysetAdapter[*User](db.Model(&User{}))(ctx, req)
    },
)
resp, err := p.Paginate(context.Background(), &relay.PaginateRequest[any]{
    First: lo.ToPtr(10), // query first 10 records
})

Reference

For more information about Relay-style pagination, refer to GraphQL Connections.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EdgesToReturn

func EdgesToReturn[T any](
	ctx context.Context,
	before, after *string, first, last *int,
	orderBys []OrderBy,
	nodesOnly bool,
	applyCursorsFunc ApplyCursorsFunc[T],
) (edges []Edge[T], nodes []T, pageInfo *PageInfo, err error)

https://relay.dev/graphql/connections.htm#sec-Pagination-algorithm https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo.Fields

Types

type ApplyCursorsRequest

type ApplyCursorsRequest struct {
	Before   *string
	After    *string
	OrderBys []OrderBy
	Limit    int
	FromLast bool
}

type ApplyCursorsResponse

type ApplyCursorsResponse[T any] struct {
	Edges              []LazyEdge[T]
	TotalCount         int
	HasBeforeOrNext    bool // `before` exists or it's next exists
	HasAfterOrPrevious bool // `after` exists or it's previous exists
}

type Edge

type Edge[T any] struct {
	Node   T      `json:"node"`
	Cursor string `json:"cursor"`
}

type LazyEdge

type LazyEdge[T any] struct {
	Node   T
	Cursor func(ctx context.Context, node T) (string, error)
}

type OrderBy

type OrderBy struct {
	Field string `json:"field"`
	Desc  bool   `json:"desc"`
}

type PageInfo

type PageInfo struct {
	TotalCount      int     `json:"totalCount,omitempty"`
	HasNextPage     bool    `json:"hasNextPage"`
	HasPreviousPage bool    `json:"hasPreviousPage"`
	StartCursor     *string `json:"startCursor"`
	EndCursor       *string `json:"endCursor"`
}

type PaginateRequest

type PaginateRequest[T any] struct {
	After    *string   `json:"after"`
	First    *int      `json:"first"`
	Before   *string   `json:"before"`
	Last     *int      `json:"last"`
	OrderBys []OrderBy `json:"orderBys"`
}

type PaginateResponse

type PaginateResponse[T any] struct {
	Edges []Edge[T] `json:"edges,omitempty"`
	// Sometimes we need nodes only
	Nodes    []T      `json:"nodes,omitempty"`
	PageInfo PageInfo `json:"pageInfo"`
}

type Pagination

type Pagination[T any] interface {
	Paginate(ctx context.Context, req *PaginateRequest[T]) (*PaginateResponse[T], error)
}

func New

func New[T any](nodesOnly bool, maxLimit int, limitIfNotSet int, orderBysIfNotSet []OrderBy, applyCursorsFunc ApplyCursorsFunc[T]) Pagination[T]

type PaginationFunc

type PaginationFunc[T any] func(ctx context.Context, req *PaginateRequest[T]) (*PaginateResponse[T], error)

func (PaginationFunc[T]) Paginate

func (f PaginationFunc[T]) Paginate(ctx context.Context, req *PaginateRequest[T]) (*PaginateResponse[T], error)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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