import files, add README & LICENSE

This commit is contained in:
2026-06-29 17:06:34 -07:00
commit aecd90eb84
17 changed files with 2552 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
package cursor
import (
"fmt"
"gitea.auvem.com/go-toolkit/dbx"
)
// PageInfo holds standard pagination information, including cursors for the
// start and end of the page, and flags indicating whether there are more pages
// before or after the current page.
//
// Modeled after the PageInfo type in the GraphQL Relay specification.
type PageInfo struct {
// The cursor string for the last item in the current page.
EndCursor *string `json:"endCursor,omitempty"`
// Whether there are more items after the current page.
HasNextPage bool `json:"hasNextPage"`
// Whether there are more items before the current page.
HasPreviousPage bool `json:"hasPreviousPage"`
// The cursor string for the first item in the current page.
StartCursor *string `json:"startCursor,omitempty"`
}
// CursorFunc is a function that takes a pointer to an object type T, the
// underlying database view object, and returns a GenericCursor or an error.
type CursorFunc[T any] = func(*T) (GenericCursor, error)
// Edge represents a single edge in a connection, containing a node of type T
// and a cursor string for pagination.
type Edge[T any] struct {
Node *T
Cursor string
}
// Connection represents a paginated list of edges, along with page information.
type Connection[T any] struct {
Edges []*Edge[T]
PageInfo *PageInfo
TotalCount int
}
// BuildEdges constructs a Connection object from a list of items of type T,
// using the provided Queryable interface for database operations and a
// count function to determine the total number of items. It also uses a
// cursor function to generate cursors for each item in the list.
func BuildEdges[T any](
sqlo dbx.Queryable,
countFn QueryCountFn,
list []*T,
cursorFunc CursorFunc[T],
) (
conn *Connection[T],
err error,
) {
// Create edges for the connection
edges := make([]*Edge[T], len(list))
for i, item := range list {
if item == nil {
return nil, fmt.Errorf("item at index %d is nil", i)
}
c, err := cursorFunc(item)
if err != nil {
return nil, fmt.Errorf("failed to get cursor for item at index %d: %w", i, err)
}
cstr, err := c.Encode()
if err != nil {
return nil, fmt.Errorf("failed to encode cursor for item at index %d: %w", i, err)
}
edges[i] = &Edge[T]{
Node: item,
Cursor: cstr,
}
}
// Create page info and connection object
var pageInfo PageInfo
var countResult QueryCountResult
if len(edges) > 0 {
// Set start and end cursor strings
pageInfo.StartCursor = &edges[0].Cursor
pageInfo.EndCursor = &edges[len(edges)-1].Cursor
// Fetch total count. Comparisons use composite tuple logic when the cursor
// encodes both order and index columns (see meeting pagination).
startCursor, err := cursorFunc(edges[0].Node)
if err != nil {
return nil, fmt.Errorf("failed to get start cursor: %w", err)
}
endCursor, err := cursorFunc(edges[len(edges)-1].Node)
if err != nil {
return nil, fmt.Errorf("failed to get end cursor: %w", err)
}
if countResult, err = countFn(sqlo, startCursor, endCursor); err != nil {
return nil, fmt.Errorf("failed to query count: %w", err)
}
}
pageInfo.HasNextPage = countResult.After > 0
pageInfo.HasPreviousPage = countResult.Before > 0
return &Connection[T]{
Edges: edges,
PageInfo: &pageInfo,
TotalCount: countResult.Total,
}, nil
}
// ExtractNodes extracts the nodes from a Connection object and returns them
// as a slice of pointers to type T.
func ExtractNodes[T any](conn *Connection[T]) []*T {
nodes := make([]*T, len(conn.Edges))
for i, edge := range conn.Edges {
nodes[i] = edge.Node
}
return nodes
}