supabase / postgres-meta

A RESTful API for managing your Postgres. Fetch tables, add roles, and run queries
https://supabase.com
Apache License 2.0
939 stars 126 forks source link

feat(typegen): generate types for columns with json_schema constraint #814

Open phmasek opened 1 month ago

phmasek commented 1 month ago

What kind of change does this PR introduce?

New feature to generate the types for JSON columns that are using jsonb_matches_schema check constraints.

Discussed here: https://github.com/orgs/supabase/discussions/30015

What is the current behavior?

The JSON columns are set to an overly generic structure:

string | number | boolean | {
    [key: string]: Json | undefined;
} | Json[]

What is the new behavior?

A JSON column that has a check constraint including the function jsonb_matches_schema or json_matches_schema gets a corresponding type generated based on that specific json schema.

Example

This SQL table:

CREATE TABLE public.memes (
    id serial NOT NULL PRIMARY KEY,
    name text NOT NULL,
    category INTEGER REFERENCES category(id),
    metadata jsonb,
    json_metadata json,
    free_metadata jsonb,
    created_at TIMESTAMP NOT NULL,
    status meme_status DEFAULT 'old'
);

ALTER TABLE public.memes ADD CONSTRAINT json_metadata_schema_check 
CHECK (
  (json_matches_schema('{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "properties": {
      "popularity_score": {
        "type": "integer"
      },
      "name": {
        "type": "string"
      }
    },
    "additionalProperties": false
  }', json_metadata))
);

ALTER TABLE public.memes ADD CONSTRAINT metadata_schema_check 
CHECK (
  (jsonb_matches_schema('{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "properties": {
      "popularity_score": {
        "type": "integer"
      },
      "name": {
        "type": "string"
      },
      "address": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string"
          },
          "street": {
            "type": "string"
          }
        },
        "required": [
          "city",
          "street"
        ]
      }
    },
    "required": [
      "popoularity_score"
    ]
  }', metadata))
);

Receives this typing:

      ...
      memes: {
        Row: {
          category: number | null
          created_at: string
          free_metadata: Json | null
          id: number
          name: string
          status: Database["public"]["Enums"]["meme_status"] | null
          json_metadata:
            | Database["public"]["SchemaTypes"]["memes"]["json_metadata"]
            | null
          metadata:
            | Database["public"]["SchemaTypes"]["memes"]["metadata"]
            | null
        }
      ...

Where the path Database["public"]["SchemaTypes"]["memes"]["json_metadata"] leads to this:

SchemaTypes: {
  memes: {
    json_metadata: {
      popularity_score?: number
      name?: string
    }
    metadata: {
      popularity_score?: number
      name?: string
      address?: {
        city: string
        street: string
        [k: string]: unknown
      }
      [k: string]: unknown
    }
  }
}

Additional context

There is another pull request that address JSON columns in general to move away from the generic type mentioned above. However, that doesn't address the JSON columns that actually have check constraints to enforce a specific type structure.