sanity-io / sanity

Sanity Studio – Rapidly configure content workspaces powered by structured content
https://www.sanity.io
MIT License
4.94k stars 387 forks source link

Typegen not reading `=> @` correctly. #6555

Open lorenzo-del-rosario opened 2 weeks ago

lorenzo-del-rosario commented 2 weeks ago

If you find a security vulnerability, do NOT open an issue. Email security@sanity.io instead.

Describe the bug I need to use a component based approach in my app. Every page has a components field that is an array of different possible objects ((Hero | Cta)[]). I want to resolve the image asset references for the Hero component and get the rest of my components without any modifier applied.

The generated type QUERYResult type contains only part of the queried Hero component and no types for all the other components (Cta)

To Reproduce

Steps to reproduce the behavior:

Groq query

export const HOMEPAGE_QUERY = groq`*[_type == "page" && slug.current == "homepage" && !(_id in path('drafts.**')) ]{
  title,
  slug,
  _type,
  components[] {
    _type == "hero" => { 
      _type != 'image' => @,
      "imageMobile": imageMobile.asset->{
        metadata {
          dimensions{
            width,
            height,
            aspectRatio
          }
        },
        extension,
        url,
        _id
      },
      "imageDesktop": imageDesktop.asset->{
        metadata {
          dimensions{
            width,
            height,
            aspectRatio
          }
        },
        extension,
        url,
        _id
      },
    },
    _type != 'hero' => @,
  }
}`;

Generated types (components) ✅

export type Hero = {
  _type: 'hero'
  title?: string
  subtitle?: string
  imageDesktop?: {
    asset?: {
      _ref: string
      _type: 'reference'
      _weak?: boolean
      [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
    }
    hotspot?: SanityImageHotspot
    crop?: SanityImageCrop
    _type: 'image'
  }
  imageMobile?: {
    asset?: {
      _ref: string
      _type: 'reference'
      _weak?: boolean
      [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
    }
    hotspot?: SanityImageHotspot
    crop?: SanityImageCrop
    _type: 'image'
  }
}

export type Cta = {
  _type: 'cta'
  title?: string
  buttons?: Array<
    {
      _key: string
    } & Button
  >
}

export type Button = {
  _type: 'button'
  linkLabel?: string
  linkURL?: string
}

Generated type (homepage query) ❌

export type HOMEPAGE_QUERYResult = Array<{
  title: string | null;
  slug: Slug | null;
  _type: 'page';
  components: Array<
    | {
        _key: string;
        imageMobile: {
          metadata: {
            dimensions: {
              width: number | null;
              height: number | null;
              aspectRatio: number | null;
            } | null;
          } | null;
          extension: string | null;
          url: string | null;
          _id: string;
        } | null;
        imageDesktop: {
          metadata: {
            dimensions: {
              width: number | null;
              height: number | null;
              aspectRatio: number | null;
            } | null;
          } | null;
          extension: string | null;
          url: string | null;
          _id: string;
        } | null;
      }
    | {
        _key: string;
      }
  > | null;
}>;

Expected behavior

HOMEPAGE_QUERYResult['components'] should match the value returned by the API. Hero should have a title, subtitle, _type and the resolved imageDesktop and imageDesktop references. Since all the orher components are returned in full (_type != 'hero' => @) HOMEPAGE_QUERYResult['components'] should contain all the other component types (Cta).

Screenshots

N/A

Which versions of Sanity are you using?

@sanity/cli (global) 3.40.0 (up to date) @sanity/eslint-config-studio 4.0.0 (up to date) @sanity/vision 3.40.0 (up to date) sanity 3.40.0 (up to date)

What operating system are you using?

iOS

Which versions of Node.js / npm are you running?

10.2.4 v20.11.1

Additional context

The types from the schema are being generated correctly, the only issue is in the GROQ query.

Security issue?

Any security issues should be submitted directly to security@sanity.io. In order to determine whether you are dealing with a security issue, ask yourself these two questions:

sgulseth commented 1 week ago

Hi, thanks for reporting. I'm struggling a bit to reproduce this case. What version of the API are you using to execute the groq query? What does the schema definition for type page looks like?

lorenzo-del-rosario commented 1 week ago

Hi, thanks for reporting. I'm struggling a bit to reproduce this case. What version of the API are you using to execute the groq query? What does the schema definition for type page looks like?

Thank You for looking into it.

Sanity API version: 2023-03-30

The Page schema seems correct:

export type Page = {
  _id: string;
  _type: 'page';
  _createdAt: string;
  _updatedAt: string;
  _rev: string;
  title?: string;
  slug?: Slug;
  components?: Array<
    | ({
        _key: string;
      } & Hero)
    | ({
        _key: string;
      } & Cta)
  >;
};

The issue seems to happen with the narrowing of Hero and Cta when generating the type for the groq query response.

This is the Page schema definition in Sanity Studio:

import { defineField, defineType } from 'sanity';

export default defineType({
  name: 'page',
  title: 'Page',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      title: 'Title',
      type: 'string'
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96
      }
    }),
    defineField({
      name: 'components',
      title: 'Components',
      type: 'array',
      of: [
        {
          name: 'hero',
          type: 'hero'
        },
        {
          name: 'cta',
          type: 'cta'
        }
      ]
    })
  ],

  preview: {
    select: {
      title: 'title',
      author: 'author.name',
      media: 'mainImage'
    },
    prepare(selection) {
      const { author } = selection;
      return { ...selection, subtitle: author && `by ${author}` };
    }
  }
});

Please let me know if you'd like more information.

Or if you suggest any other way to write the query in order for the TypeGen to read it correctly for the time being.

sgulseth commented 1 week ago

Thanks! with the Page schema definition I'm able to reproduce 🙂