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

Typegen: Opening reference always resolves to null #7858

Open
heggemsnes opened this issue Nov 21, 2024 · 0 comments
Open

Typegen: Opening reference always resolves to null #7858

heggemsnes opened this issue Nov 21, 2024 · 0 comments

Comments

@heggemsnes
Copy link

Describe the bug

Opening a reference in a specific scenario just typegens to "null".

export type FullPortableTextQueryTypeResult = {
  content: Array<{
    _key: string;
    children?: Array<{
      marks?: Array<string>;
      text?: string;
      _type: "span";
      _key: string;
    }>;
    style?: "h2" | "h3" | "h4" | "normal";
    listItem?: "bullet" | "number";
    markDefs: Array<{
      file: {
        asset?: {
          _ref: string;
          _type: "reference";
          _weak?: boolean;
          [internalGroqTypeReferenceTo]?: "sanity.fileAsset";
        };
        _type: "file";
      };
      _type: "downloadLinkObject";
      _key: string;
      url: string | null;
    } | {
      // Always null here
      internalLink: null;
      _type: "internalLinkObject";
      _key: string;
      // Correct internalGroqReference etc here
      baseInternalLink: InternalLink;
    } | {
      href: string;
      _type: "link";
      _key: string;
      url: string;
    }> | null;
    level?: number;
    _type: "block";
  }> | null;
} | null;

We have a quite dynamic way of handling "internal links" which I will explain:

import {DocumentTextIcon} from '@sanity/icons'
import {defineField, defineType} from 'sanity'
import {portableTextField} from '../portable-text/portable-text.schema'

export const post = defineType({
  name: 'post',
  title: 'Posts',
  icon: DocumentTextIcon,
  type: 'document',
  options: {
    linkable: true,
  },
  fields: [
    defineField({
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      description: 'A slug is required for the post to show up in the preview',
      options: {
        source: 'title',
        maxLength: 96,
        isUnique: (value, context) => context.defaultIsUnique(value, context),
      },
      validation: (rule) => rule.required(),
    }),
    portableTextField({
      includeHeadings: true,
      includeLinks: true,
      includeLists: true,
    }),
  ],
})

The portableTextField function looks like this:

import type {ArrayDefinition} from 'sanity'
import {defineField} from 'sanity'
import {FieldDef} from './field.type'
import {internalLinkObjectField} from './internal-link-object.field'
import {externalLinkObjectField} from './external-link-object.field'
import {downloadLinkObjectField} from './download-link-object.field'

type PortableText = FieldDef<Omit<ArrayDefinition, 'of'>> & {
  includeBlocks?: string[]
  includeLists?: boolean
  includeLinks?: boolean
  includeHeadings?: boolean
}

type DefaultName = Omit<PortableText, 'name'> & {
  name?: never
  noContent?: never
}

type CustomName = PortableText & {
  noContent: true
}

export type PortableTextFieldProps<NoContent = boolean> = NoContent extends true
  ? CustomName
  : DefaultName

// HEADINGS
/* Will be included if includeHeadings == true */
const headingStyles = [
  {title: 'H2', value: 'h2'},
  {title: 'H3', value: 'h3'},
  {title: 'H4', value: 'h4'},
]

// LISTS
/* Will be included if includeLists == true */
const listObjects = [
  {title: 'Punktliste', value: 'bullet'},
  {title: 'Nummerert liste', value: 'number'},
]

// LINKS (always included)
const linkObjects = [internalLinkObjectField, externalLinkObjectField, downloadLinkObjectField]

// DECORATORS (always included)
const basicDecorators = [
  {title: 'Fet', value: 'strong'},
  {title: 'Kursiv', value: 'em'},
]

export const portableTextField = (props?: PortableTextFieldProps) => {
  const {
    includeBlocks,
    includeHeadings,
    includeLists,
    includeLinks = true,
    options,
    required,
    validation,
  } = props ?? {}

  const styles = []
  if (includeHeadings) styles.push(...headingStyles)

  return defineField({
    ...props,
    name: props?.noContent ? props.name : 'content',
    title: props?.title ?? 'Innhold',
    type: 'array',
    of: [
      {
        type: 'block',
        styles: styles,
        lists: includeLists ? listObjects : [],
        marks: {
          decorators: basicDecorators,
          annotations: includeLinks ? linkObjects : [],
        },
      },
      ...(includeBlocks ? includeBlocks.map((type) => ({type})) : []),
    ],
    validation: validation
      ? validation
      : (Rule) => {
          const rules = []
          if (required) rules.push(Rule.required().error())
          return rules
        },
    options: {
      ...options,
    },
  })
}

The internalLinkObject is like this:

import {LinkIcon} from '@sanity/icons'
import {defineField} from 'sanity'

export const internalLinkObjectField = defineField({
  name: 'internalLinkObject',
  title: 'Intern link',
  type: 'object',
  icon: LinkIcon,
  fields: [
    defineField({
      name: 'internalLink',
      title: 'Velg dokument',
      type: 'internalLink',
      options: {
        disableNew: true,
        required: true,
      },
      validation: (Rule) => Rule.required(),
    }),
  ],
  options: {
    collapsible: false,
    modal: {
      type: 'popover',
      width: 2,
    },
  },
})

