Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.oauth

Unified OAuth token-refresh facade, shared by every provider.

WHY THIS EXISTS — Providers whose token endpoint ROTATES the refresh_token on every exchange (Anthropic, OpenAI Codex) must never run two refresh exchanges at once: the second reuses an already-rotated refresh token and the server answers HTTP 400 invalid_grant. Under a 401 "storm" — the turn loop's per-iteration retry PLUS a usage/limits poll, all sharing one credential — that race killed whole turns (~10 refreshes/min: one lost the rotation race and the turn died). Providers that mint a short-lived token from a STABLE credential (GitHub Copilot) don't 400, but still benefit: concurrent 401s otherwise stampede the exchange endpoint with redundant calls.

THE MODEL — refresh is serialized PER CREDENTIAL STORE, never globally. Each make-file-refresher / refresher call mints its OWN lock, so a refresh to Anthropic and a refresh to Codex run fully in parallel; only two refreshes to the SAME store (e.g. two sessions both hitting Anthropic) serialize — which is the whole point. Once the lock is held, a caller REUSES a result another thread just produced (creds persisted within default-reuse-window-ms, or an already-valid cache) so a burst of N concurrent 401s collapses into ONE exchange.

USE — file-backed rotating stores (Anthropic, Codex): make-file-refresher. Cache-backed / bespoke stores (Copilot): refresher with custom reuse/refresh fns. Both return a 0-arg fn yielding the provider-token map, owning their own lock; drop them straight into :provider/get-token-fn / :provider/refresh-token-fn.

Unified OAuth token-refresh facade, shared by every provider.

WHY THIS EXISTS — Providers whose token endpoint ROTATES the
refresh_token on every exchange (Anthropic, OpenAI Codex) must never run
two refresh exchanges at once: the second reuses an already-rotated
refresh token and the server answers HTTP 400 `invalid_grant`. Under a
401 "storm" — the turn loop's per-iteration retry PLUS a usage/limits
poll, all sharing one credential — that race killed whole turns (~10 refreshes/min: one lost the
rotation race and the turn died). Providers that mint a short-lived token from a STABLE
credential (GitHub Copilot) don't 400, but still benefit: concurrent
401s otherwise stampede the exchange endpoint with redundant calls.

THE MODEL — refresh is serialized PER CREDENTIAL STORE, never globally.
Each `make-file-refresher` / `refresher` call mints its OWN lock, so a
refresh to Anthropic and a refresh to Codex run fully in parallel; only
two refreshes to the SAME store (e.g. two sessions both hitting
Anthropic) serialize — which is the whole point. Once the lock is held,
a caller REUSES a result another thread just produced (creds persisted
within `default-reuse-window-ms`, or an already-valid cache) so a burst
of N concurrent 401s collapses into ONE exchange.

USE — file-backed rotating stores (Anthropic, Codex): `make-file-refresher`.
Cache-backed / bespoke stores (Copilot): `refresher` with custom
reuse/refresh fns. Both return a 0-arg fn yielding the provider-token
map, owning their own lock; drop them straight into
`:provider/get-token-fn` / `:provider/refresh-token-fn`.
raw docstring

default-reuse-window-msclj

