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

Commit 2a40d4f

Browse files
committed
wip: use function picker
1 parent eee4612 commit 2a40d4f

File tree

8 files changed

+1541
-97
lines changed

8 files changed

+1541
-97
lines changed

src/PostgrestClient.ts

Lines changed: 92 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,80 @@ import PostgrestQueryBuilder from './PostgrestQueryBuilder'
22
import PostgrestFilterBuilder from './PostgrestFilterBuilder'
33
import PostgrestBuilder from './PostgrestBuilder'
44
import { DEFAULT_HEADERS } from './constants'
5-
import { Fetch, GenericSchema, GenericSetofOption } from './types'
6-
import { IsAny } from './select-query-parser/utils'
5+
import { Fetch, GenericFunction, GenericSchema, GenericSetofOption } from './types'
6+
import { FindMatchingFunctionByArgs, IsAny } from './select-query-parser/utils'
7+
8+
type ExactMatch<T, S> = [T] extends [S] ? ([S] extends [T] ? true : false) : false
9+
10+
type ExtractExactFunction<Fns, Args> = Fns extends infer F
11+
? F extends GenericFunction
12+
? ExactMatch<F['Args'], Args> extends true
13+
? F
14+
: never
15+
: never
16+
: never
17+
18+
export type GetRpcFunctionFilterBuilderByArgs<
19+
Schema extends GenericSchema,
20+
FnName extends string & keyof Schema['Functions'],
21+
Args
22+
> = {
23+
0: Schema['Functions'][FnName]
24+
// This is here to handle the case where the args is exactly {} and fallback to the empty
25+
// args function definition if there is one in such case
26+
1: [keyof Args] extends [never]
27+
? ExtractExactFunction<Schema['Functions'][FnName], Args>
28+
: Args extends GenericFunction['Args']
29+
? FindMatchingFunctionByArgs<Schema['Functions'][FnName], Args>
30+
: any
31+
}[1] extends infer Fn
32+
? IsAny<Fn> extends true
33+
? { Row: any; Result: any; RelationName: FnName; Relationships: null }
34+
: Fn extends GenericFunction
35+
? {
36+
Row: Fn['Returns'] extends any[]
37+
? Fn['Returns'][number] extends Record<string, unknown>
38+
? Fn['Returns'][number]
39+
: never
40+
: Fn['Returns'] extends Record<string, unknown>
41+
? Fn['Returns']
42+
: never
43+
Result: Fn['Returns']
44+
RelationName: Fn['SetofOptions'] extends GenericSetofOption
45+
? Fn['SetofOptions']['to']
46+
: FnName
47+
Relationships: Fn['SetofOptions'] extends GenericSetofOption
48+
? Fn['SetofOptions']['to'] extends keyof Schema['Tables']
49+
? Schema['Tables'][Fn['SetofOptions']['to']]['Relationships']
50+
: Schema['Views'][Fn['SetofOptions']['to']]['Relationships']
51+
: null
52+
}
53+
: Fn extends never
54+
? {
55+
Row: any
56+
Result: { error: true } & "Couldn't find function"
57+
RelationName: FnName
58+
Relationships: null
59+
}
60+
: never
61+
: never
62+
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
779

