fix(storage): keep sortBy defaults when list() is given a partial sortBy - #2454
Conversation
@supabase/auth-js
@supabase/functions-js
@supabase/postgrest-js
@supabase/realtime-js
@supabase/storage-js
@supabase/supabase-js
commit: |
mandarini
left a comment
There was a problem hiding this comment.
Hi @i-anubhav-anand, thank you so much for contributing to Supabase!
Nice catch on the shallow spread dropping the nested sortBy default, and thank you for the Docker-free test that captures the request body directly. That makes it really easy to reason about.
One thing worth knowing: for the exact case in the description (sortBy: { column: 'updated_at' }), the storage server already defaults order to 'asc' when it is missing, so the result ordering is actually the same with or without this change for that example. Where your fix genuinely matters is the symmetric case: list('folder', { sortBy: { order: 'desc' } }). On master that sends sortBy with no column, and the server requires column, so it comes back as a 400. With your deep merge, column falls back to 'name' and the request succeeds. That is the real bug this fixes.
Could you add a test for that order-only case? It is the scenario that actually fails today, and it would make the value of the change obvious. It would also be great to lead the description with it.
Also, can you please rebase with master to fix your CI?
Thank you again for taking the time on this!!
list() shallow-merged DEFAULT_SEARCH_OPTIONS into the request body, so a partial
sortBy dropped the sibling default. With { sortBy: { order: 'desc' } } the request
omitted `column`, which the Storage server requires, returning a 400. Deep-merge
sortBy so a partial override keeps the other default (column or order), matching
how the top-level limit/offset defaults already behave.
cc0833a to
bf8a2da
Compare
|
Thanks for the careful review, @mandarini — and great catch on the server-side default. You're right that the order-only case (
Thanks again for taking the time! 🙏 |
…ortBy (#1490) ## What `storage.from(bucket).list()` rejected a partially-specified `SortBy` with a 400. `SortBy`'s constructor had no default for `column`/`order`, so `SortBy(column: 'updated_at')` left `order` as `null`. Since `SearchOptions.toMap()` always emits both keys and the storage client serializes with `json.encode` (which keeps nulls), the request body sent: ```json "sortBy": { "column": "updated_at", "order": null } ``` The Storage server requires both `column` and `order`, so it returned a 400. This mirrors supabase-js [#2454](supabase/supabase-js#2454) (commit `42b7bbc`), which deep-merges the partial `sortBy` onto the defaults. ## Fix Give `SortBy` constructor defaults so any partially-specified `SortBy` fills in the missing field, which is the idiomatic Dart equivalent of the JS deep-merge and works wherever `SortBy` is constructed: ```dart const SortBy({this.column = 'name', this.order = 'asc'}); ``` Behavior: - `SortBy(column: 'updated_at')` → `{column: 'updated_at', order: 'asc'}` - `SortBy(order: 'desc')` → `{column: 'name', order: 'desc'}` - `list()` with no options → `{column: 'name', order: 'asc'}` (unchanged) No breaking change for callers passing a complete `SortBy`. ## Tests Added a `list sortBy defaults` group in `client_test.dart` (using the in-memory `CustomHttpClient` to inspect the request body) covering partial column, partial order, no options, and complete sortBy. Closes SDK-1089
This PR updates `@supabase/supabase-js` to v2.110.0. **Source**: supabase-js-stable-release --- ## Release Notes ## v2.110.0 ## 2.110.0 (2026-06-30) ### 🚀 Features - **repo:** drop Node.js 20 support ([#2482](supabase/supabase-js#2482)) ### ❤️ Thank You - Katerina Skroumpelou @mandarini ## v2.109.0 ## 2.109.0 (2026-06-30) ### 🚀 Features - **auth:** add custom_claims_allowlist to custom providers admin API ([#2473](supabase/supabase-js#2473)) - **realtime:** add postgres_changes filter builder, new operators and select ([#2463](supabase/supabase-js#2463)) - **storage:** expose purgeCache for buckets and single objects ([#2429](supabase/supabase-js#2429)) ### 🩹 Fixes - **functions:** honor a caller's Content-Type override regardless of casing ([#2455](supabase/supabase-js#2455)) - **realtime:** pin @supabase/phoenix and browser test CDN deps ([#2457](supabase/supabase-js#2457)) - **realtime:** add replication connection system message option ([#2470](supabase/supabase-js#2470)) - **storage:** keep sortBy defaults when list() is given a partial sortBy ([#2454](supabase/supabase-js#2454)) ### ❤️ Thank You - Anubhav Anand @i-anubhav-anand - Cemal Kılıç @cemalkilic - Claude Opus 4.8 (1M context) - Filipe Cabaço @filipecabaco - Katerina Skroumpelou @mandarini - Lenny - Rodrigo Mansueli @mansueli ## v2.108.2 ## 2.108.2 (2026-06-15) ### 🩹 Fixes - **auth:** preserve valid session on refresh failure and cooldown repeat failures ([#2436](supabase/supabase-js#2436)) - **realtime:** clarify httpSend() 404 error and server migration note ([#2444](supabase/supabase-js#2444)) - **release:** pin Deno and bound JSR publish to survive stranded-task hangs ([#2439](supabase/supabase-js#2439)) - **release:** restore JSR publish flags and enable for beta ([#2440](supabase/supabase-js#2440)) ### ❤️ Thank You - Katerina Skroumpelou @mandarini ## v2.108.1 ## 2.108.1 (2026-06-09) ### 🩹 Fixes - **ci:** forward DOGFOOD_APP_CLIENT_ID to dogfood workflow ([#2434](supabase/supabase-js#2434)) - **postgrest:** then typing ([#2349](supabase/supabase-js#2349)) ### ❤️ Thank You - Katerina Skroumpelou @mandarini - Vaibhav @7ttp This PR was created automatically. Co-authored-by: supabase-workflow-trigger[bot] <266661614+supabase-workflow-trigger[bot]@users.noreply.github.com>
This PR updates @supabase/*-js libraries to version 2.110.1. **Source**: supabase-js-stable-release **Changes**: - Updated @supabase/supabase-js to 2.110.1 - Updated @supabase/auth-js to 2.110.1 - Updated @supabase/realtime-js to 2.110.1 - Updated @supabase/postgest-js to 2.110.1 - Refreshed pnpm-lock.yaml --- ## Release Notes ## v2.110.1 ## 2.110.1 (2026-07-07) ### 🩹 Fixes - **auth:** defer init-time notifications until initializePromise resolves ([#2498](supabase/supabase-js#2498)) - **realtime:** suppress disconnected status from onHeartbeat consumers ([#2496](supabase/supabase-js#2496)) ### ❤️ Thank You - Katerina Skroumpelou @mandarini ## v2.110.0 ## 2.110.0 (2026-06-30) ### 🚀 Features - **repo:** drop Node.js 20 support ([#2482](supabase/supabase-js#2482)) ### ❤️ Thank You - Katerina Skroumpelou @mandarini ## v2.109.0 ## 2.109.0 (2026-06-30) ### 🚀 Features - **auth:** add custom_claims_allowlist to custom providers admin API ([#2473](supabase/supabase-js#2473)) - **realtime:** add postgres_changes filter builder, new operators and select ([#2463](supabase/supabase-js#2463)) - **storage:** expose purgeCache for buckets and single objects ([#2429](supabase/supabase-js#2429)) ### 🩹 Fixes - **functions:** honor a caller's Content-Type override regardless of casing ([#2455](supabase/supabase-js#2455)) - **realtime:** pin @supabase/phoenix and browser test CDN deps ([#2457](supabase/supabase-js#2457)) - **realtime:** add replication connection system message option ([#2470](supabase/supabase-js#2470)) - **storage:** keep sortBy defaults when list() is given a partial sortBy ([#2454](supabase/supabase-js#2454)) ### ❤️ Thank You - Anubhav Anand @i-anubhav-anand - Cemal Kılıç @cemalkilic - Claude Opus 4.8 (1M context) - Filipe Cabaço @filipecabaco - Katerina Skroumpelou @mandarini - Lenny - Rodrigo Mansueli @mansueli This PR was created automatically. Co-authored-by: supabase-workflow-trigger[bot] <266661614+supabase-workflow-trigger[bot]@users.noreply.github.com>
🔍 Description
What changed?
StorageFileApi.list()shallow-spread its defaults into the request body, so a partialsortBydropped the sibling default. This deep-mergessortByso a partial override keeps the default for whichever key you don't set.Why was this change needed?
The case that actually fails today is order-only:
list('folder', { sortBy: { order: 'desc' } }). Onmasterthis sends asortBywith nocolumn, and the Storage server requirescolumn, so it returns a 400. With the deep merge,columnfalls back to'name'and the request succeeds. (The symmetric column-only case likewise keeps the defaultorder: 'asc'.)📸 Examples / Proof
test/list-sortby-defaults.test.ts(Docker-free, custom-fetch capture). Onmasterthe order-only and column-only cases fail; with this change all pass:{ sortBy: { order: 'desc' } }→{ column: 'name', order: 'desc' }(was{ order: 'desc' }→ server 400){ sortBy: { column: 'updated_at' } }→{ column: 'updated_at', order: 'asc' }{ column: 'name', order: 'asc' }🔄 Breaking changes
📋 Checklist
fix(storage): ...pnpm nx format📝 Additional notes
Rebased on
master.pnpm nx build storage-jspasses; the full storage integration suite (Docker) runs in CI.