Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
g12i authored Nov 5, 2024
2 parents e7aeb04 + 2352eb1 commit 438d0ec
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 1 deletion.
74 changes: 73 additions & 1 deletion src/select-query-parser/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import {
CheckDuplicateEmbededReference,
GetFieldNodeResultName,
IsAny,
IsRelationNullable,
ResolveRelationship,
SelectQueryError,
Expand All @@ -33,7 +34,13 @@ export type GetResult<
RelationName,
Relationships,
Query extends string
> = Relationships extends null // For .rpc calls the passed relationships will be null in that case, the result will always be the function return type
> = IsAny<Schema> extends true
? ParseQuery<Query> extends infer ParsedQuery extends Ast.Node[]
? RelationName extends string
? ProcessNodesWithoutSchema<ParsedQuery>
: any
: any
: Relationships extends null // For .rpc calls the passed relationships will be null in that case, the result will always be the function return type
? ParseQuery<Query> extends infer ParsedQuery extends Ast.Node[]
? RPCCallNodes<ParsedQuery, RelationName extends string ? RelationName : 'rpc_call', Row>
: Row
Expand All @@ -47,6 +54,71 @@ export type GetResult<
: ParsedQuery
: never

type ProcessSimpleFieldWithoutSchema<Field extends Ast.FieldNode> =
Field['aggregateFunction'] extends AggregateFunctions
? {
// An aggregate function will always override the column name id.sum() will become sum
// except if it has been aliased
[K in GetFieldNodeResultName<Field>]: Field['castType'] extends PostgreSQLTypes
? TypeScriptTypes<Field['castType']>
: number
}
: {
// Aliases override the property name in the result
[K in GetFieldNodeResultName<Field>]: Field['castType'] extends PostgreSQLTypes // We apply the detected casted as the result type
? TypeScriptTypes<Field['castType']>
: any
}

type ProcessFieldNodeWithoutSchema<Node extends Ast.FieldNode> = IsNonEmptyArray<
Node['children']
> extends true
? {
[K in Node['name']]: Node['children'] extends Ast.StarNode[]
? any[]
: Node['children'] extends Ast.FieldNode[]
? {
[P in Node['children'][number] as GetFieldNodeResultName<P>]: P['castType'] extends PostgreSQLTypes
? TypeScriptTypes<P['castType']>
: any
}[]
: any[]
}
: ProcessSimpleFieldWithoutSchema<Node>

/**
* Processes a single Node without schema and returns the resulting TypeScript type.
*/
type ProcessNodeWithoutSchema<Node extends Ast.Node> = Node extends Ast.StarNode
? any
: Node extends Ast.SpreadNode
? Node['target']['children'] extends Ast.StarNode[]
? any
: Node['target']['children'] extends Ast.FieldNode[]
? {
[P in Node['target']['children'][number] as GetFieldNodeResultName<P>]: P['castType'] extends PostgreSQLTypes
? TypeScriptTypes<P['castType']>
: any
}
: any
: Node extends Ast.FieldNode
? ProcessFieldNodeWithoutSchema<Node>
: any

/**
* Processes nodes when Schema is any, providing basic type inference
*/
type ProcessNodesWithoutSchema<
Nodes extends Ast.Node[],
Acc extends Record<string, unknown> = {}
> = Nodes extends [infer FirstNode extends Ast.Node, ...infer RestNodes extends Ast.Node[]]
? ProcessNodeWithoutSchema<FirstNode> extends infer FieldResult
? FieldResult extends Record<string, unknown>
? ProcessNodesWithoutSchema<RestNodes, Acc & FieldResult>
: FieldResult
: any
: Prettify<Acc>

/**
* Processes a single Node from a select chained after a rpc call
*
Expand Down
2 changes: 2 additions & 0 deletions src/select-query-parser/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
UnionToArray,
} from './types'

export type IsAny<T> = 0 extends 1 & T ? true : false

export type SelectQueryError<Message extends string> = { error: true } & Message

export type GetFieldNodeResultName<Field extends Ast.FieldNode> = Field['alias'] extends string
Expand Down
101 changes: 101 additions & 0 deletions test/select-query-parser/default-inference-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { PostgrestClient } from '../../src/index'
import { expectType } from 'tsd'
import { TypeEqual } from 'ts-expect'

const REST_URL = 'http://localhost:3000'

// Check for PostgrestClient without types provided to the client
{
const postgrest = new PostgrestClient(REST_URL)
const { data } = await postgrest.from('user_profile').select()
expectType<TypeEqual<typeof data, any[] | null>>(true)
}
// basic embeding
{
const postgrest = new PostgrestClient(REST_URL)
const { data } = await postgrest
.from('user_profile')
.select(
'user_id, some_embed(*), another_embed(first_field, second_field, renamed:field), aninnerembed!inner(id, name)'
)
.single()
let result: Exclude<typeof data, null>
let expected: {
user_id: any
some_embed: any[]
another_embed: {
first_field: any
second_field: any
renamed: any
}[]
aninnerembed: {
id: any
name: any
}[]
}
expectType<TypeEqual<typeof result, typeof expected>>(true)
}
// spread operator with stars should return any
{
const postgrest = new PostgrestClient(REST_URL)
const { data } = await postgrest
.from('user_profile')
.select('user_id, some_embed(*), ...spreadstars(*)')
.single()
let result: Exclude<typeof data, null>
let expected: any
expectType<TypeEqual<typeof result, typeof expected>>(true)
}
// nested spread operator with stars should return any
{
const postgrest = new PostgrestClient(REST_URL)
const { data } = await postgrest
.from('user_profile')
.select('user_id, some_embed(*), some_other(id, ...spreadstars(*))')
.single()
let result: Exclude<typeof data, null>
let expected: {
user_id: any
some_embed: any[]
some_other: any[]
}
expectType<TypeEqual<typeof result, typeof expected>>(true)
}
// rpc without types should raise similar results
{
const postgrest = new PostgrestClient(REST_URL)
const { data } = await postgrest.rpc('user_profile').select('user_id, some_embed(*)').single()
let result: Exclude<typeof data, null>
let expected: {
user_id: any
some_embed: any[]
}
expectType<TypeEqual<typeof result, typeof expected>>(true)
}
// check for nested operators
{
const postgrest = new PostgrestClient(REST_URL)
const { data } = await postgrest
.from('user_profile')
.select(
'user_id, some_embed(*), another_embed(first_field, second_field, renamed:field), aninnerembed!inner(id, name), ...spreadwithfields(field_spread, another)'
)
.single()
let result: Exclude<typeof data, null>
let expected: {
user_id: any
some_embed: any[]
another_embed: {
first_field: any
second_field: any
renamed: any
}[]
aninnerembed: {
id: any
name: any
}[]
field_spread: any
another: any
}
expectType<TypeEqual<typeof result, typeof expected>>(true)
}

0 comments on commit 438d0ec

Please sign in to comment.