end 683b0ddbf4 docs: add package godoc and clarify public API
Prepare the extracted library for external consumption with grouped
godoc, extension point docs, and setup requirements. Wire
ErrBadCursorString into decode paths and drop redundant Paginate*Conds
aliases.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 17:14:05 -07:00
2026-06-29 17:06:34 -07:00
2026-06-29 17:06:34 -07:00
2026-06-29 17:06:34 -07:00
2026-06-29 17:06:34 -07:00
2026-06-29 17:06:34 -07:00

cursor

Relay-style cursor pagination for go-jet MySQL queries.

Encodes opaque cursor strings from column values, applies consistent WHERE / ORDER BY clauses, and builds GraphQL Relaycompatible Connection results with PageInfo and total counts.

Requirements

go get gitea.auvem.com/go-toolkit/cursor

Setup

Register every column referenced by a cursor at application startup. The registry resolves columns when encoding, decoding, and building SQL.

import (
    "gitea.auvem.com/go-toolkit/cursor"
    "yourapp/.gen/yourdb/table"
)

func init() {
    cursor.RegisterColumn(table.User.ID, table.Meeting.StartTime)
}

Concepts

Term Meaning
Index Stable position column (usually the primary key).
Order column Column users sort by. May differ from the index.
Simple cursor Index and order column are the same; paginate on one value.
Tuple ordering Order column ≠ index; results sort by (order_col, index_col).
Composite cursor Tuple ordering plus an encoded OrderValue on each edge — required for correct after pagination when sort values can repeat.

Quick start

1. Define a cursor factory

func newUserCursor() *cursor.Cursor[mysql.IntegerExpression, mysql.ColumnInteger] {
    return cursor.NewCursor(
        cursor.NewInt64Value(0, table.User.ID),
        table.User.ID,
        cursor.OrderDescending,
    )
}

2. Paginate a query

active := newUserCursor()
if after != nil {
    _ = active.Decode(*after)
}

stmt := table.User.
    SELECT(table.User.AllColumns).
    WHERE(filters).
    WHERE(cursor.PaginateConds(active)).
    ORDER_BY(cursor.OrderByClauses(active)...).
    LIMIT(int64(limit))

var rows []*User
dbx.MustQuery(db, stmt, &rows, nil)

Or use PageQuery to run the query and build edges in one step:

conn, err := cursor.PageQuery[User, mysql.IntegerExpression, mysql.ColumnInteger]{
    Sqlo:    db,
    Stmt:    table.User.SELECT(table.User.AllColumns),
    Conds:   filters,
    Cursor:  decodedCursor,       // nil for first page
    Default: newUserCursor,
    Limit:   limit,
    CountFn: cursor.BuildQueryCountFn(table.User.ID, table.User, filters),
    ToEdge: func(_ *cursor.Cursor[...], item *User) (cursor.GenericCursor, error) {
        return newUserCursor().CopyWithVal(
            cursor.NewInt64Value(item.ID, table.User.ID),
        ), nil
    },
}.Run()

3. Relay after / first args

conn, err := cursor.ConnectionFromRelayArgs(
    after, first, newUserCursor,
    func(c *cursor.Cursor[...], limit int) (*cursor.Connection[User], error) {
        return listUsers(c, limit)
    },
)

4. Composite (multi-column) sort

When sorting by a non-unique column, include both index and order values on each edge:

base := cursor.NewCursor(
    cursor.NewStringValue("", table.Meeting.ID),
    table.Meeting.StartTime,
    cursor.OrderDescending,
)

edgeCursor := base.CopyWithVals(
    cursor.NewStringValue(row.ID, table.Meeting.ID),
    cursor.NewTimestampValue(row.StartTime, table.Meeting.StartTime),
)

API overview

Layer Types / functions
Column registry RegisterColumn, RegisterColumnList, GetColumn, GetColumnByKey
Cursor values NewInt64Value, NewUint64Value, NewStringValue, NewTimestampValue
Cursor NewCursor, CopyWithVal, CopyWithVals, Encode, Decode, NewCursorFromAfterPtr
SQL helpers PaginateConds, OrderByClauses, QueryCount, BuildQueryCountFn
Relay output BuildEdges, PageQuery, ConnectionFromRelayArgs, Connection, PageInfo

Value types

Index / order type Constructor
int64 NewInt64Value
uint64 NewUint64Value
string / UUID NewStringValue
time.Time NewTimestampValue

Extension points

Custom cursor value types implement [GenericExpr] (order values) and [ExprMarshaler] (index values). See package godoc for requirements.

Errors

Decode failures return ErrBadCursorString; use errors.Is to detect invalid client cursors.

Utilities

  • ExtractNodes — nodes from a Connection
  • DereferenceSlice[]*T[]T, skipping nils
S
Description
Relay-style cursor pagination for go-jet MySQL queries.
Readme BSD-3-Clause 64 KiB
Languages
Go 100%