Skip to content

feat(postgrest): configurable auto-retry and request timeout#1560

Merged
spydon merged 9 commits into
mainfrom
worktree-SDK-1270
Jul 13, 2026
Merged

feat(postgrest): configurable auto-retry and request timeout#1560
spydon merged 9 commits into
mainfrom
worktree-SDK-1270

Conversation

@spydon

@spydon spydon commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes the PostgREST client's automatic retry behavior configurable and adds an optional per-request timeout that actually cancels a stalled attempt. Previously the retry count and the retryable conditions were hard-coded, and there was no way to bound the duration of a request. Rebased on top of the request abort feature (#1368), whose AbortableRequest support is what makes a real timeout possible.

All defaults preserve the current behavior, so the change is non-breaking.

Configurable auto-retry

New optional configuration on PostgrestClient (and per request via PostgrestBuilder.retry):

  • retryEnabled (default true): whether retries are performed for retryable GET and HEAD requests.
  • retryCount (default 3): number of retry attempts for a retryable request. A negative value throws an ArgumentError.
  • retryableStatusCodes (default {503, 520}): HTTP status codes that trigger a retry. The set is copied defensively so later mutation of the caller's set does not change retry behavior.
  • PostgrestBuilder.retry({bool enabled = true, int? count, Duration? requestTimeout}): the per-request override now also accepts a count and a requestTimeout. A null requestTimeout keeps the client-level timeout.

The default retryable status codes are exposed as PostgrestClient.defaultRetryableStatusCodes, since the client is where retries are configured.

Request timeout

requestTimeout is available on PostgrestClient and PostgrestClientOptions, and can be overridden per request through PostgrestBuilder.retry. It bounds how long a single request attempt may take and is built on top of the abort mechanism from #1368, so it actually cancels a stalled attempt through AbortableRequest rather than leaving it running the way Future.timeout would. When null (the default) no timeout is applied.

It is a per-attempt timeout, not a whole-operation deadline: a timed-out attempt is retried like any other transient failure, and a TimeoutException is thrown only once the retries are exhausted. A caller-provided PostgrestBuilder.abortSignal, by contrast, cancels the request outright and stops retrying immediately with a RequestAbortedException. Internally both share the same abort trigger per attempt; the source that fired is tracked so a timeout surfaces as TimeoutException while a manual abort keeps RequestAbortedException.

If you need a hard deadline across all retries, wrap the returned future with .timeout(...) (PostgrestBuilder implements Future).

Configuration through SupabaseClient

retryEnabled, retryCount, retryableStatusCodes, and requestTimeout are surfaced through PostgrestClientOptions so they can be set on the top-level SupabaseClient.

Builder internals

The changes above required threading more state (retry settings, requestTimeout, abortSignal) through the immutable builder chain, so the copy internals were cleaned up along the way. These are internal-only and non-breaking:

  • Retry settings are bundled into a single immutable _RetryConfig value instead of separate fields, which also fixes ResponsePostgrestBuilder silently dropping the retry count and status codes on count() chains.
  • The non-generic request state (url, headers, schema, method, body, transport handles, count, retry, requestTimeout, abortSignal) is held in a single immutable _RequestConfig value with a copyWith. The typed builders now share and rewrap one config instance rather than re-listing every field, so withConverter, the transform and query chains, and the wrapper builders no longer drop or duplicate request state.

Compliance matrix

  • database.configuration.auto_retry becomes implemented.
  • database.configuration.request_timeout becomes implemented, now that request cancellation exists to back it.

Tests

Extended packages/postgrest/test/retry_test.dart with cases for configurable retry count, per-request count override, retryCount: 0, negative retryCount validation, and custom retryable status codes. For the timeout: a timed-out attempt is retried rather than hard-stopped, retries recover once an attempt completes within the timeout, a request that finishes in time is not timed out, a per-request retry(requestTimeout:) override times out, and a manual abortSignal stops retrying immediately.

SDK-1270

@spydon spydon requested a review from a team as a code owner July 8, 2026 12:59
@github-actions github-actions Bot added the postgrest This issue or pull request is related to postgrest label Jul 8, 2026
@spydon spydon requested a review from Copilot July 8, 2026 13:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the postgrest Dart client to make its automatic retry behavior configurable (retry count + retryable status codes) and adds an optional per-request HTTP request timeout, surfacing the configuration both on PostgrestClient and through PostgrestClientOptions (so it can be configured via SupabaseClient).

Changes:

  • Added configurable retryCount, retryableStatusCodes, and requestTimeout to PostgrestClient and threaded these through the various builder layers.
  • Extended PostgrestBuilder.retry(...) to support per-request retry count overrides (count) in addition to enabling/disabling retries.
  • Updated tests and compliance matrix entries to cover/reflect the new configuration and timeout behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
