Skip to content

feat(postgrest)!: make rpc a PostgrestRpc getter with typed entry points - #1958

Merged
spydon merged 2 commits into
mainfrom
lukasklingsbo/sdk-1836-typed-rpc-entry-point
Oct 15, 2026
Merged

spydon merged 2 commits into
mainfrom
lukasklingsbo/sdk-1836-typed-rpc-entry-point

Conversation

@spydon

@spydon spydon commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Linear: SDK-1836

Why

The typed surface covers tables (client.table(Books.table)) but not Postgres functions: rpc<T>() leaves T free and decodes with an unchecked cast. supabase_typegen can now resolve every callable function from postgrest-typegen 0.5.0's rpcFunctions, so it needs a runtime to generate against. The call site we settled on is client.rpc.searchBooks(query: 'dart'), generated extension methods on whatever rpc returns.

What

  • rpc is a getter on PostgrestClient, SupabaseClient and SupabaseQuerySchema, returning a PostgrestRpc. PostgrestRpc.call makes the same untyped call as before, so client.rpc('fn', params: {...}) and client.rpc<int>('fn') compile unchanged.
  • Typed entry points the generated code builds on:
    • rows(function, arguments:, rowFromJson:, schema:) returns a PostgrestTypedFilterBuilder<Row, List<Row>>, so where, order, limit, range, selectOnly, single() and count() work on the returned rows with the column tokens of the row type.
    • value(function, arguments:, fromJson:, schema:) returns a PostgrestTypedBuilder<T> for scalars, single rows, sets of scalars and void.
  • head() on a typed call sends the arguments as query parameters, since a HEAD request carries no body.
  • Schema: a typed call addresses the schema it is given, like a PostgrestTable does; a client scoped with .schema() wins.
  • Executor: both go through the typed request and executor as a new internal PostgrestTableOperation.rpc (plus a PostgrestResultShape.value), one more case of the executor's _operation, rendered through the untyped rpc builder so the wire request matches an untyped call. Both types stay hidden from the public API. isMutation is true for a function call, since nothing on the client says whether it writes, so a trailing select/selectOnly goes through the same rule as after a mutation.
  • MIGRATION.md covers the one break: passing rpc itself around as a function. sdk-compliance.yaml registers PostgrestRpc and its members under database.query.rpc.

Tests

  • typed_rpc_test.dart: the untyped call through the getter (params, type argument, GET, scoped schema), and rows/value with filters, ordering, limits, selectOnly, single(), count(), schema precedence and request options.
  • typed_rpc_integration_test.dart against the local stack: RETURNS TABLE rows filtered and ordered, selectOnly plus single(), count(), scalars, a NULL result, void, array arguments and a function in another schema.
  • postgrest (613) and supabase (242) suites pass locally; DCM reports no warnings; the compliance symbol, drift and schema checks pass against the pinned capability matrix.

Follow-up: #1959, which generates the extension methods.

Summary by CodeRabbit

  • New Features
    • Database function calls now support typed row and value results, including filtering, ordering, column selection, and schema-specific calls.
    • Function results can be decoded into application-defined types, including scalar and nullable values.
    • RPC access is available through an object that supports both regular and typed calls.
  • Documentation
    • Added migration guidance: calling RPC access remains supported, but passing it directly as a function no longer compiles. Wrap it in a closure instead.

`PostgrestClient.rpc`, `SupabaseClient.rpc` and `SupabaseQuerySchema.rpc`
are getters returning a `PostgrestRpc`. Calling it makes the same untyped
call as before; its `rows` and `value` methods are the typed entry points
the extension methods `supabase_typegen` generates are built on. Both go
through the typed request and executor, so a function returning rows keeps
`where`, `order`, `limit`, `selectOnly`, `single()` and `count()`.

SDK-1836
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: supabase/supabase-flutter/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f0584157-285f-4c9f-8c21-038867767573
📥 Commits

Reviewing files that changed from the base of the PR and between 396ec78 and 4ebbb5d.

📒 Files selected for processing: 2
  • packages/postgrest/lib/src/postgrest_table_executor.dart
  • packages/postgrest/test/typed_rpc_test.dart

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

