supabase / supabase-js

An isomorphic Javascript client for Supabase. Query your Supabase database, subscribe to realtime events, upload and download files, browse typescript examples, invoke postgres functions via rpc, invoke supabase edge functions, query pgvector.
https://supabase.com
MIT License
3.25k stars 269 forks source link

Json type: Type instantiation is excessively deep and possibly infinite. #808

Open michaelpumo opened 1 year ago

michaelpumo commented 1 year ago

Bug report

Describe the bug

I have a Vue/Nuxt project with TypeScript which is throwing me an error with the profile table when I try to assign it to a variable based on the types generated from the DB. "Type instantiation is excessively deep and possibly infinite."

The problematic column in question is one called "links" which stores JSONB data. The type that is generated from the Supabase Cli using the command npx supabase gen types typescript creates a self referencing type called Json.

export type Json =
  | string
  | number
  | boolean
  | null
  | { [key: string]: Json | undefined }
  | Json[]

With my (generated) profile row types looking like:

profile: {
  Row: {
    about: string | null
    available: string | null
    avatar: string | null
    avatar_url: string | null
    created_at: string | null
    id: number
    links: Json | null // this is the problematic column.
    location: string | null
    name: string | null
    poster: string | null
    poster_url: string | null
    tagline: string | null
    updated_at: string
    user_id: string
    username: string | null
  }
}

In my Pinia Vue store I get the following error when trying to assign it against these types.

import type { Database } from '@/types/supabase'
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    profile: null as Database['public']['Tables']['profile']['Row'] | null
  }),
  actions: {
    setProfile(profile: Database['public']['Tables']['profile']['Row'] | null) {
      this.profile = profile // Here is where the TS error occurs.
    }
  }
})

I'm guessing this error happens because the generated type is self referencing and TS does not know how deep it would be?

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Create a column and set its type to jsonb
  2. Generate types using the Supabase Cli
  3. Try to do something similar and see that TS complains.

Expected behavior

There should be no TS error.

Screenshots

Screenshot 2023-07-15 at 21 02 12 Screenshot 2023-07-15 at 21 16 15

System information

Additional context

I have not been able to see similar issues with other Supabase users but perhaps the use of JSONB is quite rare?

rahulretnan commented 1 year ago

I'm also facing same issue

soedirgo commented 1 year ago

Hey there, what version of TypeScript are you on? Also, can you put up an example project that demonstrates the issue so I can replicate it?

soedirgo commented 1 year ago

FWIW this seems to run fine: https://www.typescriptlang.org/play?#code/C4TwDgpgBAUgzgewHZQLwCgpQD5TsAJwEskBzTHKJAVwFsAjCAi3ehBAGwgEMkWrqHDvwDeUANoBrCCABceQiVIBdefGSVqSACYQAZiQjaoAX37qk45UA

michaelpumo commented 1 year ago

I did a little more digging and I think it may be an issue in combination with Vue's reactivity and not directly Supabase afterall. You may see something similar with React?

If I try to a assign to a simple variable with let for example it's okay but using ref() in Vue causes the warning, possibly due to Vue / TS not knowing how deep it would have to watch the reactive object.

If I use shallowRef() instead, it's fine: https://vuejs.org/api/reactivity-advanced.html#shallowref

LeOndaz commented 10 months ago

Bump

antoniormrzz commented 9 months ago

The Json type does reference itself. Could it be that? I've also been having the same problem with the Json type columns.

Edit: changed my generated Json type to this and it all went away:

export type Json =
  | string
  | number
  | boolean
  | null
  | { [key: string]: any | undefined }
  | any[]

The error is complaining in my opinion that the line | { [key: string]: Json | undefined } has no end to type instantiation, which when you think about it, is true.

ScreamZ commented 8 months ago

Same issue here does anyone find a good and secure workaround?

I did a little more digging and I think it may be an issue in combination with Vue's reactivity and not directly Supabase afterall. You may see something similar with React?

If I try to a assign to a simple variable with let for example it's okay but using ref() in Vue causes the warning, possibly due to Vue / TS not knowing how deep it would have to watch the reactive object.

If I use shallowRef() instead, it's fine: vuejs.org/api/reactivity-advanced.html#shallowref

Any way to manage this in pinia store without using composition ?

Grovespaz commented 6 months ago

Just wanted to confirm this is still an issue at time of writing with the latest version of Vue and supabase-js and makes it impossible to work with supabase-js in any serious capacity when using Typescript and Vue.

shanehoban commented 6 months ago

Fix I'm using is to update your supabase generated types to have Json as below:

export type Json = Record<string, any>
encima commented 6 months ago

Bringing the comments from the related issue made for the CLI here.

Summary of suggestions:

  1. @daniel-j-h suggests to create all Json types as unknown and allow the developer to decide
  2. @shanehoban suggests creating a Json type as Record with a string key and any value
  3. @lauri865 suggests using the MergeDeep from the typefest library
  4. @antoniormrzz suggests changing the Json type to any
  5. @michaelpumo suggests that this could be a combination of Supabase's type generation coupled with Vue's reference handling for watching objects
Keagel commented 5 months ago

Using this StackOverflow post as an example, I have modified it for the Json type:

export type Json<D extends number = 9, DA extends any[] = []> =
    | string
    | number
    | boolean
    | null
    | (D extends DA['length'] ? any : { [key: string]: Json<D, [0, ...DA]> | undefined })
    | (D extends DA['length'] ? any : Json<D, [0, ...DA]>[]);

D defines the depth limit. When the depth limit is reached (D extends DA['length']), any is used to stop the recursion. Might require some testing but it works for me and retains type safety.

eybel commented 4 months ago

Its probably that loop of autoreferencing without finishing the main json reference.

@encima

I would suggest the following solution, since I implemented this, I’ve never had a problem:


type Primitive = string | number | boolean | null;

type JsonObject = { [key: string]: Json | undefined };

type JsonArray = Json[];

export type Json = Primitive | JsonObject | JsonArray;
imrim12 commented 3 months ago

Fix I'm using is to update your supabase generated types to have Json as below:

export type Json = Record<string, any>

This seems to be the easiest way to do it, not super type-safe, but it does the job I have write a short script to properly replace the typescript code without using regex, we can run it with esno or jiti, eslint command is included

https://gist.github.com/imrim12/669d736f68f08c44470e63595eeb42ab

EssPow commented 1 week ago

I gotta say I love supabase a ton. Such an amazing platform with fantastic UI/UX and so many great features. This Json typing issue has been the number 2 biggest hurdle for me in working with the supabase libraries. Number 1 was definitely database transactions (plpgsql ftl).

I remember seeing a PR up a while ago to swap the Json type for an unknown in the generated types. That would be quick and easy fix for this, and it could be behind a flag in the types generation script. Can't find the PR anymore though... I'm just trying to avoid manually updating my generated types every time I re-generate them via cli.

Are there any plans on the supabase side to build Json field typing on top of Postgres? Like a Json column could have an expected schema which could be defined in the UI. This could then be used for types generation. Perhaps schema validation if you wanted to get fancy with it, but I assume that's crossing into a territory the platform may not want to go toward.