refactor(core,sdks): unify Retry into a single shared Context.Service#225
refactor(core,sdks): unify Retry into a single shared Context.Service#225sam-goodwin wants to merge 4 commits into
Conversation
The shared core `makeAPI` only ran a hard-coded throttling-only retry,
so Cloudflare's exported `Retry` Context.Service (`policy`, `none`,
`throttling`, `transient`) was effectively dead code. Callers had to
wrap individual operations with `Effect.retry({ ... })` instead of
installing one blanket policy at the layer level (the pattern AWS has
in `packages/aws/src/client/api.ts`).
Add an optional `retry?: Context.Key<any, RetryPolicy>` field to
`ClientConfig`. When set, every operation reads the policy via
`Effect.serviceOption(retry)` with `Retry.makeDefault` as the fallback
and a per-call `lastError` Ref, mirroring AWS's `outerFn`. When unset,
the legacy throttling-only schedule still runs, so other consumers
(turso, planetscale, neon, stripe, kubernetes, etc.) are unchanged
until they opt in.
Wire `retry: Retry` into Cloudflare's `makeAPI` so callers can do
`myEffect.pipe(Cloudflare.Retry.transient)` once and have every
Cloudflare API call below it transparently retry transient errors.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Install the packages built from this commit: @distilled.cloud/core bun add https://pkg.distilled.cloud/core/f9eb54f@distilled.cloud/aws bun add https://pkg.distilled.cloud/aws/f9eb54f@distilled.cloud/cloudflare bun add https://pkg.distilled.cloud/cloudflare/f9eb54f@distilled.cloud/gcp bun add https://pkg.distilled.cloud/gcp/f9eb54f@distilled.cloud/neon bun add https://pkg.distilled.cloud/neon/f9eb54f@distilled.cloud/planetscale bun add https://pkg.distilled.cloud/planetscale/f9eb54f@distilled.cloud/prisma-postgres bun add https://pkg.distilled.cloud/prisma-postgres/f9eb54f@distilled.cloud/stripe bun add https://pkg.distilled.cloud/stripe/f9eb54f@distilled.cloud/supabase bun add https://pkg.distilled.cloud/supabase/f9eb54f@distilled.cloud/posthog bun add https://pkg.distilled.cloud/posthog/f9eb54f@distilled.cloud/axiom bun add https://pkg.distilled.cloud/axiom/f9eb54f@distilled.cloud/azure bun add https://pkg.distilled.cloud/azure/f9eb54f@distilled.cloud/kubernetes bun add https://pkg.distilled.cloud/kubernetes/f9eb54f@distilled.cloud/coinbase bun add https://pkg.distilled.cloud/coinbase/f9eb54f@distilled.cloud/mongodb-atlas bun add https://pkg.distilled.cloud/mongodb-atlas/f9eb54f@distilled.cloud/fly-io bun add https://pkg.distilled.cloud/fly-io/f9eb54f@distilled.cloud/turso bun add https://pkg.distilled.cloud/turso/f9eb54f@distilled.cloud/typesense bun add https://pkg.distilled.cloud/typesense/f9eb54f@distilled.cloud/workos bun add https://pkg.distilled.cloud/workos/f9eb54f@distilled.cloud/expo-eas bun add https://pkg.distilled.cloud/expo-eas/f9eb54f |
Per-SDK Retry tags were never actually consumed: each
`packages/<sdk>/src/retry.ts` declared its own `Retry extends
Context.Service<...>("FooRetry")`, but the shared `makeAPI` in
`@distilled.cloud/core/client` had no way to read from N different tags
and so just ran a hard-coded throttling-only schedule. The previous
commit on this branch threaded a per-SDK tag through `makeAPI` config —
that worked but it left every other SDK on the legacy throttling-only
path until they migrated.
Drop the per-SDK indirection entirely:
- `packages/core/src/retry.ts` now exports a single shared `Retry`
Context.Service plus `policy`, `none`, `throttling`, `transient`
helpers bound to it. `makeRetryService` is kept as a deprecated
no-op shim for any external callers.
- `packages/core/src/client.ts`'s `makeAPI` always reads the shared
`Retry` with `Effect.serviceOption`, falling back to
`Retry.makeDefault` (transient/throttling/server with capped
exponential backoff + jitter, 5 attempts). The `config.retry` field
introduced in the previous commit and the legacy hard-coded
throttling-only branch are removed. This mirrors AWS's pattern in
`packages/aws/src/client/api.ts`.
- All 17 per-SDK `retry.ts` files (cloudflare, turso, planetscale,
neon, stripe, kubernetes, axiom, posthog, gcp, coinbase, supabase,
expo-eas, fly-io, mongodb-atlas, typesense, workos, prisma-postgres,
azure) become thin re-exports of the shared core module so existing
call sites (`Cloudflare.Retry.transient(eff)`,
`Layer.succeed(Turso.Retry.Retry, customPolicy)`, etc.) keep working
but now actually take effect.
AWS continues to use its own client and its own `Retry` tag —
unchanged in this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>
…client Reverts the shared-`Retry` approach from the previous commit. Each SDK keeps its own `Retry` Context.Service tag (so policies don't bleed across SDKs — `Cloudflare.Retry.transient` only retries Cloudflare calls, not turso underneath), but each SDK's `client.ts` now wires its own tag into `makeAPI` automatically. End users just write `myEffect.pipe(Cloudflare.Retry.transient)` — no per-SDK config to remember. - `packages/core/src/client.ts`: `ClientConfig.retry` is now required. `makeAPI` always reads the configured tag with `Effect.serviceOption` and falls back to `Retry.makeDefault`. Removed the legacy throttling-only branch. - `packages/core/src/retry.ts`: shared `Retry` tag/`policy`/`none`/ `throttling`/`transient` removed; `makeRetryService` restored as the primary API for SDKs. - 18 per-SDK `retry.ts` files: each declares its own `Retry` tag plus matching `policy`, `none`, `throttling`, `transient` helpers (added `throttling`/`transient` everywhere for parity — previously only cloudflare had them). - 18 per-SDK `client.ts` / `client/api.ts` files: each imports its local `Retry` and passes it as `retry: Retry as any` into the `makeAPI` config. AWS continues to use its own client and `Retry` tag, unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Make sure create-sdk/create-sdk-full are updated so this is automatic for future sdks (just tell Claude it can update the scripts). The tests failing rn are fine, those are broken on main. Just make sure it doesn't get worse. Most important one is supabase since its rate limit stuff is super noticable if you break it. If that test is fine the rest should be. |
Summary
Per-SDK
RetryContext.Service tags were never actually consumed: eachpackages/<sdk>/src/retry.tsdeclared its ownRetryclass but the sharedmakeAPIin@distilled.cloud/core/clienthad no way to look up N different tags and just ran a hard-coded throttling-only schedule. SoCloudflare.Retry.transient(eff),Turso.Retry.policy(...), etc. all compiled and provided a layer — but nothing read it.This PR keeps the per-SDK tag (so policies don't bleed across SDKs —
Cloudflare.Retry.transientonly retries Cloudflare calls, not turso underneath) and just has each SDK's client wire its own tag intomakeAPIautomatically. End users just writemyEffect.pipe(Cloudflare.Retry.transient)— no per-SDK config to remember.This mirrors the AWS pattern in
packages/aws/src/client/api.ts(outerFnreadsRetrywithRetry.makeDefaultas fallback).Changes
packages/core/src/client.ts—ClientConfig.retryis now a requiredContext.Key<any, RetryPolicy>.makeAPIalways reads the configured tag withEffect.serviceOptionand falls back toRetry.makeDefault(transient/throttling/server with capped exponential backoff + jitter, 5 attempts). Removed the legacy hard-coded throttling-only branch.packages/core/src/retry.ts— keptmakeRetryServiceas the primary API for SDKs, removed the short-lived sharedRetrytag from the previous commit.retry.tsfiles (cloudflare, turso, planetscale, neon, stripe, kubernetes, axiom, posthog, gcp, coinbase, supabase, expo-eas, fly-io, mongodb-atlas, typesense, workos, prisma-postgres, azure) — each declares its ownRetrytag plus matchingpolicy,none,throttling,transienthelpers (addedthrottling/transienteverywhere for parity — previously only cloudflare had them).client.ts/client/api.tsfiles — each imports its localRetryand passes it asretry: Retry as anyinto themakeAPIconfig.AWS continues to use its own client and
Retrytag, unchanged.Caller-facing result
Every Cloudflare/Turso/etc. API call below transparently retries transient/throttling/server errors — no per-resource
Effect.retryblocks, and policies stay scoped to each SDK.Test plan
Retry.transientin their test harnesses now exercise the real wiring instead of being no-ops.