📝 Walkthrough

The RPC API now exposes a PostgrestRpc getter with untyped and typed call methods. RPC requests support row and value results, schema selection, filtering, and column selection. PostgREST and Supabase client accessors, tests, SDK compliance data, and migration guidance are updated.

Changes: Typed RPC calls

Layer / File(s) Summary
RPC request execution
packages/postgrest/lib/src/postgrest_table_request.dart, packages/postgrest/lib/src/postgrest_table_executor.dart
RPC requests now have dedicated operation and result shapes. The executor builds RPC requests, applies filters, supports column selection, and returns value-shaped results directly.
Typed RPC API and client access
packages/postgrest/lib/src/postgrest_rpc.dart, packages/postgrest/lib/src/postgrest.dart, packages/postgrest/lib/src/postgrest_typed_builder.dart, packages/supabase/lib/src/supabase_client.dart, packages/supabase/lib/src/supabase_query_schema.dart
PostgrestRpc adds untyped calls and typed rows and value calls. The PostgREST and Supabase RPC members now return this object. Typed calls convert rows or response values and resolve schema selection.
RPC coverage and migration guidance
packages/postgrest/test/typed_rpc_test.dart, packages/postgrest/test/typed_rpc_integration_test.dart, MIGRATION.md, sdk-compliance.yaml
Tests cover RPC request options, typed results, schema selection, filtering, and integration cases. Migration guidance describes the getter change, and SDK compliance data lists the new RPC symbols.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant PostgrestRpc
  participant PostgrestHttpTableExecutor
  Caller->>PostgrestRpc: Call rows or value
  PostgrestRpc->>PostgrestHttpTableExecutor: Build and execute typed RPC request
  PostgrestHttpTableExecutor-->>PostgrestRpc: Return RPC result
  PostgrestRpc-->>Caller: Convert rows or response value
Loading

Suggested reviewers: grdsdev

Merge Risk: ⚪ Minimal · up to 4ebbb

The typed RPC changes show no concrete merge-blocking risk in the reviewed files. The getter migration is documented as a breaking change by the author.

Pre-merge checks: Passed 5
Passed Passed checks: 5 passed
Check name Status Explanation
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely summarizes the primary breaking change: replacing rpc with a PostgrestRpc getter and adding typed entry points.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

…meters

`head()` on a typed function call switched the POST to a HEAD, which carries
no body, so the arguments were dropped and PostgREST resolved another
overload or none. They are now sent as query parameters, the way an untyped
`get` call sends them.

The executor renders a function call as one more case of `_operation`, and
selects the columns of its rows through the shared mutation rule instead of
a special case of its own.

SDK-1836
@spydon
spydon merged commit 08392a9 into main Oct 15, 2026
47 checks passed
@spydon
spydon deleted the lukasklingsbo/sdk-1836-typed-rpc-entry-point branch October 15, 2026 13:05
spydon added a commit that referenced this pull request Oct 15, 2026
Linear: SDK-2232. Stacked on #1958.

## Why

