Skip to content

fix(postgrest): add typed column inference for order() with referencedTable - #2445

Open
7vignesh wants to merge 3 commits into
supabase:masterfrom
7vignesh:fix/order-referenced-table-types
Open

fix(postgrest): add typed column inference for order() with referencedTable#2445
7vignesh wants to merge 3 commits into
supabase:masterfrom
7vignesh:fix/order-referenced-table-types

Conversation

@7vignesh

@7vignesh 7vignesh commented Jun 11, 2026

Copy link
Copy Markdown

Description

What changed?

Added new typed overloads to the order() method in PostgrestTransformBuilder that resolve the referenced table's column names from the Schema type when referencedTable (or deprecated foreignTable) is a known table/view.

Overload resolution now works in 3 tiers:

  1. No referencedTable → autocomplete shows columns from the parent table (unchanged)
  2. referencedTable is a known table/view → autocomplete shows columns from that table's Row, invalid columns produce a compile-time error
  3. referencedTable is an unknown string → falls through to column: string (unchanged, for dynamic/alias use cases)

The catch-all overload uses a conditional never type to prevent it from matching when the referenced table is known, forcing TypeScript to use the typed overload instead.

Why was this change needed?

When users passed referencedTable: 'messages' to .order(), TypeScript offered no autocomplete for the referenced table's columns and silently accepted invalid column names. This caused runtime errors that could have been caught at compile time.

This is the TypeScript typing portion of the issue — runtime behavior was already correct (clarified by maintainers in the issue thread).

Closes #971

📸 Screenshots/Examples

After: type error on invalid column for referenced table

image

After: valid column passes without error

image

Breaking changes

  • This PR contains no breaking changes

The catch-all string overload still exists for dynamic table names or unknown aliases, so existing code that passes arbitrary strings continues to compile.

Checklist

  • I have read the Contributing Guidelines
  • My PR title follows the conventional commit format: fix(postgrest): add typed column inference for order() with referencedTable
  • I have run pnpm nx format to ensure consistent code formatting
  • I have added tests for new functionality (type tests in test/index.test-d.ts)
  • I have updated documentation (if applicable)

Additional notes

  • This supersedes the stalled PR fix(postgrest): correct order() overload typing for referenced tables #2025 which attempted to fix this by deleting overloads (which would have broken base-case autocomplete, as noted by @avallete in review).
  • The fix imports TablesAndViews from the existing select-query-parser types no new type utilities were introduced.
  • Zero runtime changes. The implementation body of order() is untouched.
  • All 28 tstyche type test files pass. Build passes for both postgrest-js and downstream supabase-js.
  • Integration tests require Docker (PostgREST + PostgreSQL) which CI will handle. Since no runtime code changed, they will pass identically.

@7vignesh
7vignesh requested review from a team as code owners June 11, 2026 20:34
Copilot AI review requested due to automatic review settings June 11, 2026 20:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates Postgrest-js TypeScript typings so .order() can infer valid column names when ordering via a referencedTable (and the deprecated foreignTable) based on the schema.

Changes:

  • Add TablesAndViews-based overloads for .order() to type column names for referencedTable.
  • Add matching typed overloads for deprecated foreignTable.
  • Extend d.ts tests to cover typed ordering on referenced tables and expected type errors.

Reviewed changes

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

File Description
packages/core/postgrest-js/src/PostgrestTransformBuilder.ts Adds schema-aware .order() overloads for referencedTable/foreignTable to type referenced-table columns.
packages/core/postgrest-js/test/index.test-d.ts Adds type-level tests verifying typed referenced-table ordering and invalid-column failures.

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

Comment on lines +148 to +156
order<ReferencedTable extends string>(
column: string,
options?: { ascending?: boolean; nullsFirst?: boolean; referencedTable?: string }
options?: {
ascending?: boolean
nullsFirst?: boolean
referencedTable?: ReferencedTable extends keyof TablesAndViews<Schema>
? never
: ReferencedTable
}
Comment on lines +178 to +184
order<ReferencedTable extends string>(
column: string,
options?: { ascending?: boolean; nullsFirst?: boolean; foreignTable?: string }
options?: {
ascending?: boolean
nullsFirst?: boolean
foreignTable?: ReferencedTable extends keyof TablesAndViews<Schema> ? never : ReferencedTable
}
Comment on lines +141 to +147
order<
ReferencedTable extends string & keyof TablesAndViews<Schema>,
ColumnName extends string & keyof TablesAndViews<Schema>[ReferencedTable]['Row'],
>(
column: ColumnName,
options: { ascending?: boolean; nullsFirst?: boolean; referencedTable: ReferencedTable }
): this
Comment on lines +168 to +174
order<
ReferencedTable extends string & keyof TablesAndViews<Schema>,
ColumnName extends string & keyof TablesAndViews<Schema>[ReferencedTable]['Row'],
>(
column: ColumnName,
options: { ascending?: boolean; nullsFirst?: boolean; foreignTable: ReferencedTable }
): this
@mandarini

Copy link
Copy Markdown
Contributor

Hi @7vignesh, thank you so much for contributing to Supabase!

This is a nice fix for a real gap. Passing referencedTable to order() today gives no autocomplete or validation against that table's columns, so typos only surface at runtime. The three tier overload approach (no referencedTable, known table, unknown string fallback) is the right shape for the problem.

While testing it locally I ran into one case that regresses: if the column name comes from a variable instead of a string literal, and referencedTable is a known table, the call no longer compiles.

const sortColumn: string = getSortColumn()
postgrest
  .from('users')
  .select('messages(*)')
  .order(sortColumn, { referencedTable: 'messages', ascending: false })

This compiles fine on master today but fails on this branch with "No overload matches this call." Since the column is a plain string it doesn't match the new typed overload, and since messages is a known table the catch-all overload's referencedTable type collapses to never, so nothing matches. This is a fairly common pattern (sort column from a prop or user selection, table name hardcoded), so it would be worth adding a test for it and adjusting the overloads so a non-literal column falls back to the loose string behavior instead of erroring.

Would you be able to take a look at that case? Happy to help think through the overload signatures if that's useful.

Thank you again for contributing, it's contributions like yours that help make our tools better for everyone.

@mandarini mandarini left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, see my comment above!

7vignesh added 2 commits July 29, 2026 02:43
…dTable

When referencedTable or foreignTable is a known table/view,

the column param is now constrained to that table's Row.

Provides autocomplete and compile-time error checking.

Closes supabase#971
Use conditional type in order() overloads so that:
- Valid column literals get autocomplete and type checking
- Wide string types (from variables) are accepted
- Invalid column literals still produce compile-time errors

This fixes the regression where a non-literal column (e.g. from a prop
or user selection) with a known referencedTable would fail with
'No overload matches this call.'

Addresses reviewer feedback on supabase#2445.
@7vignesh
7vignesh force-pushed the fix/order-referenced-table-types branch from 65f8ce8 to d8dccf0 Compare July 28, 2026 21:19
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e3f6476-048a-46ba-8834-457260ce0bfb

📥 Commits

Reviewing files that changed from the base of the PR and between d8dccf0 and 76b3675.

📒 Files selected for processing (2)
  • packages/core/postgrest-js/src/PostgrestTransformBuilder.ts
  • packages/core/postgrest-js/test/index.test-d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/postgrest-js/test/index.test-d.ts

📝 Walkthrough

Summary by CodeRabbit

  • Improvements
    • Enhanced TypeScript typing for order() when sorting by columns from related/embedded tables via referencedTable.
    • Stronger column autocomplete and more precise compile-time validation for invalid referenced columns.
    • Continued support for the deprecated foreignTable option with equivalent improved type checking.
    • Dynamic sort-column strings still work when paired with a known referenced/foreign table.
  • Tests
    • Expanded type-safety coverage for referenced/aliased referenced ordering scenarios.

Walkthrough

PostgrestTransformBuilder now imports TablesAndViews and uses schema-aware generic overloads for .order(). Columns are constrained to the row keys of the selected referencedTable or deprecated foreignTable, while dynamic string columns remain supported. The declaration tests cover valid and invalid related-table columns, both option names, dynamic sort columns, and alias fallback. A conditional type test helper was reformatted without changing its behavior.

Assessment against linked issues

Objective Addressed Explanation
Order by columns on a foreign table with TypeScript autocomplete and validation [#971]

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@7vignesh

Copy link
Copy Markdown
Author

Hi @mandarini, thanks for the detailed feedback! I've addressed the regression case you identified.

The fix uses a conditional type in the order() overload so that:

  • Valid column literals → autocomplete + compile-time validation ✓
  • Wide string type (from variables/props) → accepted without error ✓
  • Invalid column literals → still produce compile-time errors ✓
// This now compiles correctly:
const sortColumn: string = getSortColumn()
postgrest
  .from('users')
  .select('messages(*)')
  .order(sortColumn, { referencedTable: 'messages', ascending: false })

I've also added test cases for both referencedTable and the deprecated foreignTable with dynamic column variables. All 28 tstyche type test files pass, and the build is green.

Branch has been rebased on latest master as well.

@mandarini mandarini left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for turning around the dynamic-column fix so quickly!! So, while digging a bit further I found one more edge case worth addressing before we merge. I checked how PostgREST resolves the <relation>.order= param, and when an embed is aliased (e.g. .select('messages:channels(*)')), PostgREST matches on the alias, not the real table name. Right now TablesAndViews<Schema> only knows real table/view names, so if an alias happens to coincide with an unrelated real table in the schema, the typed overload will validate the column against that unrelated table's Row instead of the table that's actually embedded. That could either reject a valid column or silently accept one that's wrong at runtime.

Could you add a test that exercises an aliased referencedTable (something like .select('archived:messages(*)').order('col', { referencedTable: 'archived' })) so we can see how it behaves today, and add a short note in the JSDoc that aliased referenced tables fall back to unchecked string (or aren't safe if the alias collides with a real table name)? Given that fully resolving aliases would need more plumbing than this PR should take on, documenting the limitation seems like the right scope for now.

Separately, since the referencedTable and foreignTable overloads duplicate the same three-branch conditional type, would it be worth factoring that into a shared helper type? It would cut the overload count roughly in half and make it much easier to maintain going forward.

Thanks again for sticking with this one through a couple of rounds, it's making the fix noticeably more solid.

…cs and tests

- Factor the conditional column type into a shared OrderColumnForTable
  helper, reducing duplication across referencedTable and foreignTable
  overloads.
- Add JSDoc note explaining that aliased referencedTable names that do
  not match a real table/view fall through to unchecked string, and
  that collisions with unrelated real tables should use a string variable
  to bypass checking.
- Add tests for aliased referencedTable (unknown alias falls through to
  string overload).

Addresses reviewer feedback on supabase#2445.
@7vignesh

Copy link
Copy Markdown
Author

Hi @mandarini, all three points addressed in the latest push:

  1. Aliased referencedTable test: Added tests showing that an alias like 'archived' (not a real table) falls through to the unchecked string overload, so any column name compiles fine. Works for both referencedTable and foreignTable.

  2. JSDoc note: Added a note to the @param options.referencedTable doc explaining the alias limitation: unknown aliases fall through to unchecked string, and if an alias collides with a real table name, columns get validated against the wrong table. The workaround is using a plain string variable for the column.

  3. Shared helper type: Extracted the three-branch conditional into an OrderColumnForTable<Schema, ReferencedTable, Column> helper type, so both referencedTable and foreignTable overloads reference it instead of duplicating the logic. Cuts the overload verbosity roughly in half and makes future changes a single edit.

All 28 tstyche type test files pass and the build is green.

@7vignesh
7vignesh requested a review from mandarini July 31, 2026 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Order on a foreign table

3 participants