···4545 Reverse bool
4646}
47474848+// maxRecordPages caps how many pages ListAllRecords will follow, as a
4949+// safety net against a server that never returns an empty cursor.
5050+const maxRecordPages = 1000
5151+4852// PutRecord writes a record to the PDS, returning its at:// URI and CID.
4953func (a *ATProto) PutRecord(ctx context.Context, in PutRecordInput) (uri, cid string, err error) {
5054 var out struct {
···9094 }
9195 return &out, nil
9296}
9797+9898+// ListAllRecords fetches every record in collection for repo, following
9999+// pagination cursors until the listing is exhausted. opts.Limit sets the
100100+// page size; opts.Cursor is ignored since pagination always starts from
101101+// the first page.
102102+func (a *ATProto) ListAllRecords(ctx context.Context, repo, collection string, opts ListRecordsOpts) ([]RecordItem, error) {
103103+ var all []RecordItem
104104+ cursor := ""
105105+106106+ for range maxRecordPages {
107107+ opts.Cursor = cursor
108108+ out, err := a.ListRecords(ctx, repo, collection, opts)
109109+ if err != nil {
110110+ return nil, err
111111+ }
112112+ all = append(all, out.Records...)
113113+114114+ if out.Cursor == nil || *out.Cursor == "" {
115115+ return all, nil
116116+ }
117117+ cursor = *out.Cursor
118118+ }
119119+120120+ return nil, fmt.Errorf("exceeded %d pages listing %s records for %q", maxRecordPages, collection, repo)
121121+}
···11package tangled
2233-import "encoding/json"
33+import (
44+ "context"
55+ "encoding/json"
66+ "fmt"
77+)
88+99+// maxPaginationPages caps how many pages fetchAllPages will follow, as a
1010+// safety net against a server that never returns an empty cursor.
1111+const maxPaginationPages = 1000
412513// ListItem is one item in an issue or pull-request listing.
614type ListItem struct {
···2634 Order string // "asc" or "desc"
2735}
28362929-// params builds the XRPC query parameters for subject.
3030-func (o ListOpts) params(subject string) map[string]any {
3737+// params builds the XRPC query parameters for subject, requesting the page
3838+// after cursor (the first page if cursor is empty).
3939+func (o ListOpts) params(subject, cursor string) map[string]any {
3140 params := map[string]any{"subject": subject}
3241 if o.Author != "" {
3342 params["author"] = o.Author
···4352 if o.Order != "" {
4453 params["order"] = o.Order
4554 }
5555+ if cursor != "" {
5656+ params["cursor"] = cursor
5757+ }
4658 return params
4759}
6060+6161+// fetchAllPages calls fetch for successive pages, advancing the cursor it
6262+// returns, until a page reports no further cursor. It returns every item
6363+// across all pages combined.
6464+func fetchAllPages[T any](ctx context.Context, fetch func(ctx context.Context, cursor string) (items []T, nextCursor *string, err error)) ([]T, error) {
6565+ var all []T
6666+ cursor := ""
6767+6868+ for page := 0; page < maxPaginationPages; page++ {
6969+ items, nextCursor, err := fetch(ctx, cursor)
7070+ if err != nil {
7171+ return nil, err
7272+ }
7373+ all = append(all, items...)
7474+7575+ if nextCursor == nil || *nextCursor == "" {
7676+ return all, nil
7777+ }
7878+ cursor = *nextCursor
7979+ }
8080+8181+ return nil, fmt.Errorf("exceeded %d pages without reaching the end of the list", maxPaginationPages)
8282+}
+11-3
tangled/list_issues.go
···1717 References []string `json:"references,omitempty"`
1818}
19192020+// ListIssues fetches every issue for repoDid, following pagination
2121+// cursors until the listing is exhausted.
2022func (t *Tangled) ListIssues(ctx context.Context, repoDid string, opts ListOpts) (*List, error) {
2121- var out List
2222- if err := t.Client.Get(ctx, syntax.NSID("sh.tangled.repo.listIssues"), opts.params(repoDid), &out); err != nil {
2323+ items, err := fetchAllPages(ctx, func(ctx context.Context, cursor string) ([]ListItem, *string, error) {
2424+ var page List
2525+ if err := t.Client.Get(ctx, syntax.NSID("sh.tangled.repo.listIssues"), opts.params(repoDid, cursor), &page); err != nil {
2626+ return nil, nil, err
2727+ }
2828+ return page.Items, page.Cursor, nil
2929+ })
3030+ if err != nil {
2331 return nil, fmt.Errorf("list issues for %q: %w", repoDid, err)
2432 }
2525- return &out, nil
3333+ return &List{Items: items}, nil
2634}
+11-3
tangled/list_pulls.go
···4444 Size int64 `json:"size"`
4545}
46464747+// ListPulls fetches every pull request for repoDid, following pagination
4848+// cursors until the listing is exhausted.
4749func (t *Tangled) ListPulls(ctx context.Context, repoDid string, opts ListOpts) (*List, error) {
4848- var out List
4949- if err := t.Client.Get(ctx, syntax.NSID("sh.tangled.repo.listPulls"), opts.params(repoDid), &out); err != nil {
5050+ items, err := fetchAllPages(ctx, func(ctx context.Context, cursor string) ([]ListItem, *string, error) {
5151+ var page List
5252+ if err := t.Client.Get(ctx, syntax.NSID("sh.tangled.repo.listPulls"), opts.params(repoDid, cursor), &page); err != nil {
5353+ return nil, nil, err
5454+ }
5555+ return page.Items, page.Cursor, nil
5656+ })
5757+ if err != nil {
5058 return nil, fmt.Errorf("list PRs for %q: %w", repoDid, err)
5159 }
5252- return &out, nil
6060+ return &List{Items: items}, nil
5361}