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
While auditing dependencies in the monorepo we found several pieces of code that are duplicated or near-duplicated across packages. Some duplication was even introduced by the recent dependency-removal work (e.g. the in-house retry helper now lives identically in two packages). This issue tracks consolidating that shared code into a single internal (but published) supabase_common package.
Create a supabase_common package and move genuinely shared code into it, starting with the lowest-risk, highest-confidence items and deferring anything that would break public API.
Tier 1 — identical / near-identical, lift-and-shift (no public-API risk)
retry / RetryOptions — byte-for-byte identical in packages/gotrue/lib/src/retry.dart and packages/storage_client/lib/src/retry.dart. Move to one shared copy.
ToSnakeCase extension on Enum — byte-for-byte identical in packages/gotrue/lib/src/types/auth_response.dart (~line 106) and packages/storage_client/lib/src/types.dart (~line 469).
Platform detection — platform_io.dart / platform_stub.dart plus the conditional-import trick are near-identical between packages/supabase/lib/src/ and packages/supabase_flutter/lib/src/ (flutter only adds isRunningInFlutterTest). Move with the extra flag preserved.
Replay subject — _ReplaySubject in packages/gotrue/lib/src/gotrue_client.dart (~line 1708) and _BehaviorSubject in packages/supabase/lib/src/supabase_stream_builder.dart (~line 455) reimplement the same broadcast + latest-event-replay behavior. Unify into one ReplaySubject supporting sync, onListen/onCancel, and addStream.
Tier 2 — same shape, unify with a small parameter (low risk, some reconciliation)
X-Client-Info header builder — every data package builds {'X-Client-Info': '<lib>-dart/$version'} (functions_client, gotrue, postgrest, realtime_client, storage_client), and supabase / supabase_flutter share a richer platform-aware variant. Provide buildClientInfoHeader(clientName, version, {platformInfo}).
FetchOptions — same two fields (headers, noResolveJson) in packages/gotrue/lib/src/types/fetch_options.dart and packages/storage_client/lib/src/types.dart, differing only in nullability/constructor style.
base64url / JWT / PKCE / uuid helpers — currently only in gotrue (base64url.dart, helper.dart, types/jwt.dart), so not cross-package duplication, but generic utilities worth centralizing. Moving them also removes an intra-gotrue duplication (jwt.dart ~line 294 re-implements Base64Url.decodeToBytes).
Tier 3 — breaking, belongs in v3
These are "same concept, different shape" and touch public API, so they need careful reconciliation and are tracked under the v3 umbrella (#1278).
Shared exception base — AuthException, StorageException, PostgrestException share a message + status + toString shape and could get a common SupabaseException base. FunctionException is the outlier (status: int, details, reasonPhrase), see Make FunctionException a sealed class for v3 #1550.
HTTP fetch wrappers — GotrueFetch, storage Fetch, functions' inlined invoke, postgrest's _parseResponse all follow "check 2xx → decode JSON → else parse typed error" but each is bound to its own exception type. Shareable in pieces (see Tier 2), not as one wrapper.
RetryTimer (packages/realtime_client/lib/src/retry_timer.dart) — same exponential-backoff concept as retry but timer/callback-driven for websocket reconnect. Only the delay calculation could be shared.
Explicitly out of scope
version.dart (const version = '...') is auto-generated per package by release tooling and must stay local.
supabase_common must be a real published package (client packages are published independently and cannot depend on an unpublished local-only package). That adds a new versioned artifact for release tooling and a new dependency edge for each package that uses it.
Total shared code is currently modest (~250-300 lines for Tier 1). Weigh the packaging/publishing overhead against the duplication before committing to the full scope.
Suggested approach
First PR: create supabase_common and move Tier 1 only (all identical or near-identical, no API changes).
Background
While auditing dependencies in the monorepo we found several pieces of code that are duplicated or near-duplicated across packages. Some duplication was even introduced by the recent dependency-removal work (e.g. the in-house
retryhelper now lives identically in two packages). This issue tracks consolidating that shared code into a single internal (but published)supabase_commonpackage.Packages under
packages/:functions_client,gotrue,postgrest,realtime_client,storage_client,supabase,supabase_flutter,yet_another_json_isolate.Linear: SDK-1281
Goal
Create a
supabase_commonpackage and move genuinely shared code into it, starting with the lowest-risk, highest-confidence items and deferring anything that would break public API.Tier 1 — identical / near-identical, lift-and-shift (no public-API risk)
retry/RetryOptions— byte-for-byte identical inpackages/gotrue/lib/src/retry.dartandpackages/storage_client/lib/src/retry.dart. Move to one shared copy.ToSnakeCaseextension onEnum— byte-for-byte identical inpackages/gotrue/lib/src/types/auth_response.dart(~line 106) andpackages/storage_client/lib/src/types.dart(~line 469).platform_io.dart/platform_stub.dartplus the conditional-import trick are near-identical betweenpackages/supabase/lib/src/andpackages/supabase_flutter/lib/src/(flutter only addsisRunningInFlutterTest). Move with the extra flag preserved._ReplaySubjectinpackages/gotrue/lib/src/gotrue_client.dart(~line 1708) and_BehaviorSubjectinpackages/supabase/lib/src/supabase_stream_builder.dart(~line 455) reimplement the same broadcast + latest-event-replay behavior. Unify into oneReplaySubjectsupportingsync,onListen/onCancel, andaddStream.Tier 2 — same shape, unify with a small parameter (low risk, some reconciliation)
X-Client-Infoheader builder — every data package builds{'X-Client-Info': '<lib>-dart/$version'}(functions_client,gotrue,postgrest,realtime_client,storage_client), andsupabase/supabase_fluttershare a richer platform-aware variant. ProvidebuildClientInfoHeader(clientName, version, {platformInfo}).isSuccessStatusCode(code) => code >= 200 && code <= 299— duplicated/inlined ingotrue/fetch.dart,storage_client/fetch.dart,functions_client/functions_client.dart,postgrest/postgrest_builder.dart.FetchOptions— same two fields (headers,noResolveJson) inpackages/gotrue/lib/src/types/fetch_options.dartandpackages/storage_client/lib/src/types.dart, differing only in nullability/constructor style.gotrue(base64url.dart,helper.dart,types/jwt.dart), so not cross-package duplication, but generic utilities worth centralizing. Moving them also removes an intra-gotrue duplication (jwt.dart~line 294 re-implementsBase64Url.decodeToBytes).Tier 3 — breaking, belongs in v3
These are "same concept, different shape" and touch public API, so they need careful reconciliation and are tracked under the v3 umbrella (#1278).
AuthException,StorageException,PostgrestExceptionshare amessage+ status +toStringshape and could get a commonSupabaseExceptionbase.FunctionExceptionis the outlier (status: int,details,reasonPhrase), see Make FunctionException a sealed class for v3 #1550.GotrueFetch, storageFetch, functions' inlinedinvoke, postgrest's_parseResponseall follow "check 2xx → decode JSON → else parse typed error" but each is bound to its own exception type. Shareable in pieces (see Tier 2), not as one wrapper.RetryTimer(packages/realtime_client/lib/src/retry_timer.dart) — same exponential-backoff concept asretrybut timer/callback-driven for websocket reconnect. Only the delay calculation could be shared.Explicitly out of scope
version.dart(const version = '...') is auto-generated per package by release tooling and must stay local.RealtimeProtocolVersion,AuthChangeEvent,OtpType, etc.).Logger('supabase.<area>')convention — no code to share; asupabaseLogger(area)factory is optional polish at most (see Look into setting up logging in a proper way #1533).Considerations
supabase_commonmust be a real published package (client packages are published independently and cannot depend on an unpublished local-only package). That adds a new versioned artifact for release tooling and a new dependency edge for each package that uses it.Suggested approach
supabase_commonand move Tier 1 only (all identical or near-identical, no API changes).Related
The duplication was surfaced during dependency cleanup:
supabase(introduced the second replay-subject impl)http_parserfromstorage_clientretryin favor of the in-house helper (introduced the identicalretry.dartin two packages)