Note that the generic type T above has no handling of union types. For example, for PostgrestResponseSuccess<A | B>, data is of type (A | B)[], when it should be of type A[] | B[].
This is problematic because (A | B)[], an array that contains both type A and B entries, isn't a valid return type for a database SELECT query, while the correct type, A[] | B[], generates a type error.
To Reproduce
To illustrate, consider a generic useTable hook which takes in tableName and returns a Tanstack Query useQuery hook that loads a table from Supabase.
Type 'PostgrestResponse<Research | Link | Result>' is not assignable to type 'Response<TableName>'.
Type 'PostgrestResponseSuccess<Research | Link | Result>' is not assignable to type 'Response<TableName>'.
Types of property 'data' are incompatible.
Type '(Research | Link | Result)[]' is not assignable to type 'Research[] | Link[] | Result[] | null'.
Type '(Research | Link | Result)[]' is not assignable to type 'Research[]'.
Type 'Research | Link | Result' is not assignable to type 'Research'.
Property 'research' is missing in type 'Link' but required in type 'Research'.
Expected behaviour
In a nutshell, our supplied Response<TableName> generates the correct type, Research[] | Link[] | Result[] | null, which is what we expect returned from the database.
However, Typescript tries to assign it to (Research | Link | Result)[], an array that mixes Research, Link, and Result entries. This should, in no circumstance, be returned from the database. Hence, the type handling here is incorrect.
The fix
The fix is to allow the generic type T of PostgrestResponseSuccess<T> to be transformed into a distributive type if T is a union type. This can be implemented by adding a type ToArray<T>, as follows:
type ToArray<T> = T extends any ? T[] : never;
interface PostgrestResponseSuccess<T> extends PostgrestResponseBase {
error: null;
data: ToArray<T>;
count: number | null;
}
With the fix in place, PostgrestResponseSuccess<Research | Link | Result> can be assigned correctly to Response<TableName>.
Bug report
Describe the bug
await supabase.from(tableName).select("*")
returns a typePostgrestResponse<T>
, which could be of typePostgrestResponseSuccess<T>
.Currently
PostgrestResponseSuccess<T>
is defined as:Note that the generic type
T
above has no handling of union types. For example, forPostgrestResponseSuccess<A | B>
,data
is of type(A | B)[]
, when it should be of typeA[] | B[]
.This is problematic because
(A | B)[]
, an array that contains both typeA
andB
entries, isn't a valid return type for a databaseSELECT
query, while the correct type,A[] | B[]
, generates a type error.To Reproduce
To illustrate, consider a generic
useTable
hook which takes intableName
and returns a Tanstack QueryuseQuery
hook that loads a table from Supabase.The full type error generated is as follows:
Expected behaviour
In a nutshell, our supplied
Response<TableName>
generates the correct type,Research[] | Link[] | Result[] | null
, which is what we expect returned from the database.However, Typescript tries to assign it to
(Research | Link | Result)[]
, an array that mixesResearch
,Link
, andResult
entries. This should, in no circumstance, be returned from the database. Hence, the type handling here is incorrect.The fix
The fix is to allow the generic type
T
ofPostgrestResponseSuccess<T>
to be transformed into a distributive type ifT
is a union type. This can be implemented by adding a typeToArray<T>
, as follows:With the fix in place,
PostgrestResponseSuccess<Research | Link | Result>
can be assigned correctly toResponse<TableName>
.References
Distributive Conditional Types: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types