From a10bf94521ce5f4288be353470b1400b04a25f89 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 15 Sep 2026 07:50:26 -0300 Subject: [PATCH] docs(capability-matrix): add specs for database and storage auto_retry PostgREST's rule is fixed (GET/HEAD, 503/520, 4 attempts, on/off switch only); Storage's is configurable. Both share full-jitter capped backoff, Retry-After honoured up to the cap, stop on cancellation, and no replay of one-shot bodies or of errors thrown by caller-supplied code. Refs SDK-1791, supabase/supabase-swift#1341. Co-Authored-By: Claude --- .../database/configuration/auto_retry.md | 46 +++++++++++++++++++ .../specs/storage/configuration/auto_retry.md | 34 ++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 packages/capability-matrix/specs/database/configuration/auto_retry.md create mode 100644 packages/capability-matrix/specs/storage/configuration/auto_retry.md diff --git a/packages/capability-matrix/specs/database/configuration/auto_retry.md b/packages/capability-matrix/specs/database/configuration/auto_retry.md new file mode 100644 index 0000000..679d359 --- /dev/null +++ b/packages/capability-matrix/specs/database/configuration/auto_retry.md @@ -0,0 +1,46 @@ +# Automatic Retry with Backoff + +Automatically retry idempotent database requests that failed transiently, with capped exponential backoff, switchable per client and overridable per request. + +## API + +There is no dedicated endpoint. The SDK re-sends the same PostgREST request, adding an `X-Retry-Count: n` header on the n-th retry so a server or proxy can tell a replay from a first attempt. + +## Behavior + +The rule is fixed and mirrors postgrest-js. A request is retryable when both hold: + +- Its method is `GET` or `HEAD`. Nothing else is ever replayed: a write can duplicate its effect, and PostgREST offers no idempotency key. +- The attempt failed transiently: the transport reported a network failure, or the server answered `503` or `520`. Both mean the schema cache or the edge in front of the database is reloading. Every other status, including `500`, is a real answer from the database and is never retried. + +The caller cannot widen or narrow the rule. The only knobs are a per-client switch (on by default) and a per-request override of that switch. + +Attempts are bounded: 4 in total, including the first. + +The wait before the n-th retry is a random duration in `0...min(30s, 1s · 2^(n-1))` ("full jitter", per the AWS Architecture Blog's *Exponential Backoff And Jitter*). Jitter is mandatory: without it every client that lost the same connection retries at the same instant. + +When a retryable response carries a `Retry-After` header ([RFC 9110 §10.2.3](https://www.rfc-editor.org/rfc/rfc9110.html#section-10.2.3), delta-seconds or HTTP-date), the SDK waits that long instead of the jittered backoff, capped at 30 s. A `Retry-After` the SDK cannot parse falls back to the jittered backoff. + +Cancellation ends the loop at once: a request cancelled during an attempt or during the wait surfaces the cancellation, never a retry. + +Only the network send is retried. A response that decodes badly is never retried. An error thrown by caller-supplied code that runs inside the request (a custom transport or middleware, an access-token callback) propagates untouched and is never retried. + +When attempts run out, the last failure (response or transport error) is the request's outcome. + +## Prerequisites + +None. Retries are on by default. + +## Errors + +The final error is whatever the last attempt produced; retrying adds no error of its own. + +## Notes + +- Delays must come from an injectable clock so tests can drive them without sleeping. +- Other targets (Storage, Functions, Auth) reuse the same retry implementation with their own rules; only PostgREST's rule is fixed and not configurable. + +## Related + +- [Per-Request Access Token](access_token.md) - renewal after a `401` may share this retry limit +- [Automatic Retry](../../storage/configuration/auto_retry.md) - the Storage counterpart, which is configurable diff --git a/packages/capability-matrix/specs/storage/configuration/auto_retry.md b/packages/capability-matrix/specs/storage/configuration/auto_retry.md new file mode 100644 index 0000000..5dc48a1 --- /dev/null +++ b/packages/capability-matrix/specs/storage/configuration/auto_retry.md @@ -0,0 +1,34 @@ +# Automatic Retry + +Automatically retry a storage request that failed transiently, with control over how many attempts are made before the error surfaces to the caller. + +## API + +There is no dedicated endpoint. The SDK re-sends the same Storage API request, adding an `X-Retry-Count: n` header on the n-th retry. + +## Behavior + +A request is retryable when both hold: + +- Its method is safe to replay: `GET`, `HEAD`, `OPTIONS`, `PUT` or `DELETE`. A request that carries an `Idempotency-Key` header is retryable whatever its method. A `POST` without that header is never retried, since replaying it can duplicate a write. +- The attempt failed transiently: the transport reported a network failure, or the server answered with one of `408`, `429`, `500`, `502`, `503`, `504`, or Cloudflare's `520`–`524` and `530`. + +Uploads deserve one extra rule. An upload body is replayed only when it can be read again from the start (an in-memory buffer or a file on disk). A body streamed from a one-shot source is never retried, and the first failure is the request's outcome. + +Attempts are bounded. The default is 3 attempts in total, including the first. The caller configures the count per client; setting it to 1 disables retries. The caller may also tune the delays and the retryable statuses and methods. + +The wait before the n-th retry is a random duration in `0...min(maxDelay, baseDelay · 2^(n-1))` ("full jitter"). Defaults are a 500 ms base and a 20 s cap. When a retryable response carries a `Retry-After` header ([RFC 9110 §10.2.3](https://www.rfc-editor.org/rfc/rfc9110.html#section-10.2.3), delta-seconds or HTTP-date), the SDK waits that long instead, capped at `maxDelay`. + +Cancellation ends the loop at once. Only the network send is retried; an error thrown by caller-supplied code that runs inside the request propagates untouched. When attempts run out, the last failure is the request's outcome. + +## Prerequisites + +None. Retries are on by default. + +## Errors + +The final error is whatever the last attempt produced; retrying adds no error of its own. + +## Related + +- [Automatic Retry with Backoff](../../database/configuration/auto_retry.md) - the PostgREST counterpart, whose rule is fixed