Skip to content

Commit a1dff4b

Browse files
committed
wip: tests pass with hand fixed database types
1 parent 3dd1fe3 commit a1dff4b

File tree

4 files changed

+117
-81
lines changed

4 files changed

+117
-81
lines changed

src/PostgrestClient.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
GenericSetofOption,
1010
} from './types'
1111
import { FindMatchingFunctionByArgs, IsAny } from './select-query-parser/utils'
12+
import { LastOf } from './select-query-parser/types'
1213

1314
type ExactMatch<T, S> = [T] extends [S] ? ([S] extends [T] ? true : false) : false
1415

@@ -20,20 +21,21 @@ type ExtractExactFunction<Fns, Args> = Fns extends infer F
2021
: never
2122
: never
2223

24+
type IsNever<T> = [T] extends [never] ? true : false
25+
2326
export type GetRpcFunctionFilterBuilderByArgs<
2427
Schema extends GenericSchema,
2528
FnName extends string & keyof Schema['Functions'],
2629
Args
2730
> = {
2831
0: Schema['Functions'][FnName]
29-
// This is here to handle the case where the args is exactly {} and fallback to the empty
30-
// args function definition if there is one in such case
31-
1: [keyof Args] extends [never]
32+
// If the Args is exactly never (function call without any params)
33+
1: IsNever<Args> extends true
3234
? ExtractExactFunction<Schema['Functions'][FnName], Args>
3335
: // Otherwise, we attempt to match with one of the function definition in the union based
3436
// on the function arguments provided
3537
Args extends GenericFunction['Args']
36-
? FindMatchingFunctionByArgs<Schema['Functions'][FnName], Args>
38+
? LastOf<FindMatchingFunctionByArgs<Schema['Functions'][FnName], Args>>
3739
: // If we can't find a matching function by args, we try to find one by function name
3840
ExtractExactFunction<Schema['Functions'][FnName], Args> extends GenericFunction
3941
? ExtractExactFunction<Schema['Functions'][FnName], Args>
@@ -197,7 +199,7 @@ export default class PostgrestClient<
197199
*/
198200
rpc<
199201
FnName extends string & keyof Schema['Functions'],
200-
Args extends Schema['Functions'][FnName]['Args'] = {},
202+
Args extends Schema['Functions'][FnName]['Args'] = never,
201203
FilterBuilder extends GetRpcFunctionFilterBuilderByArgs<
202204
Schema,
203205
FnName,

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export type GenericSetofOption = {
6868
}
6969

7070
export type GenericFunction = {
71-
Args: Record<string, unknown>
71+
Args: Record<string, unknown> | never
7272
Returns: unknown
7373
SetofOptions?: GenericSetofOption
7474
}

test/advanced_rpc.test.ts

Lines changed: 57 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ describe('advanced rpc', () => {
154154
},
155155
})
156156
let result: Exclude<typeof res.data, null>
157-
let expected: z.infer<typeof UserProfileSchema>
157+
let expected: z.infer<typeof UserProfileSchema>[]
158158
expectType<TypeEqual<typeof result, typeof expected>>(true)
159159
expect(res).toMatchInlineSnapshot(`
160160
Object {
@@ -170,7 +170,7 @@ describe('advanced rpc', () => {
170170
"statusText": "OK",
171171
}
172172
`)
173-
UserProfileSchema.parse(res.data)
173+
UserProfileSchema.array().parse(res.data)
174174
})
175175

176176
test('function with scalar input', async () => {
@@ -440,7 +440,7 @@ describe('advanced rpc', () => {
440440
})
441441
.select('channel_id, message, users(username, catchphrase)')
442442
let result: Exclude<typeof res.data, null>
443-
let expected: z.infer<typeof SelectWithUsersSchema>[]
443+
let expected: RequiredDeep<z.infer<typeof SelectWithUsersSchema>>[]
444444
expectType<TypeEqual<typeof result, typeof expected>>(true)
445445
expect(res).toMatchInlineSnapshot(`
446446
Object {
@@ -938,24 +938,11 @@ describe('advanced rpc', () => {
938938
`)
939939
})
940940