880
/**
981
* PostgREST client.
@@ -122,9 +194,17 @@ export default class PostgrestClient<
122194
* `"estimated"`: Uses exact count for low numbers and planned count for high
123195
* numbers.
124196
*/
125-
rpc<FnName extends string & keyof Schema['Functions'], Fn extends Schema['Functions'][FnName]>(
197+
rpc<
198+
FnName extends string & keyof Schema['Functions'],
199+
Args extends Schema['Functions'][FnName]['Args'] = {},
200+
FilterBuilder extends GetRpcFunctionFilterBuilderByArgs<
201+
Schema,
202+
FnName,
203+
Args
204+
> = GetRpcFunctionFilterBuilderByArgs<Schema, FnName, Args>
205+
>(
126206
fn: FnName,
127-
args: Fn['Args'] = {},
207+
args: Args = {} as Args,
128208
{
129209
head = false,
130210
get = false,
@@ -134,39 +214,13 @@ export default class PostgrestClient<
134214
get?: boolean
135215
count?: 'exact' | 'planned' | 'estimated'
136216
} = {}
137-
// if rpc is called with a typeless client, default to infering everything as any
138-
): IsAny<Fn> extends true
139-
? PostgrestFilterBuilder<
140-
Schema,
141-
Fn['Returns'] extends any[]
142-
? Fn['Returns'][number] extends Record<string, unknown>
143-
? Fn['Returns'][number]
144-
: never
145-
: Fn['Returns'] extends Record<string, unknown>
146-
? Fn['Returns']
147-
: never,
148-
Fn['Returns'],
149-
FnName,
150-
null
151-
>
152-
: PostgrestFilterBuilder<
153-
// otherwise, provide the right params for typed .select chaining
154-
Schema,
155-
Fn['Returns'] extends any[]
156-
? Fn['Returns'][number] extends Record<string, unknown>
157-
? Fn['Returns'][number]
158-
: never
159-
: Fn['Returns'] extends Record<string, unknown>
160-
? Fn['Returns']
161-
: never,
162-
Fn['Returns'],
163-
Fn['SetofOptions'] extends GenericSetofOption ? Fn['SetofOptions']['to'] : FnName,
164-
Fn['SetofOptions'] extends GenericSetofOption
165-
? Fn['SetofOptions']['to'] extends keyof Schema['Tables']
166-
? Schema['Tables'][Fn['SetofOptions']['to']]['Relationships']
167-
: Schema['Views'][Fn['SetofOptions']['to']]['Relationships']
168-
: null
169-
> {
217+
): PostgrestFilterBuilder<
218+
Schema,
219+
FilterBuilder['Row'],
220+
FilterBuilder['Result'],
221+
FilterBuilder['RelationName'],
222+
FilterBuilder['Relationships']
223+
> {
170224
let method: 'HEAD' | 'GET' | 'POST'
171225
const url = new URL(`${this.url}/rpc/${fn}`)
172226
let body: unknown | undefined
@@ -199,6 +253,6 @@ export default class PostgrestClient<
199253
body,
200254
fetch: this.fetch,
201255
allowEmpty: false,
202-
} as unknown as PostgrestBuilder<Fn['Returns']>)
256+
} as unknown as PostgrestBuilder<FilterBuilder['Row']>)
203257
}
204258
}

src/select-query-parser/utils.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -527,11 +527,12 @@ type ResolveEmbededFunctionJoinTableRelationship<
527527
Schema extends GenericSchema,
528528
CurrentTableOrView extends keyof TablesAndViews<Schema> & string,
529529
FieldName extends string
530-
> = Schema['Functions'][FieldName] extends GenericFunction
531-
? Schema['Functions'][FieldName]['SetofOptions'] extends GenericSetofOption
532-
? CurrentTableOrView extends Schema['Functions'][FieldName]['SetofOptions']['from']
533-
? Schema['Functions'][FieldName]['SetofOptions']
534-
: false
530+
> = FindMatchingFunctionBySetofFrom<
531+
Schema['Functions'][FieldName],
532+
CurrentTableOrView
533+
> extends infer Fn
534+
? Fn extends GenericFunction
535+
? Fn['SetofOptions']
535536
: false
536537
: false
537538

@@ -618,3 +619,47 @@ export type IsStringUnion<T> = string extends T
618619
? false
619620
: true
620621
: false
622+
623+
// Functions matching utils
624+
export type IsMatchingArgs<
625+
FnArgs extends GenericFunction['Args'],
626+
PassedArgs extends GenericFunction['Args']
627+
> = [FnArgs] extends [Record<PropertyKey, never>]
628+
? PassedArgs extends Record<PropertyKey, never>
629+
? true
630+
: false
631+
: keyof PassedArgs extends keyof FnArgs
632+
? PassedArgs extends FnArgs
633+
? true
634+
: false
635+
: false
636+
637+
export type MatchingFunctionArgs<
638+
Fn extends GenericFunction,
639+
Args extends GenericFunction['Args']
640+
> = Fn extends { Args: infer A extends GenericFunction['Args'] }
641+
? IsMatchingArgs<A, Args> extends true
642+
? Fn
643+
: never
644+
: never
645+
646+
export type FindMatchingFunctionByArgs<
647+
FnUnion,
648+
Args extends GenericFunction['Args']
649+
> = FnUnion extends infer Fn extends GenericFunction ? MatchingFunctionArgs<Fn, Args> : never
650+
651+
type MatchingFunctionBySetofFrom<
652+
Fn extends GenericFunction,
653+
TableName extends string
654+
> = Fn['SetofOptions'] extends GenericSetofOption
655+
? TableName extends Fn['SetofOptions']['from']
656+
? Fn
657+
: never
658+
: never
659+
660+
type FindMatchingFunctionBySetofFrom<
661+
FnUnion,
662+
TableName extends string
663+
> = FnUnion extends infer Fn extends GenericFunction
664+
? MatchingFunctionBySetofFrom<Fn, TableName>
665+
: never

