relay

package module
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Jan 4, 2025 License: MIT Imports: 3 Imported by: 24

README

relay

relay is a library designed to simplify Relay-style pagination in Go applications, supporting both keyset-based and offset-based pagination. It helps developers efficiently implement 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 keyset-based and offset-based pagination: You can freely choose high-performance keyset pagination based on multiple indexed columns, or use offset pagination.
  • Optional cursor encryption: Supports encrypting cursors using GCM(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(
    cursor.Base64(func(ctx context.Context, req *relay.ApplyCursorsRequest) (*relay.ApplyCursorsResponse[*User], error) {
        // Offset-based pagination
        // return gormrelay.NewOffsetAdapter[*User](db)(ctx, req)

        // Keyset-based pagination
        return gormrelay.NewKeysetAdapter[*User](db)(ctx, req)
    }),
    // defaultLimit / maxLimit
    relay.EnsureLimits[*User](10, 100),
    // Append primary sorting fields, if any are unspecified
    relay.EnsurePrimaryOrderBy[*User](
        relay.OrderBy{Field: "ID", Desc: false},
        relay.OrderBy{Field: "Version", Desc: false},
    ),
)

conn, err := p.Paginate(
    context.Background(),
    // relay.WithSkip(context.Background(), relay.Skip{
    //     Edges:      true,
    //     Nodes:      true,
    //     PageInfo:   true,
    //     TotalCount: true,
    // }),

    // Query first 10 records
    &relay.PaginateRequest[*User]{
        First: lo.ToPtr(10),
    }
)
Cursor Encryption

If you need to encrypt cursors, you can use cursor.Base64 or cursor.GCM wrappers:

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

// Encrypt cursors with GCM(AES)
gcm, err := cursor.NewGCM(encryptionKey)
require.NoError(t, err)
cursor.GCM(gcm)(gormrelay.NewKeysetAdapter[*User](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(
    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[any](db.Model(&User{}))(ctx, req)
    },
    relay.EnsureLimits[any](10, 100),
    relay.EnsurePrimaryOrderBy[any](relay.OrderBy{Field: "ID", Desc: false}),
)
conn, 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 GetNodeProcessor added in v0.3.0

func GetNodeProcessor[T any](ctx context.Context) func(ctx context.Context, node T) (T, error)

func WithNodeProcessor added in v0.3.0

func WithNodeProcessor[T any](ctx context.Context, processor func(ctx context.Context, node T) (T, error)) context.Context

func WithSkip added in v0.3.0

func WithSkip(ctx context.Context, skip Skip) context.Context

Types

type ApplyCursorsRequest

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

type ApplyCursorsResponse

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

type Connection added in v0.3.0

type Connection[T any] struct {
	Edges      []*Edge[T] `json:"edges,omitempty"`
	Nodes      []T        `json:"nodes,omitempty"`
	PageInfo   *PageInfo  `json:"pageInfo,omitempty"`
	TotalCount *int       `json:"totalCount,omitempty"`
}

type CursorMiddleware

type CursorMiddleware[T any] func(next ApplyCursorsFunc[T]) ApplyCursorsFunc[T]

CursorMiddleware is a wrapper for ApplyCursorsFunc (middleware pattern)

func CursorMiddlewaresFromContext

func CursorMiddlewaresFromContext[T any](ctx context.Context) []CursorMiddleware[T]

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"`
}

func AppendPrimaryOrderBy

func AppendPrimaryOrderBy(orderBys []OrderBy, primaryOrderBys ...OrderBy) []OrderBy

type PageInfo

type PageInfo struct {
	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 Pagination

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

func New

func New[T any](applyCursorsFunc ApplyCursorsFunc[T], middlewares ...PaginationMiddleware[T]) Pagination[T]

type PaginationFunc

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

func (PaginationFunc[T]) Paginate

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

type PaginationMiddleware

type PaginationMiddleware[T any] func(next Pagination[T]) Pagination[T]

PaginationMiddleware is a wrapper for Pagination (middleware pattern)

func AppendCursorMiddleware

func AppendCursorMiddleware[T any](cursorMiddlewares ...CursorMiddleware[T]) PaginationMiddleware[T]

func EnsureLimits added in v0.2.0

func EnsureLimits[T any](defaultLimit, maxLimit int) PaginationMiddleware[T]

EnsureLimits ensures that the limit is within the range 0 -> maxLimit and uses defaultLimit if limit is not set or is negative This method introduced a breaking change in version 0.4.0, intentionally swapping the order of parameters to strongly indicate the breaking change. https://github.com/theplant/relay/compare/genx?expand=1#diff-02f50901140d6057da6310a106670552aa766a093efbc2200fb34c099b762131R14

func EnsurePrimaryOrderBy added in v0.2.0

func EnsurePrimaryOrderBy[T any](primaryOrderBys ...OrderBy) PaginationMiddleware[T]

type Skip added in v0.3.0

type Skip struct {
	Edges, Nodes, TotalCount, PageInfo bool
}

func GetSkip added in v0.3.0

func GetSkip(ctx context.Context) Skip

func (Skip) All added in v0.3.0

func (s Skip) All() bool

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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