941-
test('polymorphic function with unnamed params definition call with bool param', async () => {
941+
test('polymorphic function with unnamed params definition call with string param', async () => {
942942
const res = await postgrest.rpc('polymorphic_function_with_no_params_or_unnamed', {
943-
// @ts-expect-error should not have generated a type definition for the boolean
944-
'': true,
943+
'': '',
945944
})
946945
let result: Exclude<typeof res.data, null>
947-
// TODO: since this call use an invalid type definition, we can't distinguish between the "no values" or the "empty"
948-
// property call:
949-
// polymorphic_function_with_no_params_or_unnamed:
950-
// | {
951-
// Args: Record<PropertyKey, never>
952-
// Returns: number
953-
// }
954-
// | {
955-
// Args: { '': string }
956-
// Returns: string
957-
// }
958-
// A type error would be raised at higher level (argument providing) time though
959946
let expected: string
960947
expectType<TypeEqual<typeof result, typeof expected>>(true)
961948
expect(res).toMatchInlineSnapshot(`
@@ -1009,22 +996,22 @@ describe('advanced rpc', () => {
1009996
})
1010997

1011998
test('polymorphic function with unnamed default int param', async () => {
1012-
const res = await postgrest.rpc('polymorphic_function_with_unnamed_default', {
1013-
//@ts-expect-error the type definition for empty params should be text
1014-
'': 123,
1015-
})
999+
const res = await postgrest.rpc('polymorphic_function_with_unnamed_default', undefined)
10161000
let result: Exclude<typeof res.data, null>
1017-
// TODO: since this call use an invalid type definition, we can't distinguish between the "no values" or the "empty"
1018-
// A type error would be raised at higher level (argument providing) time though
10191001
let expected: string
10201002
expectType<TypeEqual<typeof result, typeof expected>>(true)
10211003
expect(res).toMatchInlineSnapshot(`
10221004
Object {
10231005
"count": null,
1024-
"data": "foo",
1025-
"error": null,
1026-
"status": 200,
1027-
"statusText": "OK",
1006+
"data": null,
1007+
"error": Object {
1008+
"code": "PGRST203",
1009+
"details": null,
1010+
"hint": "Try renaming the parameters or the function itself in the database so function overloading can be resolved",
1011+
"message": "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default(), public.polymorphic_function_with_unnamed_default( => text)",
1012+
},
1013+
"status": 300,
1014+
"statusText": "Multiple Choices",
10281015
}
10291016
`)
10301017
})
@@ -1069,22 +1056,22 @@ describe('advanced rpc', () => {
10691056
})
10701057

10711058
test('polymorphic function with unnamed default overload int param', async () => {
1072-
const res = await postgrest.rpc('polymorphic_function_with_unnamed_default_overload', {
1073-
//@ts-expect-error the type definition for empty params should be text
1074-
'': 123,
1075-
})
1059+
const res = await postgrest.rpc('polymorphic_function_with_unnamed_default_overload', undefined)
10761060
let result: Exclude<typeof res.data, null>
1077-
// TODO: since this call use an invalid type definition, we can't distinguish between the "no values" or the "empty"
1078-
// A type error would be raised at higher level (argument providing) time though
10791061
let expected: string
10801062
expectType<TypeEqual<typeof result, typeof expected>>(true)
10811063
expect(res).toMatchInlineSnapshot(`
10821064
Object {
10831065
"count": null,
1084-
"data": "foo",
1085-
"error": null,
1086-
"status": 200,
1087-
"statusText": "OK",
1066+
"data": null,
1067+
"error": Object {
1068+
"code": "PGRST203",
1069+
"details": null,
1070+
"hint": "Try renaming the parameters or the function itself in the database so function overloading can be resolved",
1071+
"message": "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default_overload(), public.polymorphic_function_with_unnamed_default_overload( => text)",
1072+
},
1073+
"status": 300,
1074+
"statusText": "Multiple Choices",
10881075
}
10891076
`)
10901077
})
@@ -1130,15 +1117,7 @@ describe('advanced rpc', () => {
11301117
})
11311118

11321119
test('function with blurb_message', async () => {
1133-
// @ts-expect-error the function types doesn't exist and should fail to be retrieved by cache
1134-
// for direct rpc call
1135-
const res = await postgrest.rpc('blurb_messages', {
1136-
channel_id: 1,
1137-
data: null,
1138-
id: 1,
1139-
message: 'Hello World 👋',
1140-
username: 'supabot',
1141-
})
1120+
const res = await postgrest.rpc('blurb_message')
11421121
let result: Exclude<typeof res.data, null>
11431122
let expected: never
11441123
expectType<TypeEqual<typeof result, typeof expected>>(true)
@@ -1148,9 +1127,38 @@ describe('advanced rpc', () => {
11481127
"data": null,
11491128
"error": Object {
11501129
"code": "PGRST202",
1151-
"details": "Searched for the function public.blurb_messages with parameters channel_id, data, id, message, username or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.",
1130+
"details": "Searched for the function public.blurb_message without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.",
1131+
"hint": "Perhaps you meant to call the function public.get_messages",
1132+
"message": "Could not find the function public.blurb_message without parameters in the schema cache",
1133+
},
1134+
"status": 404,
1135+
"statusText": "Not Found",
1136+
}
1137+
`)
1138+
})
1139+
test('function with blurb_message with params', async () => {
1140+
const res = await postgrest.rpc('blurb_message', {
1141+
'': {
1142+
channel_id: 1,
1143+
data: null,
1144+
id: 1,
1145+
message: null,
1146+
username: 'test',
1147+
blurb_message: null,
1148+
},
1149+
})
1150+
let result: Exclude<typeof res.data, null>
1151+
let expected: SelectQueryError<'the function public.blurb_message with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache'>
1152+
expectType<TypeEqual<typeof result, typeof expected>>(true)
1153+
expect(res).toMatchInlineSnapshot(`
1154+
Object {
1155+
"count": null,
1156+
"data": null,
1157+
"error": Object {
1158+
"code": "PGRST202",
1159+
"details": "Searched for the function public.blurb_message with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.",
11521160
"hint": "Perhaps you meant to call the function public.get_messages",
1153-
"message": "Could not find the function public.blurb_messages(channel_id, data, id, message, username) in the schema cache",
1161+
"message": "Could not find the function public.blurb_message() in the schema cache",
11541162
},
11551163
"status": 404,
11561164
"statusText": "Not Found",

test/types.generated.ts

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -545,10 +545,12 @@ export type Database = {
545545
Functions: {
546546
blurb_message: {
547547
Args: { '': Database['public']['Tables']['messages']['Row'] }
548-
Returns: string
548+
Returns: {
549+
error: true
550+
} & 'the function public.blurb_message with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache'
549551
}
550552
function_returning_row: {
551-
Args: Record<PropertyKey, never>
553+
Args: never
552554
Returns: {
553555
age_range: unknown | null
554556
catchphrase: unknown | null
@@ -558,7 +560,7 @@ export type Database = {
558560
}
559561
}
560562
function_returning_set_of_rows: {
561-
Args: Record<PropertyKey, never>
563+
Args: never
562564
Returns: {
563565
age_range: unknown | null
564566
catchphrase: unknown | null
@@ -659,6 +661,11 @@ export type Database = {
659661
message: string | null
660662
username: string | null
661663
}[]
664+
SetofOptions: {
665+
from: '*'
666+
to: 'recent_messages'
667+
isOneToOne: false
668+
}
662669
}
663670
get_status: {
664671
Args: { name_param: string }
@@ -701,7 +708,7 @@ export type Database = {
701708
Returns: {
702709
id: number
703710
username: string | null
704-
}
711+
}[]
705712
SetofOptions: {
706713
from: 'users'
707714
to: 'user_profiles'
@@ -764,27 +771,36 @@ export type Database = {
764771
Args: { name_param: string }
765772
Returns: Database['public']['Enums']['user_status']
766773
}
767-
polymorphic_function_with_different_return:
768-
| { Args: { '': boolean }; Returns: number }
769-
| { Args: { '': number }; Returns: number }
770-
| { Args: { '': string }; Returns: string }
774+
polymorphic_function_with_different_return: { Args: { '': string }; Returns: string }
771775
polymorphic_function_with_no_params_or_unnamed:
772-
| { Args: Record<PropertyKey, never>; Returns: number }
773-
| { Args: { '': boolean }; Returns: number }
776+
| { Args: never; Returns: number }
774777
| { Args: { '': string }; Returns: string }
775778
polymorphic_function_with_unnamed_default:
776-
| { Args: Record<PropertyKey, never>; Returns: number }
777-
| { Args: { ''?: number }; Returns: number }
778-
| { Args: { ''?: string }; Returns: string }
779+
| {
780+
Args: { ''?: string }
781+
Returns: string
782+
}
783+
| {
784+
Args: never
785+
Returns: {
786+
error: true
787+
} & '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'
788+
}
779789
polymorphic_function_with_unnamed_default_overload:
780-
| { Args: Record<PropertyKey, never>; Returns: number }
781-
| { Args: { ''?: number }; Returns: number }
782-
| { Args: { ''?: string }; Returns: string }
783-
| { Args: { ''?: boolean }; Returns: number }
784-
polymorphic_function_with_unnamed_integer: {
785-
Args: { '': number }
786-
Returns: number
787-
}
790+
| {
791+
Args: never
792+
Returns: {
793+
error: true
794+
} & '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'
795+
}
796+
| {
797+
Args: { ''?: string }
798+
Returns: string
799+
}
800+
// polymorphic_function_with_unnamed_integer: {
801+
// Args: { ''?: any };
802+
// Returns: number
803+
// }
788804
polymorphic_function_with_unnamed_json: {
789805
Args: { '': Json }
790806
Returns: number
@@ -798,7 +814,7 @@ export type Database = {
798814
Returns: number
799815
}
800816
postgrest_resolvable_with_override_function:
801-
| { Args: Record<PropertyKey, never>; Returns: undefined }
817+
| { Args: never; Returns: undefined }
802818
| { Args: { a: string }; Returns: number }
803819
| { Args: { b: number }; Returns: string }
804820
| {
@@ -834,9 +850,19 @@ export type Database = {
834850
}
835851
}
836852
postgrest_unresolvable_function:
837-
| { Args: Record<PropertyKey, never>; Returns: undefined }
838-
| { Args: { a: string }; Returns: number }
839-
| { Args: { a: number }; Returns: string }
853+
| { Args: never; Returns: undefined }
854+
| {
855+
Args: { a: string }
856+
Returns: {
857+
error: true
858+
} & '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'
859+
}
860+
| {
861+
Args: { a: number }
862+
Returns: {
863+
error: true
864+
} & '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'
865+
}
840866
set_users_offline: {
841867
Args: { name_param: string }
842868
Returns: {
@@ -847,7 +873,7 @@ export type Database = {
847873
username: string
848874
}[]
849875
}
850-
void_func: { Args: Record<PropertyKey, never>; Returns: undefined }
876+
void_func: { Args: never; Returns: undefined }
851877
}
852878
Enums: {
853879
user_status: 'ONLINE' | 'OFFLINE'

0 commit comments

Comments
 (0)