Using a internalLink field:

import {defineField} from 'sanity'
import {LINKABLE_TYPES} from '../linkable-types'

export const internalLinkSchema = defineField({
  name: 'internalLink',
  title: 'Velg dokument',
  type: 'reference',
  to: LINKABLE_TYPES.map((type) => {
    return {type}
  }),
  options: {
    disableNew: true,
  },
})

Based on a linkableType export

import * as allDocumentSchemas from './documents'

export const LINKABLE_TYPES = Object.values(allDocumentSchemas)
  .filter((schemaType) => schemaType?.options?.linkable)
  .map((schemaType) => schemaType.name)

Now in my frontend I am using this to create a base portableTextQuery that we can reuse. The last is used only for creating a correct type with "all options".

import { defineQuery } from "next-sanity";

// @sanity-typegen-ignore
const linkInPortableTextQuery = defineQuery(`
  "url": href
`);

// @sanity-typegen-ignore
const internalLinkObjectInPortableTextQuery = defineQuery(`
  internalLink-> {
    _type,
    "slug": slug.current
  },
  "baseInternalLink": internalLink
`);

// @sanity-typegen-ignore
const downloadLinkObjectInPortableTextQuery = defineQuery(`
  "url": file.asset->url
`);

// @sanity-typegen-ignore
export const portableTextInnerQuery = defineQuery(`
  ...,
  markDefs[] {
    ...,
    _type == "link" => {
      ${linkInPortableTextQuery}
    },
    _type == "internalLinkObject" => {
      ${internalLinkObjectInPortableTextQuery}
    },
    _type == "downloadLinkObject" => {
      ${downloadLinkObjectInPortableTextQuery}
    }
  }
`);

// @sanity-typegen-ignore
export const portableTextQuery = defineQuery(`
  content[] {
    _key,
    _type == "block" => {
      ${portableTextInnerQuery}
    }
  }
`);

const fullPortableTextQueryType = defineQuery(`
  *[_type == "post"][0]{
    ${portableTextQuery}
  }
`);

To Reproduce

Steps to reproduce the behavior:

Se reproduction here: https://github.com/heggemsnes/sanity-template-nextjs-clean/tree/internal-link-resolves-to-null

  1. Add project ID
  2. Run npm run extract-types in studio folder
  3. Run npm run typegen in nextjs-app folder
  4. Notice problem in sanity-types.ts under the FullPortableTextQueryTypeResult around line 422

Expected behavior

Be resolved to the actual type. Other places we use this we get the resolved value like:
(Example from bigger project)

{
      internalLink: {
        _type: "applicationPage";
        slug: string | null;
      } | {
        _type: "article";
        slug: string;
      } | {
        _type: "articleArchive";
        slug: string | null;
      } | {
        _type: "country";
        slug: string;
      } | {
        _type: "countryArchive";
        slug: string | null;
      } | {
        _type: "course";
        slug: string;
      } | {
        _type: "courseArchive";
        slug: string | null;
      } | {
        _type: "event";
        slug: string;
      } | {
        _type: "eventArchive";
        slug: string | null;
      } | {
        _type: "faq";
        slug: string;
      } | {
        _type: "faqArchive";
        slug: string | null;
      } | {
        _type: "frontPage";
        slug: null;
      } | {
        _type: "infoArchive";
        slug: string | null;
      } | {
        _type: "infoPage";
        slug: string;
      } | {
        _type: "location";
        slug: string;
      } | {
        _type: "page";
        slug: string;
      } | {
        _type: "pastEventArchive";
        slug: string | null;
      } | {
        _type: "program";
        slug: string;
      } | {
        _type: "programArchive";
        slug: string | null;
      } | {
        _type: "studentBlog";
        slug: string;
      } | {
        _type: "studentBlogArchive";
        slug: string | null;
      } | {
        _type: "studentBloggersArchive";
        slug: string | null;
      } | {
        _type: "testPage";
        slug: string | null;
      } | {
        _type: "university";
        slug: string;
      } | {
        _type: "universityArchive";
        slug: string | null;
      };
      _type: "internalLinkObject";
      _key: string;
    }

Which versions of Sanity are you using?

@sanity/cli (global) 3.62.0 (latest: 3.64.2)
@sanity/assist 3.0.8 (up to date)
@sanity/eslint-config-studio 4.0.0 (up to date)
@sanity/icons 3.4.0 (up to date)
@sanity/vision 3.61.0 (latest: 3.64.2)
sanity 3.62.2 (latest: 3.64.2)

(Tested with 3.64.2 as well)

What operating system are you using?
MacOS 14.3.1

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

10.2.4
v20.11.0

@rexxars rexxars added the typegen Issues related to TypeScript types generation label Nov 21, 2024
@linear linear bot removed the typegen Issues related to TypeScript types generation label Jan 3, 2025
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

No branches or pull requests

2 participants