sdk-compliance.yaml Marks database auto-retry and request timeout as implemented and lists related symbols.
packages/supabase/lib/src/supabase_client.dart Passes PostgREST retry/timeout options from SupabaseClient into PostgrestClient.
packages/supabase/lib/src/supabase_client_options.dart Adds PostgREST retry/timeout configuration to PostgrestClientOptions.
packages/postgrest/test/retry_test.dart Adds test coverage for configurable retry count, custom status codes, and request timeout behavior.
packages/postgrest/lib/src/raw_postgrest_builder.dart Propagates retry/timeout configuration through raw builder wrapping/copying.
packages/postgrest/lib/src/postgrest.dart Adds retry/timeout fields to PostgrestClient and forwards them into builders.
packages/postgrest/lib/src/postgrest_transform_builder.dart Updates .retry(...) override to support optional count.
packages/postgrest/lib/src/postgrest_rpc_builder.dart Threads retry/timeout configuration into RPC builder construction.
packages/postgrest/lib/src/postgrest_query_builder.dart Threads retry/timeout config and updates .retry(...) to allow per-request count override.
packages/postgrest/lib/src/postgrest_filter_builder.dart Updates .retry(...) override to support optional count.
packages/postgrest/lib/src/postgrest_builder.dart Implements configurable retry count/status codes and applies per-request timeout logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/postgrest/lib/src/postgrest_builder.dart Outdated
Comment thread packages/postgrest/lib/src/postgrest_builder.dart Outdated
@spydon spydon changed the title feat(postgrest): configurable auto-retry and request timeout feat(postgrest): configurable auto-retry Jul 10, 2026
@Vinzent03

Copy link
Copy Markdown
Collaborator

Actually with http 1.5.0 aborting requests is supported so the timeout option might be reconsidered. https://pub.dev/packages/http#aborting-requests See #1368 for manual abort.

@spydon

spydon commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Actually with http 1.5.0 aborting requests is supported so the timeout option might be reconsidered. https://pub.dev/packages/http#aborting-requests See #1368 for manual abort.

Oooh they finally added that, I know it has been an open issue for years. 😅
Opened #1577 to deal with that.

Edit: forgot that you already have #1368 open, I'll close 1577 and let 1368 deal with it then

@spydon spydon force-pushed the worktree-SDK-1270 branch from b854cc0 to 756f0f0 Compare July 10, 2026 13:24
@spydon spydon requested a review from Vinzent03 July 10, 2026 14:16
@spydon spydon force-pushed the worktree-SDK-1270 branch from 756f0f0 to 1099735 Compare July 10, 2026 14:17
@spydon spydon changed the title feat(postgrest): configurable auto-retry feat(postgrest): configurable auto-retry and request timeout Jul 10, 2026

@Vinzent03 Vinzent03 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of extending the http RequestAbortedException exception with two distinct subclasses for either manual abort and and timeout abort? This would allow the user to easier differentiate whether the abort came from a timeout or the user clicked a button in their ui. Surely, this could also be implemented by the user manually, but including it in the library should be easier for the user.

Comment thread packages/postgrest/lib/src/response_postgrest_builder.dart Outdated
Comment thread packages/supabase/lib/src/supabase_client_options.dart Outdated
@spydon spydon force-pushed the worktree-SDK-1270 branch 2 times, most recently from b06a401 to da7b5a6 Compare July 12, 2026 09:49
…eout

Makes the automatic retry behavior configurable through retryEnabled,
retryCount and retryableStatusCodes on PostgrestClient and
PostgrestClientOptions, and per request via PostgrestBuilder.retry (which
now also accepts a count). All defaults preserve the previous behavior.

Rebased on top of the request abort feature (#1368) and now builds the
request timeout on top of it. A configured requestTimeout aborts the
in-flight request through AbortableRequest and stops any pending retries
instead of merely abandoning the future like Future.timeout would. The
retry loop rethrows RequestAbortedException so an abort always stops
retries, and requestTimeout and abortSignal are threaded through the
reconstructing builders so they survive withConverter and count chains.
@spydon spydon force-pushed the worktree-SDK-1270 branch from da7b5a6 to b03bc95 Compare July 12, 2026 10:31
@spydon

spydon commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

What do you think of extending the http RequestAbortedException exception with two distinct subclasses for either manual abort and and timeout abort?

I don't think we'd need to create new exceptions for that but it would indeed be good for the user to be able to separate them, I think a better way is if we just synthesize a TimeoutException when it's a timeout since the user shouldn't need to care that the request was aborted as an artifact of the timeout.

When investigating this I also noticed that the timeout was wrapping the retries, so timed out requests didn't retry, which obviously is wrong. Fixed now.

Comment thread packages/postgrest/lib/src/postgrest_filter_builder.dart Outdated
@Vinzent03

Copy link
Copy Markdown
Collaborator

I like just using the existing TimeoutException! Besides my last comment here about requestTimeout this LGTM.

@spydon spydon enabled auto-merge (squash) July 13, 2026 21:41
@spydon spydon merged commit 59d4b3a into main Jul 13, 2026
31 checks passed
@spydon spydon deleted the worktree-SDK-1270 branch July 13, 2026 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

postgrest This issue or pull request is related to postgrest

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants