Skip to content

fix: shape .single() responses after insert, update, upsert and delete - #29

Merged
spydon merged 1 commit into
mainfrom
fix/single-after-mutation
Sep 9, 2026
Merged

spydon merged 1 commit into
mainfrom
fix/single-after-mutation

Conversation

@spydon

@spydon spydon commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Closes #25

What

.single() sets Accept: application/vnd.pgrst.object+json, and PostgREST answers that with a bare object instead of a list. The mock client only honored that header in the GET handler, so every mutation that returned rows still answered with a list:

await client.from('expenses').insert(row).select().single();
// type 'List<dynamic>' is not a subtype of type 'Map<dynamic, dynamic>'

The shaping now lives in a single _createRowsResponse helper that every handler returning rows goes through: insert, update, upsert, delete and select, including the count and empty-table paths.

Error response for a non-single result

Previously a .single() matching zero or several rows answered 200 with {'error': 'N rows were found for single query'}, which the client happily returned as if it were the row. It now answers 406 with the body PostgREST sends:

{
  "code": "PGRST116",
  "details": "Results contain 2 rows, application/vnd.pgrst.object+json requires 1 row",
  "hint": null,
  "message": "JSON object requested, multiple (or no) rows returned"
}

so .single() throws a PostgrestException and .maybeSingle() resolves to null on zero rows, matching a real project.

The separate Accept: application/json branch that used to shape maybeSingle on GET is gone. PostgREST returns a list for that media type, and postgrest-dart already narrows it client-side.

Tests

Added cases for .single() and .maybeSingle() after insert, update, upsert and delete, for a single insert of several rows, for a select matching zero or several rows, and for .single() combined with .count(). The suite passes at 106 tests.

The single-row content negotiation was only applied to GET requests, so
`insert(...).select().single()` got a list back and the client failed
with a type error while casting it to a map.

Response shaping now happens in one place for every request that returns
rows, and a result that does not contain exactly one row answers with the
same 406 PGRST116 error PostgREST returns instead of a 200 carrying an
`error` key.
@spydon
spydon merged commit 657f270 into main Sep 9, 2026
1 check passed
@spydon
spydon deleted the fix/single-after-mutation branch September 9, 2026 13:16
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.

insert + select().single() returns List instead of Map

2 participants