Skip to content
New issue

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

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

Already on GitHub? # 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
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants