Skip to content
This repository was archived by the owner on Oct 9, 2025. It is now read-only.

Commit 5fec334

Browse files
committed
feat: add rpc typing with union of functions definitions
1 parent 2a40d4f commit 5fec334

File tree

10 files changed

+1279
-1420
lines changed

10 files changed

+1279
-1420
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"test:types:watch": "run-s build && tsd --files 'test/**/*.test-d.ts' --watch",
4545
"db:clean": "cd test/db && docker compose down --volumes",
4646
"db:run": "cd test/db && docker compose up --detach && wait-for-localhost 3000",
47-
"db:generate-test-types": "cd test/db && docker compose up --detach && wait-for-localhost 8080 && curl --location 'http://0.0.0.0:8080/generators/typescript?included_schemas=public,personal&detect_one_to_one_relationships=true' > ../types.generated.ts"
47+
"db:generate-test-types": "cd test/db && docker compose up --detach && wait-for-localhost 8080 && curl --location 'http://0.0.0.0:8080/generators/typescript?included_schemas=public,personal&detect_one_to_one_relationships=true' > ../types.generated.ts && npm run format"
4848
},
4949
"dependencies": {
5050
"@supabase/node-fetch": "^2.6.14"

src/PostgrestClient.ts

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@ export type GetRpcFunctionFilterBuilderByArgs<
2525
// args function definition if there is one in such case
2626
1: [keyof Args] extends [never]
2727
? ExtractExactFunction<Schema['Functions'][FnName], Args>
28-
: Args extends GenericFunction['Args']
28+
: // Otherwise, we attempt to match with one of the function definition in the union based
29+
// on the function arguments provided
30+
Args extends GenericFunction['Args']
2931
? FindMatchingFunctionByArgs<Schema['Functions'][FnName], Args>
3032
: any
3133
}[1] extends infer Fn
32-
? IsAny<Fn> extends true
34+
? // If we are dealing with an non-typed client everything is any
35+
IsAny<Fn> extends true
3336
? { Row: any; Result: any; RelationName: FnName; Relationships: null }
34-
: Fn extends GenericFunction
37+
: // Otherwise, we use the arguments based function definition narrowing to get the rigt value
38+
Fn extends GenericFunction
3539
? {
3640
Row: Fn['Returns'] extends any[]
3741
? Fn['Returns'][number] extends Record<string, unknown>
@@ -50,7 +54,8 @@ export type GetRpcFunctionFilterBuilderByArgs<
5054
: Schema['Views'][Fn['SetofOptions']['to']]['Relationships']
5155
: null
5256
}
53-
: Fn extends never
57+
: // If we failed to find the function by argument, we still pass with any but also add an overridable
58+
Fn extends never
5459
? {
5560
Row: any
5661
Result: { error: true } & "Couldn't find function"
@@ -60,23 +65,6 @@ export type GetRpcFunctionFilterBuilderByArgs<
6065
: never
6166
: never
6267

63-
export type RpcRowType<
64-
Schema extends GenericSchema,
65-
FnName extends string & keyof Schema['Functions'],
66-
Args
67-
> = {
68-
0: Schema['Functions'][FnName]
69-
1: Args extends GenericFunction['Args']
70-
? FindMatchingFunctionByArgs<Schema['Functions'][FnName], Args>
71-
: any
72-
}[1] extends infer Fn
73-
? IsAny<Fn> extends true
74-
? any
75-
: Fn extends GenericFunction
76-
? Fn['Returns']
77-
: never
78-
: never
79-
8068
/**
8169
* PostgREST client.
8270
*

test/advanced_rpc.test-d.ts

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
import { expectType } from 'tsd'
2+
import { TypeEqual } from 'ts-expect'
3+
import { Database } from './types.override'
4+
import { rpcQueries } from './advanced_rpc'
5+
import { SelectQueryError } from '../src/select-query-parser/utils'
6+
7+
type Schema = Database['public']
8+
9+
{
10+
const { data } = await rpcQueries['function returning a setof embeded table']
11+
let result: Exclude<typeof data, null>
12+
let expected: Array<Schema['Tables']['messages']['Row']>
13+
expectType<TypeEqual<typeof result, typeof expected>>(true)
14+
}
15+
16+
{
17+
const { data } = await rpcQueries['function double definition returning a setof embeded table']
18+
let result: Exclude<typeof data, null>
19+
let expected: Array<Schema['Tables']['messages']['Row']>
20+
expectType<TypeEqual<typeof result, typeof expected>>(true)
21+
}
22+
23+
{
24+
const { data } = await rpcQueries['function returning a single row embeded table']
25+
let result: Exclude<typeof data, null>
26+
let expected: Schema['Tables']['user_profiles']['Row']
27+
expectType<TypeEqual<typeof result, typeof expected>>(true)
28+
}
29+
30+
{
31+
const { data } = await rpcQueries['function with scalar input']
32+
let result: Exclude<typeof data, null>
33+
let expected: Array<Schema['Tables']['messages']['Row']>
34+
expectType<TypeEqual<typeof result, typeof expected>>(true)
35+
}
36+
37+
{
38+
const { data } = await rpcQueries['function with table row input']
39+
let result: Exclude<typeof data, null>
40+
let expected: Array<Schema['Tables']['messages']['Row']>
41+
expectType<TypeEqual<typeof result, typeof expected>>(true)
42+
}
43+
44+
{
45+
const { data } = await rpcQueries['function with view row input']
46+
let result: Exclude<typeof data, null>
47+
let expected: Array<Schema['Tables']['messages']['Row']>
48+
expectType<TypeEqual<typeof result, typeof expected>>(true)
49+
}
50+
51+
{
52+
const { data } = await rpcQueries['function returning view']
53+
let result: Exclude<typeof data, null>
54+
let expected: Array<Schema['Views']['recent_messages']['Row']>
55+
expectType<TypeEqual<typeof result, typeof expected>>(true)
56+
}
57+
58+
{
59+
const { data } = await rpcQueries['function with scalar input returning view']
60+
let result: Exclude<typeof data, null>
61+
let expected: Array<Schema['Views']['recent_messages']['Row']>
62+
expectType<TypeEqual<typeof result, typeof expected>>(true)
63+
}
64+
65+
{
66+
const { data } = await rpcQueries['function with scalar input with followup select']
67+
let result: Exclude<typeof data, null>
68+
let expected: Array<{
69+
channel_id: number | null
70+
message: string | null
71+
users: {
72+
catchphrase: unknown
73+
username: string
74+
} | null
75+
}>
76+
expectType<TypeEqual<typeof result, typeof expected>>(true)
77+
}
78+
79+
{
80+
const { data } = await rpcQueries['function with row input with followup select']
81+
let result: Exclude<typeof data, null>
82+
let expected: Array<{
83+
id: number
84+
username: string | null
85+
users: {
86+
catchphrase: unknown
87+
username: string
88+
} | null
89+
}>
90+
expectType<TypeEqual<typeof result, typeof expected>>(true)
91+
}
92+
93+
// Tests for unresolvable functions
94+
{
95+
const { data } = await rpcQueries['unresolvable function with no params']
96+
let result: Exclude<typeof data, null>
97+
let expected: undefined
98+
expectType<TypeEqual<typeof result, typeof expected>>(true)
99+
}
100+
101+
{
102+
const { data } = await rpcQueries['unresolvable function with text param']
103+
let result: Exclude<typeof data, null>
104+
// Should be an error response due to ambiguous function resolution
105+
let expected: SelectQueryError<'Could not choose the best candidate function between: postgrest_unresolvable_function(a => int4), postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved'>
106+
expectType<TypeEqual<typeof result, typeof expected>>(true)
107+
}
108+
109+
{
110+
const { data } = await rpcQueries['unresolvable function with int param']
111+
let result: Exclude<typeof data, null>
112+
// Should be an error response due to ambiguous function resolution
113+
let expected: SelectQueryError<'Could not choose the best candidate function between: postgrest_unresolvable_function(a => int4), postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved'>
114+
expectType<TypeEqual<typeof result, typeof expected>>(true)
115+
}
116+
117+
// Tests for resolvable functions
118+
{
119+
const { data } = await rpcQueries['resolvable function with no params']
120+
let result: Exclude<typeof data, null>
121+
let expected: undefined
122+
expectType<TypeEqual<typeof result, typeof expected>>(true)
123+
}
124+
125+
{
126+
const { data } = await rpcQueries['resolvable function with text param']
127+
let result: Exclude<typeof data, null>
128+
let expected: number
129+
expectType<TypeEqual<typeof result, typeof expected>>(true)
130+
}
131+
132+
{
133+
const { data } = await rpcQueries['resolvable function with int param']
134+
let result: Exclude<typeof data, null>
135+
let expected: string
136+
expectType<TypeEqual<typeof result, typeof expected>>(true)
137+
}
138+
139+
{
140+
const { data } = await rpcQueries['resolvable function with profile_id param']
141+
let result: Exclude<typeof data, null>
142+
let expected: Array<Schema['Tables']['user_profiles']['Row']>
143+
expectType<TypeEqual<typeof result, typeof expected>>(true)
144+
}
145+
146+
{
147+
const { data } = await rpcQueries['resolvable function with channel_id and search params']
148+
let result: Exclude<typeof data, null>
149+
let expected: Array<Schema['Tables']['messages']['Row']>
150+
expectType<TypeEqual<typeof result, typeof expected>>(true)
151+
}
152+
153+
{
154+
const { data } = await rpcQueries['resolvable function with user_row param']
155+
let result: Exclude<typeof data, null>
156+
let expected: Array<Schema['Tables']['messages']['Row']>
157+
expectType<TypeEqual<typeof result, typeof expected>>(true)
158+
}
159+
160+
// Tests for polymorphic functions
161+
{
162+
const { data } = await rpcQueries['polymorphic function with text param']
163+
let result: Exclude<typeof data, null>
164+
let expected: string
165+
expectType<TypeEqual<typeof result, typeof expected>>(true)
166+
}
167+
168+
{
169+
const { data } = await rpcQueries['polymorphic function with unnamed json param']
170+
let result: Exclude<typeof data, null>
171+
let expected: number
172+
expectType<TypeEqual<typeof result, typeof expected>>(true)
173+
}
174+
175+
{
176+
const { data } = await rpcQueries['polymorphic function with unnamed jsonb param']
177+
let result: Exclude<typeof data, null>
178+
let expected: number
179+
expectType<TypeEqual<typeof result, typeof expected>>(true)
180+
}
181+
182+
{
183+
const { data } = await rpcQueries['polymorphic function with unnamed text param']
184+
let result: Exclude<typeof data, null>
185+
let expected: number
186+
expectType<TypeEqual<typeof result, typeof expected>>(true)
187+
}
188+
189+
{
190+
const { data } = await rpcQueries[
191+
'polymorphic function with unnamed params definition call with bool param'
192+
]
193+
let result: Exclude<typeof data, null>
194+
// TODO: since this call use an invalid type definition, we can't distinguish between the "no values" or the "empty"
195+
// property call:
196+
// polymorphic_function_with_no_params_or_unnamed:
197+
// | {
198+
// Args: Record<PropertyKey, never>
199+
// Returns: number
200+
// }
201+
// | {
202+
// Args: { '': string }
203+
// Returns: string
204+
// }
205+
// A type error would be raised at higher level (argument providing) time though
206+
let expected: string | number
207+
expectType<TypeEqual<typeof result, typeof expected>>(true)
208+
}
209+
210+
{
211+
// The same call, but using a valid text params: `{'': 'test'}` should properly
212+
// narrow down the resilt to the right definition
213+
const { data } = await rpcQueries[
214+
'polymorphic function with unnamed params definition call with text param'
215+
]
216+
let result: Exclude<typeof data, null>
217+
let expected: string
218+
expectType<TypeEqual<typeof result, typeof expected>>(true)
219+
}
220+
{
221+
// Same thing if we call the function with explicitely no params, it should narrow down to the right definition
222+
const { data } = await rpcQueries[
223+
'polymorphic function with no params and unnamed params definition call with no params'
224+
]
225+
let result: Exclude<typeof data, null>
226+
let expected: number
227+
expectType<TypeEqual<typeof result, typeof expected>>(true)
228+
}
229+
230+
{
231+
const { data } = await rpcQueries['polymorphic function with unnamed default no params']
232+
let result: Exclude<typeof data, null>
233+
let expected: SelectQueryError<'Could not choose the best candidate function between: polymorphic_function_with_unnamed_default( => int4), polymorphic_function_with_unnamed_default(). Try renaming the parameters or the function itself in the database so function overloading can be resolved'>
234+
expectType<TypeEqual<typeof result, typeof expected>>(true)
235+
}
236+
237+
{
238+
const { data } = await rpcQueries['polymorphic function with unnamed default int param']
239+
let result: Exclude<typeof data, null>
240+
// TODO: since this call use an invalid type definition, we can't distinguish between the "no values" or the "empty"
241+
// A type error would be raised at higher level (argument providing) time though
242+
let expected:
243+
| string
244+
| SelectQueryError<'Could not choose the best candidate function between: polymorphic_function_with_unnamed_default( => int4), polymorphic_function_with_unnamed_default(). Try renaming the parameters or the function itself in the database so function overloading can be resolved'>
245+
expectType<TypeEqual<typeof result, typeof expected>>(true)
246+
}
247+
248+
{
249+
const { data } = await rpcQueries['polymorphic function with unnamed default text param']
250+
let result: Exclude<typeof data, null>
251+
let expected: string
252+
expectType<TypeEqual<typeof result, typeof expected>>(true)
253+
}
254+
255+
{
256+
const { data } = await rpcQueries['polymorphic function with unnamed default overload no params']
257+
let result: Exclude<typeof data, null>
258+
let expected: SelectQueryError<'Could not choose the best candidate function between: polymorphic_function_with_unnamed_default_overload( => int4), polymorphic_function_with_unnamed_default_overload(). Try renaming the parameters or the function itself in the database so function overloading can be resolved'>
259+
expectType<TypeEqual<typeof result, typeof expected>>(true)
260+
}
261+
262+
{
263+
const { data } = await rpcQueries['polymorphic function with unnamed default overload int param']
264+
let result: Exclude<typeof data, null>
265+
// TODO: since this call use an invalid type definition, we can't distinguish between the "no values" or the "empty"
266+
// A type error would be raised at higher level (argument providing) time though
267+
let expected:
268+
| string
269+
| SelectQueryError<'Could not choose the best candidate function between: polymorphic_function_with_unnamed_default_overload( => int4), polymorphic_function_with_unnamed_default_overload(). Try renaming the parameters or the function itself in the database so function overloading can be resolved'>
270+
expectType<TypeEqual<typeof result, typeof expected>>(true)
271+
}
272+
273+
{
274+
const { data } = await rpcQueries['polymorphic function with unnamed default overload text param']
275+
let result: Exclude<typeof data, null>
276+
let expected: string
277+
expectType<TypeEqual<typeof result, typeof expected>>(true)
278+
}
279+
280+
{
281+
const { data } = await rpcQueries['polymorphic function with unnamed default overload bool param']
282+
let result: Exclude<typeof data, null>
283+
// TODO: since this call use an invalid type definition, we can't distinguish between the "no values" or the "empty"
284+
// A type error would be raised at higher level (argument providing) time though
285+
let expected:
286+
| string
287+
| SelectQueryError<'Could not choose the best candidate function between: polymorphic_function_with_unnamed_default_overload( => int4), polymorphic_function_with_unnamed_default_overload(). Try renaming the parameters or the function itself in the database so function overloading can be resolved'>
288+
expectType<TypeEqual<typeof result, typeof expected>>(true)
289+
}

0 commit comments

Comments
 (0)