Skip to content

Commit 861ec34

Browse files
committed
fix: Correctly validate relationship enum values in eq, neq and in methods
1 parent ae2e244 commit 861ec34

File tree

2 files changed

+86
-5
lines changed

2 files changed

+86
-5
lines changed

src/PostgrestFilterBuilder.ts

+38-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import PostgrestTransformBuilder from './PostgrestTransformBuilder'
2-
import { GenericSchema } from './types'
2+
import { GenericSchema, GenericTable } from './types'
33

44
type FilterOperator =
55
| 'eq'
@@ -25,6 +25,34 @@ type FilterOperator =
2525
| 'phfts'
2626
| 'wfts'
2727

28+
// Match relationshop filters with `table.column` syntax and resolve underlying column value.
29+
// If not matched, fallback to generic type.
30+
type ResolveFilterValue<
31+
Tables extends Record<string, GenericTable>,
32+
Row extends Record<string, unknown>,
33+
ColumnName extends string,
34+
Wrapper,
35+
Fallback
36+
> = ColumnName extends `${infer RelationshipTable}.${infer RelationshipColumn}`
37+
? ResolveFilterRelationshipValue<Tables, RelationshipTable, RelationshipColumn>
38+
: ColumnName extends keyof Row
39+
? Wrapper extends Something
40+
? Wrapper<Row[ColumnName]>
41+
: Row[ColumnName]
42+
: Fallback
43+
44+
type ResolveFilterRelationshipValue<
45+
Tables extends Record<string, GenericTable>,
46+
RelationshipTable extends string,
47+
RelationshipColumn extends string
48+
> = RelationshipTable extends keyof Tables
49+
? 'Row' extends keyof Tables[RelationshipTable]
50+
? RelationshipColumn extends keyof Tables[RelationshipTable]['Row']
51+
? Tables[RelationshipTable]['Row'][RelationshipColumn]
52+
: unknown
53+
: unknown
54+
: unknown
55+
2856
export default class PostgrestFilterBuilder<
2957
Schema extends GenericSchema,
3058
Row extends Record<string, unknown>,
@@ -42,7 +70,9 @@ export default class PostgrestFilterBuilder<
4270
*/
4371
eq<ColumnName extends string>(
4472
column: ColumnName,
45-
value: ColumnName extends keyof Row ? NonNullable<Row[ColumnName]> : NonNullable<unknown>
73+
value: ResolveFilterValue<Schema['Tables'], Row, ColumnName> extends never
74+
? NonNullable<unknown>
75+
: NonNullable<ResolveFilterValue<Schema['Tables'], Row, ColumnName>>
4676
): this {
4777
this.url.searchParams.append(column, `eq.${value}`)
4878
return this
@@ -56,7 +86,9 @@ export default class PostgrestFilterBuilder<
5686
*/
5787
neq<ColumnName extends string>(
5888
column: ColumnName,
59-
value: ColumnName extends keyof Row ? Row[ColumnName] : unknown
89+
value: ResolveFilterValue<Schema['Tables'], Row, ColumnName> extends never
90+
? unknown
91+
: ResolveFilterValue<Schema['Tables'], Row, ColumnName>
6092
): this {
6193
this.url.searchParams.append(column, `neq.${value}`)
6294
return this
@@ -234,7 +266,9 @@ export default class PostgrestFilterBuilder<
234266
*/
235267
in<ColumnName extends string>(
236268
column: ColumnName,
237-
values: ColumnName extends keyof Row ? ReadonlyArray<Row[ColumnName]> : unknown[]
269+
values: ResolveFilterValue<Schema['Tables'], Row, ColumnName> extends never
270+
? unknown[]
271+
: ReadonlyArray<ResolveFilterValue<Schema['Tables'], Row, ColumnName>>
238272
): this {
239273
const cleanedValues = Array.from(new Set(values))
240274
.map((s) => {

test/index.test-d.ts

+48-1
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,26 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
2121
expectError(postgrest.from('users').select().eq('username', nullableVar))
2222
}
2323

24-
// `.eq()`, '.neq()' and `.in()` validate value when column is an enum
24+
// `.eq()`, '.neq()' and `.in()` validate provided filter value when column is an enum.
25+
// Behaves the same for simple columns, as well as relationship filters.
2526
{
2627
expectError(postgrest.from('users').select().eq('status', 'invalid'))
2728
expectError(postgrest.from('users').select().neq('status', 'invalid'))
2829
expectError(postgrest.from('users').select().in('status', ['invalid']))
2930

31+
expectError(
32+
postgrest.from('best_friends').select('users!first_user(status)').eq('users.status', 'invalid')
33+
)
34+
expectError(
35+
postgrest.from('best_friends').select('users!first_user(status)').neq('users.status', 'invalid')
36+
)
37+
expectError(
38+
postgrest
39+
.from('best_friends')
40+
.select('users!first_user(status)')
41+
.in('users.status', ['invalid'])
42+
)
43+
3044
{
3145
const { data, error } = await postgrest.from('users').select('status').eq('status', 'ONLINE')
3246
if (error) {
@@ -53,6 +67,39 @@ const postgrest = new PostgrestClient<Database>(REST_URL)
5367
}
5468
expectType<{ status: Database['public']['Enums']['user_status'] | null }[]>(data)
5569
}
70+
71+
{
72+
const { data, error } = await postgrest
73+
.from('best_friends')
74+
.select('users!first_user(status)')
75+
.eq('users.status', 'ONLINE')
76+
if (error) {
77+
throw new Error(error.message)
78+
}
79+
expectType<{ users: { status: Database['public']['Enums']['user_status'] | null } }[]>(data)
80+
}
81+
82+
{
83+
const { data, error } = await postgrest
84+
.from('best_friends')
85+
.select('users!first_user(status)')
86+
.neq('users.status', 'ONLINE')
87+
if (error) {
88+
throw new Error(error.message)
89+
}
90+
expectType<{ users: { status: Database['public']['Enums']['user_status'] | null } }[]>(data)
91+
}
92+
93+
{
94+
const { data, error } = await postgrest
95+
.from('best_friends')
96+
.select('users!first_user(status)')
97+
.in('users.status', ['ONLINE', 'OFFLINE'])
98+
if (error) {
99+
throw new Error(error.message)
100+
}
101+
expectType<{ users: { status: Database['public']['Enums']['user_status'] | null } }[]>(data)
102+
}
56103
}
57104

58105
// can override result type

0 commit comments

Comments
 (0)