test/db/00-schema.sql

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,4 +250,47 @@ RETURNS SETOF recent_messages
250250
LANGUAGE SQL STABLE
251251
AS $$
252252
SELECT * FROM public.recent_messages WHERE username = active_user_row.username;
253-
$$;
253+
$$;
254+
CREATE OR REPLACE FUNCTION public.get_user_first_message(active_user_row active_users)
255+
RETURNS SETOF recent_messages ROWS 1
256+
LANGUAGE SQL STABLE
257+
AS $$
258+
SELECT * FROM public.recent_messages WHERE username = active_user_row.username ORDER BY id ASC LIMIT 1;
259+
$$;
260+
261+
262+
-- Valid postgresql function override but that produce an unresolvable postgrest function call
263+
create function postgrest_unresolvable_function() returns void language sql as '';
264+
create function postgrest_unresolvable_function(a text) returns int language sql as 'select 1';
265+
create function postgrest_unresolvable_function(a int) returns text language sql as $$
266+
SELECT 'toto'
267+
$$;
268+
-- Valid postgresql function override with differents returns types depending of different arguments
269+
create function postgrest_resolvable_with_override_function() returns void language sql as '';
270+
create function postgrest_resolvable_with_override_function(a text) returns int language sql as 'select 1';
271+
create function postgrest_resolvable_with_override_function(b int) returns text language sql as $$
272+
SELECT 'toto'
273+
$$;
274+
-- Function overrides returning setof tables
275+
create function postgrest_resolvable_with_override_function(profile_id bigint) returns setof user_profiles language sql stable as $$
276+
SELECT * FROM user_profiles WHERE id = profile_id;
277+
$$;
278+
create function postgrest_resolvable_with_override_function(cid bigint, search text default '') returns setof messages language sql stable as $$
279+
SELECT * FROM messages WHERE channel_id = cid AND message = search;
280+
$$;
281+
-- Function override taking a table as argument and returning a setof
282+
create function postgrest_resolvable_with_override_function(user_row users) returns setof messages language sql stable as $$
283+
SELECT * FROM messages WHERE messages.username = user_row.username;
284+
$$;
285+
286+
create or replace function public.polymorphic_function_with_different_return(bool) returns int language sql as 'SELECT 1';
287+
create or replace function public.polymorphic_function_with_different_return(text) returns void language sql as '';
288+
289+
create or replace function public.polymorphic_function_with_no_params_or_unnamed() returns int language sql as 'SELECT 1';
290+
create or replace function public.polymorphic_function_with_no_params_or_unnamed(bool) returns int language sql as 'SELECT 1';
291+
create or replace function public.polymorphic_function_with_no_params_or_unnamed(text) returns void language sql as '';
292+
-- Function with a single unnamed params that isn't a json/jsonb/text should never appears in the type gen as it won't be in postgrest schema
293+
create or replace function public.polymorphic_function_with_unnamed_integer(int) returns int language sql as 'SELECT 1';
294+
create or replace function public.polymorphic_function_with_unnamed_json(json) returns int language sql as 'SELECT 1';
295+
create or replace function public.polymorphic_function_with_unnamed_jsonb(jsonb) returns int language sql as 'SELECT 1';
296+
create or replace function public.polymorphic_function_with_unnamed_text(text) returns int language sql as 'SELECT 1';

test/db/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ services:
2828
POSTGRES_HOST: /var/run/postgresql
2929
POSTGRES_PORT: 5432
3030
pgmeta:
31-
image: supabase/postgres-meta:v0.87.1
31+
image: local-pg-meta
3232
ports:
3333
- '8080:8080'
3434
environment:

0 commit comments

Comments
 (0)