Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(types): add json path type inference #592

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

avallete
Copy link
Member

@avallete avallete commented Jan 12, 2025

What kind of change does this PR introduce?

Allow users to override Json type with custom types, and take into account the "json accessor" to infer the proper resulting type.

What is the current behavior?

Given a table defined like so:

type CustomJsonType = {
  foo: string
  bar: {
    baz: number
  }
  en: 'ONE' | 'TWO' | 'THREE'
}
table: {
 data: CustomJsonType | null
}

The following behavior is observed:

select(`data`) -> {data: CustomJsonType | null}
select(`data->bar`) -> {bar: Json}
select(`data->>bar`) -> {bar: string}
select(`data->>en`) -> {bar: string}
select(`data->bar->baz`) -> {baz: Json}

What is the new behavior?

select(`data`) -> {data: CustomJsonType | null}
select(`data->bar`) -> { bar:  { baz: number } }
// Preserve the postgrest casting behaviour due to ->> accessor
select(`data->>bar`) -> {bar: string}
// Preserve the union of string
select(`data->>en`) -> {bar:  'ONE' | 'TWO' | 'THREE'}
// Preserve the accessed ressource type
select(`data->bar->baz`) -> {baz: number}
// Fallback on the default behavior for non-matching properties
select(`data->bar->nope`) -> {nope: Json}
select(`data->bar->>nope`) -> {nope: string}

Additional context

Add any other context or screenshots.

name: Name
alias: PropertyName
castType: PropertyType
jsonPath: JsonPathToAccessor<
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use this to format and save the value "path accessor" information from the parser to the result inference. That way, the parser can still be agnostic to the database types, only caring about string parsing.

@@ -544,3 +544,35 @@ export type FindFieldMatchingRelationships<
name: Field['name']
}
: SelectQueryError<'Failed to find matching relation via name'>

export type JsonPathToAccessor<Path extends string> = Path extends `${infer P1}->${infer P2}`
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turn something like data->some->>property into some.property

? JsonPathToAccessor<P1>
: Path

export type JsonPathToType<T, Path extends string> = Path extends ''
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parse some path like some.property and try to match it with the type T provided.

const { data, error } = await postgrest.from('users').select('status').eq('status', 'ONLINE')
if (error) {
throw new Error(error.message)
const result = await postgrest.from('users').select('status').eq('status', 'ONLINE')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here for some reason (maybe TS upgrade) using this syntax:

const { data, error } = await ...

if (error) {
 throw XXX
}
// here data was still of type data[] | null
// because typescript seemed to fail to understand the branching

Changing to this syntax properly narrow down result.data as non nullable once we checked the error.

@kamilogorek
Copy link
Member

That's already a black-magic level, but validated behavior on our live project and everything works as expected.

@kamilogorek
Copy link
Member

Gentle ping @soedirgo

@soedirgo
Copy link
Member

Seems to not work on embedded tables:

    const result = await postgrest
      .from('messages')
      .select('users(data->bar)')

(I updated the types for public.users.data with CustomUserDataType)

But that can be handled in a separate PR 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants