You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+46-5Lines changed: 46 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,21 +51,22 @@ func main() {
51
51
52
52
`ghkit.New` is generic over the returned type; passing `github.NewClient` lets type inference pick up `*github.Client`. ghkit itself has zero dependency on `go-github`. It isn't in `go.mod`, isn't imported, and won't end up in your compiled binary unless you pull it in yourself. Pass whichever go-github major (or any other `func(*http.Client) T` factory) you want.
53
53
54
-
For runnable starter programs, see [`examples/`](examples/): `static-pat`, `installation-token`, `backfill`, and `github-enterprise` are each a complete `main()` you can copy-paste.
54
+
For runnable starter programs, see [`examples/`](examples/): `static-pat`, `installation-token`, `backfill`, `github-enterprise`, and `retry-on-flaky` are each a complete `main()` you can copy-paste.
Each layer is optional. The stack is opt-in: `ghkit.HTTPClient(...)` only includes the layers you asked for. The order is load-bearing, though. ETag sits below the oauth2 layer so it hashes the request with the current Authorization header. Rate limiting sits above so it sees every outgoing call including ETag-triggered conditional GETs. The proactive throttle sits outermost so it caps issued RPS regardless of cache replays.
69
+
Each layer is optional. The stack is opt-in: `ghkit.HTTPClient(...)` only includes the layers you asked for. The order is load-bearing, though. ETag sits below the oauth2 layer so it hashes the request with the current Authorization header. Rate limiting sits above so it sees every outgoing call including ETag-triggered conditional GETs. Retry sits below RateLimit so 429s are deferred to the rate-limit layer; sits above oauth2 so retried requests get the latest token via oauth2's per-call `Source.Token()`. The proactive throttle sits outermost so it caps issued RPS regardless of cache replays.
69
70
70
71
The rate-limit layer's named options (`WithPrimaryLimitDetected`, `WithSecondaryLimitDetected`, `WithTotalSleepLimit`, `WithLogger`) cover the common callbacks. For upstream features ghkit does not curate, `ratelimit.WithUpstreamOptions(opts ...any)` forwards raw options to `gofri/go-github-ratelimit/v2`.
`WithEnterpriseURLs` requires both URLs to end with a trailing slash and returns an error otherwise. `UserAgent` can also be set at the transport level via `ghkit.WithUserAgent("my-app/1.0")`, which applies to every outbound request regardless of which SDK you wrap around `HTTPClient()`.
184
185
</details>
185
186
187
+
<details>
188
+
<summary><b>Retry on transient failures (5xx, network errors)</b></summary>
189
+
190
+
```go
191
+
gh, err:= ghkit.New(github.NewClient,
192
+
ghkit.WithToken(os.Getenv("GITHUB_TOKEN")),
193
+
ghkit.WithRetry(), // 3 attempts, 200ms..2s decorrelated jitter, idempotent methods only
194
+
)
195
+
```
196
+
197
+
Tuned policy with POST opt-in via `Idempotency-Key`:
429 is hard-excluded regardless of the predicate so `ratelimit` (the layer above) owns it. `Retry-After` is honored when present; if it exceeds `maxDelay` the call returns `(resp, retry.ErrRetryAfterExceedsMax)` and the caller owns drain+close on `resp`.
223
+
</details>
224
+
186
225
<details>
187
226
<summary><b>Use only the etag sub-package in a hand-built stack</b></summary>
188
227
@@ -240,6 +279,8 @@ Gzip has to be disabled on the underlying transport, otherwise the hash domain d
240
279
241
280
`etag.WithKeyScope` is required the moment you share a cache across identities, static PAT or JIT alike. Two callers hitting the same URL under different auth without a scope would race their bodies into the same key, and the library refuses to guess which one wins. Use the installation ID, a per-app scope, or any opaque string.
242
281
282
+
Each retry attempt is a real HTTP call from the throttle layer's perspective. `WithRetry(retry.WithMaxAttempts(5))` combined with `WithRequestsPerSecond(2, 1)` means a worst-case failing request can briefly use 5x your nominal RPS budget. Size accordingly or leave `maxAttempts` at the default (3).
283
+
243
284
## Using a different go-github version
244
285
245
286
The kit has no compile-time pin on `go-github`. Its main `go.mod` does not require `github.com/google/go-github`, so you choose the major. Two equally valid shapes:
|`github-enterprise/`| Targeting GitHub Enterprise Server via `WithEnterpriseURLs` and a custom user agent. |
29
+
|`retry-on-flaky/`|`WithRetry` with a tuned backoff and a custom predicate that opts POST in via `Idempotency-Key`. |
29
30
30
31
Each example reads its credentials from environment variables (e.g. `GITHUB_TOKEN`, `GITHUB_ENTERPRISE_TOKEN`) and calls `gh.Repositories.Get(...)` as a smoke test. They will fail at the API call without valid credentials. That's expected; they're starting templates, not standalone tools.
0 commit comments