Skip to content

Commit 5ce861c

Browse files
author
baton-admin[bot]
committed
chore: update baton-admin doc files
1 parent 68f906d commit 5ce861c

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

.claude/skills/connector/ref-antipatterns.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,95 @@ log.Printf("Authenticating with API key: %s...", apiKey[:8]) // Truncate
2424

2525
---
2626

27+
### Client-Side Pagination Loop
28+
29+
This applies to `List()`, `Entitlements()`, and `Grants()` — any SDK method that receives a pagination token.
30+
31+
```go
32+
// NEVER DO THIS - loop inside a SDK method (List/Entitlements/Grants)
33+
func (u *userBuilder) List(ctx context.Context, parentID *v2.ResourceId,
34+
token *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
35+
36+
var resources []*v2.Resource
37+
cursor := ""
38+
for {
39+
page, nextCursor, err := u.client.ListUsers(ctx, cursor)
40+
if err != nil {
41+
return nil, "", nil, err
42+
}
43+
for _, user := range page {
44+
r, _ := convertUser(user)
45+
resources = append(resources, r)
46+
}
47+
if nextCursor == "" {
48+
break
49+
}
50+
cursor = nextCursor
51+
}
52+
return resources, "", nil, nil // SDK called once; no checkpointing possible
53+
}
54+
55+
// NEVER DO THIS - loop hidden inside the HTTP client
56+
func (c *Client) ListUsers(ctx context.Context) ([]User, error) {
57+
var all []User
58+
cursor := ""
59+
for {
60+
page, next, err := c.getPage(ctx, "/users", cursor)
61+
if err != nil {
62+
return nil, err
63+
}
64+
all = append(all, page...)
65+
if next == "" {
66+
break
67+
}
68+
cursor = next
69+
}
70+
return all, nil // Caller cannot paginate; entire dataset loaded into memory
71+
}
72+
```
73+
74+
**Why it's bad:** The SDK drives the pagination loop — it calls `List()`, `Entitlements()`, and `Grants()` repeatedly with incrementing tokens. If the connector paginates internally (in the SDK method itself or inside the HTTP client), the SDK only ever calls the method once and receives all results at once. This:
75+
- Breaks checkpointing: if a sync crashes mid-run, it cannot resume from the last page
76+
- Loads the entire dataset into memory at once (OOM for orgs with 100k+ users)
77+
- Bypasses any rate limiting or backpressure the SDK applies between pages
78+
- Causes detached contexts and timeouts: the SDK manages context lifetimes and deadlines per page call; a long-running internal loop outlives the context the SDK intended for a single page, leading to cancelled requests and hard-to-diagnose timeout errors
79+
80+
There are no exceptions. HTTP client methods must always accept a cursor/token parameter and return a single page.
81+
82+
**Do instead:**
83+
```go
84+
// HTTP client: always return one page + next token
85+
func (c *Client) ListUsers(ctx context.Context, cursor string) ([]User, string, error) {
86+
resp, err := c.get(ctx, "/users", cursor)
87+
if err != nil {
88+
return nil, "", err
89+
}
90+
return resp.Users, resp.NextCursor, nil
91+
}
92+
93+
// SDK method: handle exactly one page per call, let the SDK loop
94+
func (u *userBuilder) List(ctx context.Context, parentID *v2.ResourceId,
95+
token *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
96+
97+
cursor := extractCursor(token)
98+
users, nextCursor, err := u.client.ListUsers(ctx, cursor)
99+
if err != nil {
100+
return nil, "", nil, err
101+
}
102+
103+
resources := make([]*v2.Resource, 0, len(users))
104+
for _, user := range users {
105+
r, _ := convertUser(user)
106+
resources = append(resources, r)
107+
}
108+
109+
return resources, nextCursor, nil, nil // SDK calls List() again with nextCursor
110+
}
111+
// Same pattern applies identically to Entitlements() and Grants()
112+
```
113+
114+
---
115+
27116
### Buffering All Data in Memory
28117

29118
```go

0 commit comments

Comments
 (0)