Skip to content

fix(typegen): only collapse ROWS 1 return types when SetofOptions is emitted - #1096

Merged
avallete merged 2 commits into
masterfrom
fix/typegen-rows-one-array
Aug 5, 2026
Merged

fix(typegen): only collapse ROWS 1 return types when SetofOptions is emitted#1096
avallete merged 2 commits into
masterfrom
fix/typegen-rows-one-array

Conversation

@mandarini

@mandarini mandarini commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Fixes supabase/supabase#46525.

The TypeScript typegen drops the [] from the return type of every set-returning function declared with ROWS 1, but the compensating SetofOptions metadata is only emitted when the function returns a named table, view, or composite type. For ad-hoc RETURNS TABLE (...) and scalar SETOF functions this produced a single-object type while PostgREST still returns an array at runtime, and there is no metadata for postgrest-js to correct it. The collapse is now gated on the same condition as the SetofOptions emission, so those functions keep the array type, while named-relation functions (where postgrest-js restores the array for direct RPC calls via isOneToOne) are unchanged.

Note for consumers: regenerating types flips Returns from object to array for affected functions. The resulting type errors point at real runtime mismatches, since PostgREST has always returned an array for these calls.

@mandarini

mandarini commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

E2E verification against postgrest-js

I ran the cross-repo check locally: built two pg-meta images — one from this PR's merge-base (e7d86a3) and one from this branch — pinned each into the postgrest-js test stack, regenerated types for the postgrest-js test database with both, and compared against PostgREST's actual runtime responses.

1. No change to postgrest-js's existing type surface

supabase gen types typescript --local --schema public,personal output is byte-identical between the merge-base image and this PR's image, for the entire postgrest-js test database.

This is expected: all three ROWS 1 functions in that schema (get_user_profile_non_nullable, get_user_first_message, function_using_setof_rows_one) return named relations, so they keep the collapsed object Returns + SetofOptions.isOneToOne: true. The contract postgrest-js relies on (isOneToOne restoring the array shape for direct RPC calls) is untouched — no inference regression is possible against the current test surface.

2. For the broken shapes, the new types match runtime; the old ones don't

Added the affected shapes to the live test DB and called them through PostgREST:

Function merge-base typegen this PR actual PostgREST response
RETURNS TABLE(id int, name text) ROWS 1 { id, name } { id, name }[] [{"id":1,"name":"hello_one"}]
RETURNS SETOF text ROWS 1 string string[] ["hello_scalar"]

Neither shape emits SetofOptions, so postgrest-js has no metadata to correct the collapsed type — the merge-base types diverge from the actual response, which is exactly supabase/supabase#46525.

3. postgrest-js type suite (tstyche)

  • Committed types.generated.ts (control): 29/29 files pass
  • Fully regenerated types (either image — outputs are identical): 28/29, the one failure being the scalar_computed_count / scalar_computed_ids assertions in embeded_functions_join.test.ts

That failure is pre-existing drift, not this PR: it reproduces identically with the unmodified merge-base image. The committed types.generated.ts in postgrest-js contains SetofOptions.isNotNullable entries (introduced via supabase/supabase-js#2224) that aren't emitted by any pg-meta master build, so regenerating with any current master-based image drops them. Worth tracking separately from this fix.

Reproduction steps
# 1. Build both images so the Supabase CLI resolves them locally (checked before pulling)
git -C postgres-meta worktree add /tmp/pgmeta-base $(git -C postgres-meta merge-base HEAD master)
docker build postgres-meta -t public.ecr.aws/supabase/postgres-meta:rows1-pr
docker build /tmp/pgmeta-base -t public.ecr.aws/supabase/postgres-meta:rows1-base

# 2. In supabase-js/packages/core/postgrest-js: pin the image tag and start the stack
printf 'rows1-base' > test/supabase/.temp/pgmeta-version
pnpm exec supabase --workdir test start

# 3. Generate with each image (the pin is re-read per invocation)
pnpm exec supabase --workdir test gen types typescript --local --schema public,personal > /tmp/types-base.ts
printf 'rows1-pr' > test/supabase/.temp/pgmeta-version
pnpm exec supabase --workdir test gen types typescript --local --schema public,personal > /tmp/types-pr.ts
diff /tmp/types-base.ts /tmp/types-pr.ts   # → identical

# 4. Runtime source of truth for the affected shapes
docker exec supabase_db_test psql -U postgres -c "
CREATE FUNCTION public.e2e_rows1_ad_hoc_table(prefix text)
RETURNS TABLE(id int, name text) LANGUAGE SQL STABLE ROWS 1
AS \$\$ SELECT 1, prefix || '_one' \$\$;
CREATE FUNCTION public.e2e_rows1_scalar_setof(prefix text)
RETURNS SETOF text LANGUAGE SQL STABLE ROWS 1
AS \$\$ SELECT prefix || '_scalar' \$\$;
NOTIFY pgrst, 'reload schema';"
curl -s http://127.0.0.1:54321/rest/v1/rpc/e2e_rows1_ad_hoc_table \
  -X POST -H "apikey: $ANON" -H "Content-Type: application/json" -d '{"prefix":"hello"}'
# → [{"id":1,"name":"hello_one"}]

# 5. Type suite: swap regenerated types in, post-process, run tstyche
cp /tmp/types-pr.ts test/types.generated.ts
node scripts/update-json-type.js && pnpm exec prettier --write test/types.generated.ts
pnpm exec tstyche

Follow-up regression tests for the ROWS 1 array contract on the SDK side are tracked in SDK-1383.

@mandarini
mandarini marked this pull request as ready for review August 5, 2026 17:18
@mandarini
mandarini requested review from a team, avallete and soedirgo as code owners August 5, 2026 17:18
@mandarini mandarini self-assigned this Aug 5, 2026

@avallete avallete left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice. I think we'll want the new tests to be added to postgrest-js against actual e2e postgrest runtime when this is merged and released. That way we don't get a regression on the behavior in the future 👍

@avallete
avallete merged commit 9eda207 into master Aug 5, 2026
10 checks passed
@avallete
avallete deleted the fix/typegen-rows-one-array branch August 5, 2026 17:20
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.

Type generation incorrectly treats PostgreSQL ROWS 1 RPC functions as returning a single object instead of an array

2 participants