Repository navigation
feat(postgrest)!: make rpc a PostgrestRpc getter with typed entry points - #1958
Conversation
`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
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
…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
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.
…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.
Linear: SDK-1836
Why
The typed surface covers tables (
client.table(Books.table)) but not Postgres functions:rpc<T>()leavesTfree and decodes with an unchecked cast.supabase_typegencan now resolve every callable function from postgrest-typegen 0.5.0'srpcFunctions, so it needs a runtime to generate against. The call site we settled on isclient.rpc.searchBooks(query: 'dart'), generated extension methods on whateverrpcreturns.What
rpcis a getter onPostgrestClient,SupabaseClientandSupabaseQuerySchema, returning aPostgrestRpc.PostgrestRpc.callmakes the same untyped call as before, soclient.rpc('fn', params: {...})andclient.rpc<int>('fn')compile unchanged.rows(function, arguments:, rowFromJson:, schema:)returns aPostgrestTypedFilterBuilder<Row, List<Row>>, sowhere,order,limit,range,selectOnly,single()andcount()work on the returned rows with the column tokens of the row type.value(function, arguments:, fromJson:, schema:)returns aPostgrestTypedBuilder<T>for scalars, single rows, sets of scalars andvoid.head()on a typed call sends the arguments as query parameters, since a HEAD request carries no body.PostgrestTabledoes; a client scoped with.schema()wins.PostgrestTableOperation.rpc(plus aPostgrestResultShape.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.isMutationistruefor a function call, since nothing on the client says whether it writes, so a trailingselect/selectOnlygoes through the same rule as after a mutation.MIGRATION.mdcovers the one break: passingrpcitself around as a function.sdk-compliance.yamlregistersPostgrestRpcand its members underdatabase.query.rpc.Tests
typed_rpc_test.dart: the untyped call through the getter (params, type argument, GET, scoped schema), androws/valuewith filters, ordering, limits,selectOnly,single(),count(), schema precedence and request options.typed_rpc_integration_test.dartagainst the local stack:RETURNS TABLErows filtered and ordered,selectOnlyplussingle(),count(), scalars, aNULLresult,void, array arguments and a function in another schema.postgrest(613) andsupabase(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