Skip to content

Commit cf81db0

Browse files
avalletesoedirgo
authored andcommitted
fix(types): types result with schema as any
1 parent 1020cdd commit cf81db0

File tree

3 files changed

+171
-1
lines changed

3 files changed

+171
-1
lines changed

src/select-query-parser/result.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import {
1414
CheckDuplicateEmbededReference,
1515
GetFieldNodeResultName,
16+
IsAny,
1617
IsRelationNullable,
1718
ResolveRelationship,
1819
SelectQueryError,
@@ -33,7 +34,13 @@ export type GetResult<
3334
RelationName,
3435
Relationships,
3536
Query extends string
36-
> = Relationships extends null // For .rpc calls the passed relationships will be null in that case, the result will always be the function return type
37+
> = IsAny<Schema> extends true
38+
? ParseQuery<Query> extends infer ParsedQuery extends Ast.Node[]
39+
? RelationName extends string
40+
? ProcessNodesWithoutSchema<ParsedQuery>
41+
: any
42+
: any
43+
: Relationships extends null // For .rpc calls the passed relationships will be null in that case, the result will always be the function return type
3744
? ParseQuery<Query> extends infer ParsedQuery extends Ast.Node[]
3845
? RPCCallNodes<ParsedQuery, RelationName extends string ? RelationName : 'rpc_call', Row>
3946
: Row
@@ -47,6 +54,71 @@ export type GetResult<
4754
: ParsedQuery
4855
: never
4956

57+
type ProcessSimpleFieldWithoutSchema<Field extends Ast.FieldNode> =
58+
Field['aggregateFunction'] extends AggregateFunctions
59+
? {
60+
// An aggregate function will always override the column name id.sum() will become sum
61+
// except if it has been aliased
62+
[K in GetFieldNodeResultName<Field>]: Field['castType'] extends PostgreSQLTypes
63+
? TypeScriptTypes<Field['castType']>
64+
: number
65+
}
66+
: {
67+
// Aliases override the property name in the result
68+
[K in GetFieldNodeResultName<Field>]: Field['castType'] extends PostgreSQLTypes // We apply the detected casted as the result type
69+
? TypeScriptTypes<Field['castType']>
70+
: any
71+
}
72+
73+
type ProcessFieldNodeWithoutSchema<Node extends Ast.FieldNode> = IsNonEmptyArray<
74+
Node['children']
75+
> extends true
76+
? {
77+
[K in Node['name']]: Node['children'] extends Ast.StarNode[]
78+
? any[]
79+
: Node['children'] extends Ast.FieldNode[]
80+
? {
81+
[P in Node['children'][number] as GetFieldNodeResultName<P>]: P['castType'] extends PostgreSQLTypes
82+
? TypeScriptTypes<P['castType']>
83+
: any
84+
}[]
85+
: any[]
86+
}
87+
: ProcessSimpleFieldWithoutSchema<Node>
88+
89+
/**
90+
* Processes a single Node without schema and returns the resulting TypeScript type.
91+
*/
92+
type ProcessNodeWithoutSchema<Node extends Ast.Node> = Node extends Ast.StarNode
93+
? any
94+
: Node extends Ast.SpreadNode
95+
? Node['target']['children'] extends Ast.StarNode[]
96+
? any
97+
: Node['target']['children'] extends Ast.FieldNode[]
98+
? {
99+
[P in Node['target']['children'][number] as GetFieldNodeResultName<P>]: P['castType'] extends PostgreSQLTypes
100+
? TypeScriptTypes<P['castType']>
101+
: any
102+
}
103+
: any
104+
: Node extends Ast.FieldNode
105+
? ProcessFieldNodeWithoutSchema<Node>
106+
: any
107+
108+
/**
109+
* Processes nodes when Schema is any, providing basic type inference
110+
*/
111+
type ProcessNodesWithoutSchema<
112+
Nodes extends Ast.Node[],
113+
Acc extends Record<string, unknown> = {}
114+
> = Nodes extends [infer FirstNode extends Ast.Node, ...infer RestNodes extends Ast.Node[]]
115+
? ProcessNodeWithoutSchema<FirstNode> extends infer FieldResult
116+
? FieldResult extends Record<string, unknown>
117+
? ProcessNodesWithoutSchema<RestNodes, Acc & FieldResult>
118+
: FieldResult
119+
: any
120+
: Prettify<Acc>
121+
50122
/**
51123
* Processes a single Node from a select chained after a rpc call
52124
*

src/select-query-parser/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
UnionToArray,
1111
} from './types'
1212

13+
export type IsAny<T> = 0 extends 1 & T ? true : false
14+
1315
export type SelectQueryError<Message extends string> = { error: true } & Message
1416

1517
export type GetFieldNodeResultName<Field extends Ast.FieldNode> = Field['alias'] extends string
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { PostgrestClient } from '../../src/index'
2+
import { expectType } from 'tsd'
3+
import { TypeEqual } from 'ts-expect'
4+
5+
const REST_URL = 'http://localhost:3000'
6+
7+
// Check for PostgrestClient without types provided to the client
8+
// basic embeding
9+
{
10+
const postgrest = new PostgrestClient(REST_URL)
11+
const { data } = await postgrest
12+
.from('user_profile')
13+
.select(
14+
'user_id, some_embed(*), another_embed(first_field, second_field, renamed:field), aninnerembed!inner(id, name)'
15+
)
16+
.single()
17+
let result: Exclude<typeof data, null>
18+
let expected: {
19+
user_id: any
20+
some_embed: any[]
21+
another_embed: {
22+
first_field: any
23+
second_field: any
24+
renamed: any
25+
}[]
26+
aninnerembed: {
27+
id: any
28+
name: any
29+
}[]
30+
}
31+
expectType<TypeEqual<typeof result, typeof expected>>(true)
32+
}
33+
// spread operator with stars should return any
34+
{
35+
const postgrest = new PostgrestClient(REST_URL)
36+
const { data } = await postgrest
37+
.from('user_profile')
38+
.select('user_id, some_embed(*), ...spreadstars(*)')
39+
.single()
40+
let result: Exclude<typeof data, null>
41+
let expected: any
42+
expectType<TypeEqual<typeof result, typeof expected>>(true)
43+
}
44+
// nested spread operator with stars should return any
45+
{
46+
const postgrest = new PostgrestClient(REST_URL)
47+
const { data } = await postgrest
48+
.from('user_profile')
49+
.select('user_id, some_embed(*), some_other(id, ...spreadstars(*))')
50+
.single()
51+
let result: Exclude<typeof data, null>
52+
let expected: {
53+
user_id: any
54+
some_embed: any[]
55+
some_other: any[]
56+
}
57+
expectType<TypeEqual<typeof result, typeof expected>>(true)
58+
}
59+
// rpc without types should raise similar results
60+
{
61+
const postgrest = new PostgrestClient(REST_URL)
62+
const { data } = await postgrest.rpc('user_profile').select('user_id, some_embed(*)').single()
63+
let result: Exclude<typeof data, null>
64+
let expected: {
65+
user_id: any
66+
some_embed: any[]
67+
}
68+
expectType<TypeEqual<typeof result, typeof expected>>(true)
69+
}
70+
// check for nested operators
71+
{
72+
const postgrest = new PostgrestClient(REST_URL)
73+
const { data } = await postgrest
74+
.from('user_profile')
75+
.select(
76+
'user_id, some_embed(*), another_embed(first_field, second_field, renamed:field), aninnerembed!inner(id, name), ...spreadwithfields(field_spread, another)'
77+
)
78+
.single()
79+
let result: Exclude<typeof data, null>
80+
let expected: {
81+
user_id: any
82+
some_embed: any[]
83+
another_embed: {
84+
first_field: any
85+
second_field: any
86+
renamed: any
87+
}[]
88+
aninnerembed: {
89+
id: any
90+
name: any
91+
}[]
92+
field_spread: any
93+
another: any
94+
}
95+
expectType<TypeEqual<typeof result, typeof expected>>(true)
96+
}

0 commit comments

Comments
 (0)