Files
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

84 lines
2.2 KiB
Go

package cursor
import (
"fmt"
"gitea.auvem.com/go-toolkit/dbx"
"github.com/go-jet/jet/v2/mysql"
)
// PageQuery runs a paginated SELECT and builds a Connection from the results.
type PageQuery[T any, IE mysql.Expression, IC mysql.Column] struct {
Sqlo dbx.Queryable
Stmt mysql.SelectStatement
Conds mysql.BoolExpression
Cursor *Cursor[IE, IC]
Default func() *Cursor[IE, IC]
Limit int
CountFn QueryCountFn
ToEdge func(active *Cursor[IE, IC], item *T) (GenericCursor, error)
// Scan runs the paginated query into dest. When nil, Run uses Stmt.Query.
Scan func(stmt mysql.SelectStatement, dest *[]*T) error
// AfterScan optionally transforms rows after the query and before building edges.
AfterScan func(items []*T) ([]*T, error)
}
// Run executes the paginated query and returns a Connection.
func (q PageQuery[T, IE, IC]) Run() (*Connection[T], error) {
active := q.Cursor
if active == nil {
active = q.Default()
}
stmt := q.Stmt
stmt.WHERE(q.Conds.AND(PaginateConds(q.Cursor)))
stmt.ORDER_BY(OrderByClauses(active)...)
if q.Limit > 0 {
stmt = stmt.LIMIT(int64(q.Limit))
}
var items []*T
scan := q.Scan
if scan == nil {
scan = func(s mysql.SelectStatement, dest *[]*T) error {
return s.Query(q.Sqlo, dest)
}
}
if err := scan(stmt, &items); err != nil {
return nil, fmt.Errorf("failed to run paginated query: %w", err)
}
if q.AfterScan != nil {
var err error
items, err = q.AfterScan(items)
if err != nil {
return nil, err
}
}
return BuildEdges(q.Sqlo, q.CountFn, items, func(item *T) (GenericCursor, error) {
return q.ToEdge(active, item)
})
}
// ConnectionFromRelayArgs parses Relay after/first args and calls list with the
// decoded cursor and limit. newZero returns a template cursor used to decode after
// (same shape as the cursor factory for the resource, e.g. newUserCursor).
func ConnectionFromRelayArgs[T any, IE mysql.Expression, IC mysql.Column](
after *string,
first *int,
newZero func() *Cursor[IE, IC],
list func(cursor *Cursor[IE, IC], limit int) (*Connection[T], error),
) (*Connection[T], error) {
limit := 0
if first != nil {
limit = *first
}
c, err := NewCursorFromAfterPtr(newZero, after)
if err != nil {
return nil, err
}
return list(c, limit)
}