A refresh persisted/produced within this window is REUSED instead of re-run. Wide enough to absorb a 401 burst (every iteration's retry + the limits poll), short enough that a genuinely-stale token still refreshes promptly.

A refresh persisted/produced within this window is REUSED instead of
re-run. Wide enough to absorb a 401 burst (every iteration's retry +
the limits poll), short enough that a genuinely-stale token still
refreshes promptly.
sourceraw docstring

fresh-within?clj

(fresh-within? saved-at-ms)
(fresh-within? saved-at-ms window-ms)

True when saved-at-ms (epoch millis, or nil) is within window-ms of now — i.e. another caller persisted these creds this recently, so they are safe to reuse rather than running another (rotating) exchange.

True when `saved-at-ms` (epoch millis, or nil) is within `window-ms` of
now — i.e. another caller persisted these creds this recently, so they
are safe to reuse rather than running another (rotating) exchange.
sourceraw docstring

make-file-refresherclj

(make-file-refresher {:keys [load saved-at refresh-token exchange! persist!
                             ->token no-token! reuse-window-ms]})

Build a 0/1-arg single-flight refresh fn for a FILE-backed credential store whose token endpoint ROTATES the refresh_token (Anthropic, OpenAI Codex). Owns its own lock; reuses creds persisted within the reuse window instead of running a second (rotating) exchange — UNLESS the reusable token equals the one just rejected (pass that token when calling the returned fn on 401 recovery), so a server-rotated but locally-fresh token is re-exchanged rather than handed back dead.

opts — all required unless noted: :load (fn [] -> creds-map|nil) read persisted creds :saved-at (fn [creds] -> epoch-ms|nil) persist timestamp (usually the keyword :saved-at-ms/:saved-at) :refresh-token (fn [creds] -> string|nil) the rotating refresh token :exchange! (fn [refresh-token] -> creds) the rotating HTTP call :persist! (fn [creds] -> creds) save + STAMP saved-at :->token (fn [creds] -> token-map) runtime provider-token shape :no-token! (fn [] -> throws) when no refresh token on file :reuse-window-ms (optional) override the default window

Returns the provider-token map produced by :->token.

Build a 0/1-arg single-flight refresh fn for a FILE-backed credential
store whose token endpoint ROTATES the refresh_token (Anthropic,
OpenAI Codex). Owns its own lock; reuses creds persisted within the
reuse window instead of running a second (rotating) exchange — UNLESS the
reusable token equals the one just rejected (pass that token when calling the
returned fn on 401 recovery), so a server-rotated but locally-fresh token is
re-exchanged rather than handed back dead.

opts — all required unless noted:
  :load          (fn [] -> creds-map|nil)       read persisted creds
  :saved-at      (fn [creds] -> epoch-ms|nil)   persist timestamp
                                                (usually the keyword
                                                `:saved-at-ms`/`:saved-at`)
  :refresh-token (fn [creds] -> string|nil)     the rotating refresh token
  :exchange!     (fn [refresh-token] -> creds)  the rotating HTTP call
  :persist!      (fn [creds] -> creds)          save + STAMP saved-at
  :->token       (fn [creds] -> token-map)      runtime provider-token shape
  :no-token!     (fn [] -> throws)              when no refresh token on file
  :reuse-window-ms (optional) override the default window

Returns the provider-token map produced by `:->token`.
sourceraw docstring

new-lockclj

(new-lock)

A fresh monitor object. One per credential store, owned by the refresher built around it — never share a lock across providers.

A fresh monitor object. One per credential store, owned by the refresher
built around it — never share a lock across providers.
sourceraw docstring

refresherclj

(refresher reuse refresh!)

Build a single-flight refresh fn that OWNS a fresh lock. refresh! is as in single-flight!; reuse takes ONE arg — the REJECTED token (the access token the server just 401'd, or nil) — and returns the reusable token map or nil. For stores that aren't a plain rotating file — e.g. an in-memory cache (GitHub Copilot).

The returned fn is 0/1-arity: call it WITH the rejected token on 401 recovery so reuse never hands back the very token that was just rejected; the 0-arity form (nil rejected) keeps plain freshness reuse.

Build a single-flight refresh fn that OWNS a fresh lock. `refresh!` is as in
`single-flight!`; `reuse` takes ONE arg — the REJECTED token (the access
token the server just 401'd, or nil) — and returns the reusable token map or
nil. For stores that aren't a plain rotating file — e.g. an in-memory cache
(GitHub Copilot).

The returned fn is 0/1-arity: call it WITH the rejected token on 401 recovery
so `reuse` never hands back the very token that was just rejected; the 0-arity
form (nil rejected) keeps plain freshness reuse.
sourceraw docstring

single-flight!clj

(single-flight! lock reuse refresh!)

Serialize refresh! on lock. Once the lock is held, call reuse first: if it returns a non-nil result, another caller already refreshed while this thread waited for the lock, so return that WITHOUT a second exchange; otherwise run refresh! and return its result. reuse and refresh! are 0-arg fns; reuse returns the reusable token map or nil.

Low-level core — prefer make-file-refresher / refresher.

Serialize `refresh!` on `lock`. Once the lock is held, call `reuse`
first: if it returns a non-nil result, another caller already refreshed
while this thread waited for the lock, so return that WITHOUT a second
exchange; otherwise run `refresh!` and return its result. `reuse` and
`refresh!` are 0-arg fns; `reuse` returns the reusable token map or nil.

Low-level core — prefer `make-file-refresher` / `refresher`.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close