Skip to content

Commit a551b8e

Browse files
committed
fix(postgrest): computed field over rpc call
Fixes: #1364
1 parent 31761f8 commit a551b8e

File tree

2 files changed

+173
-43
lines changed

2 files changed

+173
-43
lines changed

packages/core/postgrest-js/src/types.ts

Lines changed: 129 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,86 @@
11
import PostgrestError from './PostgrestError'
2-
import { ContainsNull } from './select-query-parser/types'
3-
import { IsAny, SelectQueryError } from './select-query-parser/utils'
2+
import { ContainsNull, TablesAndViews } from './select-query-parser/types'
3+
import { FindMatchingFunctionByArgs, IsAny, SelectQueryError } from './select-query-parser/utils'
4+
import { LastOf } from './select-query-parser/types'
45

56
export type Fetch = typeof fetch
67

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+
type IsNever<T> = [T] extends [never] ? true : false
19+
20+
export type GetRpcFunctionFilterBuilderByArgs<
21+
Schema extends GenericSchema,
22+
FnName extends string & keyof Schema['Functions'],
23+
Args
24+
> = {
25+
0: Schema['Functions'][FnName]
26+
// If the Args is exactly never (function call without any params)
27+
1: IsAny<Schema> extends true
28+
? any
29+
: IsNever<Args> extends true
30+
? ExtractExactFunction<Schema['Functions'][FnName], Args>
31+
: // Otherwise, we attempt to match with one of the function definition in the union based
32+
// on the function arguments provided
33+
Args extends GenericFunction['Args']
34+
? LastOf<FindMatchingFunctionByArgs<Schema['Functions'][FnName], Args>>
35+
: // If we can't find a matching function by args, we try to find one by function name
36+
ExtractExactFunction<Schema['Functions'][FnName], Args> extends GenericFunction
37+
? ExtractExactFunction<Schema['Functions'][FnName], Args>
38+
: any
39+
}[1] extends infer Fn
40+
? // If we are dealing with an non-typed client everything is any
41+
IsAny<Fn> extends true
42+
? { Row: any; Result: any; RelationName: FnName; Relationships: null }
43+
: // Otherwise, we use the arguments based function definition narrowing to get the rigt value
44+
Fn extends GenericFunction
45+
? {
46+
Row: Fn['SetofOptions'] extends GenericSetofOption
47+
? Fn['SetofOptions']['isSetofReturn'] extends true
48+
? TablesAndViews<Schema>[Fn['SetofOptions']['to']]['Row']
49+
: TablesAndViews<Schema>[Fn['SetofOptions']['to']]['Row']
50+
: Fn['Returns'] extends any[]
51+
? Fn['Returns'][number] extends Record<string, unknown>
52+
? Fn['Returns'][number]
53+
: never
54+
: Fn['Returns'] extends Record<string, unknown>
55+
? Fn['Returns']
56+
: never
57+
Result: Fn['SetofOptions'] extends GenericSetofOption
58+
? Fn['SetofOptions']['isSetofReturn'] extends true
59+
? Fn['SetofOptions']['isOneToOne'] extends true
60+
? Fn['Returns'][]
61+
: Fn['Returns']
62+
: Fn['Returns']
63+
: Fn['Returns']
64+
RelationName: Fn['SetofOptions'] extends GenericSetofOption
65+
? Fn['SetofOptions']['to']
66+
: FnName
67+
Relationships: Fn['SetofOptions'] extends GenericSetofOption
68+
? Fn['SetofOptions']['to'] extends keyof Schema['Tables']
69+
? Schema['Tables'][Fn['SetofOptions']['to']]['Relationships']
70+
: Schema['Views'][Fn['SetofOptions']['to']]['Relationships']
71+
: null
72+
}
73+
: // If we failed to find the function by argument, we still pass with any but also add an overridable
74+
Fn extends false
75+
? {
76+
Row: any
77+
Result: { error: true } & "Couldn't infer function definition matching provided arguments"
78+
RelationName: FnName
79+
Relationships: null
80+
}
81+
: never
82+
: never
83+
784
/**
885
* Response format
986
*
@@ -60,9 +137,18 @@ export type GenericNonUpdatableView = {
60137

61138
export type GenericView = GenericUpdatableView | GenericNonUpdatableView
62139

140+
export type GenericSetofOption = {
141+
isSetofReturn?: boolean | undefined
142+
isOneToOne?: boolean | undefined
143+
isNotNullable?: boolean | undefined
144+
to: string
145+
from: string
146+
}
147+
63148
export type GenericFunction = {
64-
Args: Record<string, unknown>
149+
Args: Record<string, unknown> | never
65150
Returns: unknown
151+
SetofOptions?: GenericSetofOption
66152
}
67153

68154
export type GenericSchema = {
@@ -95,12 +181,12 @@ export type SimplifyDeep<Type, ExcludeType = never> = ConditionalSimplifyDeep<
95181
type ConditionalSimplifyDeep<
96182
Type,
97183
ExcludeType = never,
98-
IncludeType = unknown,
184+
IncludeType = unknown
99185
> = Type extends ExcludeType
100186
? Type
101187
: Type extends IncludeType
102-
? { [TypeKey in keyof Type]: ConditionalSimplifyDeep<Type[TypeKey], ExcludeType, IncludeType> }
103-
: Type
188+
? { [TypeKey in keyof Type]: ConditionalSimplifyDeep<Type[TypeKey], ExcludeType, IncludeType> }
189+
: Type
104190
type NonRecursiveType = BuiltIns | Function | (new (...arguments_: any[]) => unknown)
105191
type BuiltIns = Primitive | void | Date | RegExp
106192
type Primitive = null | undefined | string | number | boolean | symbol | bigint
@@ -112,9 +198,9 @@ export type IsValidResultOverride<Result, NewResult, ErrorResult, ErrorNewResult
112198
true
113199
: ErrorResult
114200
: NewResult extends any[]
115-
? ErrorNewResult
116-
: // Neither are arrays - valid
117-
true
201+
? ErrorNewResult
202+
: // Neither are arrays - valid
203+
true
118204
/**
119205
* Utility type to check if array types match between Result and NewResult.
120206
* Returns either the valid NewResult type or an error message type.
@@ -124,23 +210,23 @@ export type CheckMatchingArrayTypes<Result, NewResult> =
124210
Result extends SelectQueryError<string>
125211
? NewResult
126212
: IsValidResultOverride<
127-
Result,
128-
NewResult,
129-
{
130-
Error: 'Type mismatch: Cannot cast array result to a single object. Use .overrideTypes<Array<YourType>> or .returns<Array<YourType>> (deprecated) for array results or .single() to convert the result to a single object'
131-
},
132-
{
133-
Error: 'Type mismatch: Cannot cast single object to array type. Remove Array wrapper from return type or make sure you are not using .single() up in the calling chain'
134-
}
135-
> extends infer ValidationResult
136-
? ValidationResult extends true
137-
? // Preserve the optionality of the result if the overriden type is an object (case of chaining with `maybeSingle`)
138-
ContainsNull<Result> extends true
139-
? NewResult | null
140-
: NewResult
141-
: // contains the error
142-
ValidationResult
143-
: never
213+
Result,
214+
NewResult,
215+
{
216+
Error: 'Type mismatch: Cannot cast array result to a single object. Use .overrideTypes<Array<YourType>> or .returns<Array<YourType>> (deprecated) for array results or .single() to convert the result to a single object'
217+
},
218+
{
219+
Error: 'Type mismatch: Cannot cast single object to array type. Remove Array wrapper from return type or make sure you are not using .single() up in the calling chain'
220+
}
221+
> extends infer ValidationResult
222+
? ValidationResult extends true
223+
? // Preserve the optionality of the result if the overriden type is an object (case of chaining with `maybeSingle`)
224+
ContainsNull<Result> extends true
225+
? NewResult | null
226+
: NewResult
227+
: // contains the error
228+
ValidationResult
229+
: never
144230

145231
type Simplify<T> = T extends object ? { [K in keyof T]: T[K] } : T
146232

@@ -157,25 +243,25 @@ type MergeExplicit<New, Row> = {
157243
? Row[K] extends SelectQueryError<string>
158244
? New[K]
159245
: // Check if the override is on a embedded relation (array)
160-
New[K] extends any[]
161-
? Row[K] extends any[]
162-
? Array<Simplify<MergeDeep<NonNullable<New[K][number]>, NonNullable<Row[K][number]>>>>
163-
: New[K]
164-
: // Check if both properties are objects omitting a potential null union
165-
IsPlainObject<NonNullable<New[K]>> extends true
166-
? IsPlainObject<NonNullable<Row[K]>> extends true
167-
? // If they are, use the new override as source of truth for the optionality
168-
ContainsNull<New[K]> extends true
169-
? // If the override wants to preserve optionality
170-
Simplify<MergeDeep<NonNullable<New[K]>, NonNullable<Row[K]>>> | null
171-
: // If the override wants to enforce non-null result
172-
Simplify<MergeDeep<New[K], NonNullable<Row[K]>>>
173-
: New[K] // Override with New type if Row isn't an object
174-
: New[K] // Override primitives with New type
246+
New[K] extends any[]
247+
? Row[K] extends any[]
248+
? Array<Simplify<MergeDeep<NonNullable<New[K][number]>, NonNullable<Row[K][number]>>>>
249+
: New[K]
250+
: // Check if both properties are objects omitting a potential null union
251+
IsPlainObject<NonNullable<New[K]>> extends true
252+
? IsPlainObject<NonNullable<Row[K]>> extends true
253+
? // If they are, use the new override as source of truth for the optionality
254+
ContainsNull<New[K]> extends true
255+
? // If the override wants to preserve optionality
256+
Simplify<MergeDeep<NonNullable<New[K]>, NonNullable<Row[K]>>> | null
257+
: // If the override wants to enforce non-null result
258+
Simplify<MergeDeep<New[K], NonNullable<Row[K]>>>
259+
: New[K] // Override with New type if Row isn't an object
260+
: New[K] // Override primitives with New type
175261
: New[K] // Add new properties from New
176262
: K extends keyof Row
177-
? Row[K] // Keep existing properties not in New
178-
: never
263+
? Row[K] // Keep existing properties not in New
264+
: never
179265
}
180266

181267
type MergeDeep<New, Row> = Simplify<

packages/core/postgrest-js/test/advanced_rpc.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,3 +1309,47 @@ test('should be able to filter before and after select rpc', async () => {
13091309
}
13101310
`)
13111311
})
1312+
1313+
test('RPC call with subselect and computed field', async () => {
1314+
const res = await postgrest
1315+
.rpc('get_messages_by_username', { search_username: 'supabot' })
1316+
// should be able to select computed field
1317+
.select('message, blurb_message')
1318+
// .limit(1)
1319+
expect(res).toMatchInlineSnapshot(`
1320+
Object {
1321+
"count": null,
1322+
"data": Array [
1323+
Object {
1324+
"blurb_message": "Hel",
1325+
"message": "Hello World 👋",
1326+
},
1327+
Object {
1328+
"blurb_message": "Per",
1329+
"message": "Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.",
1330+
},
1331+
Object {
1332+
"blurb_message": "Som",
1333+
"message": "Some message on channel wihtout details",
1334+
},
1335+
Object {
1336+
"blurb_message": "Som",
1337+
"message": "Some message on channel wihtout details",
1338+
},
1339+
],
1340+
"error": null,
1341+
"status": 200,
1342+
"statusText": "OK",
1343+
}
1344+
`)
1345+
let result: Exclude<typeof res.data, null>
1346+
const ExpectedSchema = z.array(
1347+
z.object({
1348+
message: z.string().nullable(),
1349+
blurb_message: z.string().nullable(),
1350+
})
1351+
)
1352+
let expected: z.infer<typeof ExpectedSchema>
1353+
expectType<TypeEqual<typeof result, typeof expected>>(true)
1354+
ExpectedSchema.parse(res.data)
1355+
})

0 commit comments

Comments
 (0)