postgrest-typegen 0.5.0 resolves which functions PostgREST exposes under
`/rpc`, how their overloads group and what a call returns into
`GeneratorMetadata.rpcFunctions` (supabase/sdk#271). With the
`PostgrestRpc` runtime from #1958, `supabase_typegen` can now generate a
typed method for every function instead of leaving `rpc` untyped.

## What

```dart
final List<BooksRow> books = await client.rpc
    .searchBooks(query: 'dart', mood: Mood.happy)
    .where(Books.price.lt(20))
    .order(Books.title.asc());

final int? total = await client.rpc.countBooks();
```

- **One extension, `DatabaseFunctions on PostgrestRpc`**, with a method
per callable overload. Arguments are named parameters, optional where
the function declares a default, converted to the wire the same way
insert values are (enums, dates, timestamps, ranges, `bytea`, vectors,
arrays, row values).
- **Results by shape:**
- Rows of a generated table resolve to
`PostgrestTypedFilterBuilder<BooksRow, List<BooksRow>>`, filterable with
that table's column tokens.
- A `RETURNS TABLE (…)` function gets its own row type and column tokens
(`AuthorBookCountsRow`, `AuthorBookCounts.bookCount`), every column
nullable.
- A single row resolves to the nullable row type: PostgREST answers a
function that finds no row with an object whose every column is null,
which reads as `null`. Scalars and sets of scalars come back converted
and nullable, `void` to `void`, anything else to `Object?`.
- **Naming:** functions outside `public` carry the schema as a prefix
(`inventoryRestock`). Of overloads, the one with the fewest arguments
keeps the plain name and the others spell out their arguments
(`booksOfWithAuthorNameAndMaxPrice`). Names stay clear of
`PostgrestRpc`'s own members (`call`, `rows`, `value`), the core types
the bodies name (`int`, `num`, …) and the codecs they call, and an
overload taking the unnamed json body is named `…WithBody`.
- **Not generated:** an overload PostgREST cannot resolve (the
resolver's `error`), or one taking an unnamed parameter other than a
sole `json`/`jsonb` one. Each gets a line in the extension's doc comment
saying why, except computed fields and relationships, which stay
reachable through their table.
- **Introspection** moves its pin to `postgrest-typegen/v0.5.0`. The SQL
is unchanged; `rpc_functions.dart` ports `resolveRpcFunctions`,
`introspect` attaches its result and `sortGeneratorMetadata` orders it.
The parser reads `rpcFunctions` and falls back to the port for documents
that predate it. The port already carries the fixes of supabase/sdk#272
(no dot-joined keys, narrower computed-field fallback).

## Deviation from the TypeScript resolver

The resolver treats a sole unnamed row parameter as callable when the
function returns rows (`recent_books(public.authors)`), but PostgREST
answers that call with `PGRST202`. The port keeps the upstream semantics
so it stays byte-identical to 0.5.0, and the generator refuses that case
itself. Upstream fix tracked in SDK-2231.

## Tests

- `rpc_functions_test.dart`: the Dart resolver reproduces the TypeScript
`rpcFunctions` of the fixture exactly, plus callable filtering, input
modes, `RETURNS TABLE` columns, `setof_options`, computed fields, both
conflict errors, overload order and schema keys with dots.
- Parser, generator and behavior tests for every result shape, overload
naming, notes and the `typed_data` import; the generated methods are
exercised through `MockSupabaseHttpClient` for the wire request and the
conversions.
- `seed.sql` gains functions covering each shape; the fixture is
regenerated with postgrest-typegen v0.5.0 (insertions only, existing
object ids unchanged) and the hostile fixture gains functions named
`call`, `rows`, `class` and `map`.
- The parity suite passes against a fresh `postgres:15`. The generated
golden was also run end to end against a real PostgREST on the fixture
database, every method included.
- Golden diffs are insertions only; hunks the local formatter would have
rewrapped keep `main`'s text, which the SDK formatter accepts unchanged.
spydon added a commit that referenced this pull request Oct 16, 2026
…ity matrix to v1.16.0 (#1964)

Linear: SDK-2258

## Why

`database.generated_types.typed_queries` and
`database.generated_types.typed_rpc` were added to the capability matrix
in supabase/sdk#276, released in capability-matrix v1.16.0. Both are
already implemented here: typed tables through `table()`, and typed
function calls through `PostgrestRpc.rows`/`PostgrestRpc.value` (#1958,
generated by #1959). They show up as unregistered rather than missing.

## What

- Bumps the supabase/sdk pins in `validate-capabilities.yml` and
`sync-compliance.yml` from v1.15.0 to v1.16.0, the first tag that
defines the two IDs. The reusable workflows are unchanged between the
tags.
- Registers `database.generated_types.typed_queries` with
`PostgrestClient.table`, `SupabaseClient.table` and
`SupabaseQuerySchema.table` as entry points and the typed builders plus
`PostgrestTable` as supporting symbols.
- Registers `database.generated_types.typed_rpc` with
`PostgrestRpc.rows` and `PostgrestRpc.value` as entry points.

## Verification

Against a supabase/sdk checkout at `capability-matrix/v1.16.0`:
`validate-compliance`, `check-drift` and `check-api-symbols` all pass.
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.

1 participant