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

Groq Typegen: create types from projections and aliases #7923

Open
EricWVGG opened this issue Dec 1, 2024 · 2 comments
Open

Groq Typegen: create types from projections and aliases #7923

EricWVGG opened this issue Dec 1, 2024 · 2 comments

Comments

@EricWVGG
Copy link

EricWVGG commented Dec 1, 2024

I frequently find myself with a query that's something like this (ultra-simplified example)…

export const someDoc = defineQuery(`
  *[_type == 'someDocument' && slug.current == $slug][0]{
    title,
    links[] -> {
      _id,
     link
    },
  }
`)

On the front end, I can pluck the type for links pretty easily…

const LinkList = ({ links }: Pick<Sanity.SomeDocQuery, 'links'>) => {
  return links.map(link => <Link link={link} />)
}

But I haven't found an easy way to type a single member of the array.

const Link = ({ link }: { link: Sanity.Ummmmmm }) => …

It occurred to me that Sanity's use of projection aliases (I don't know if you have a technical term for that) could solve this.

For example, this could return a new type for the list array…

// query:
  *[_type == 'linkList' && slug.current == $slug][0]{
    title,
    "links": links[] -> {
    // ^ projection alias
      _id,
      link
    },
  }

// generated code:
export type SomeDocQueryResult = {
  title: string
  linkList: SomeDocQueryResultMemberLinks
}
export type SomeDocQueryResultMemberLinks = Array<{
  _id: string
  link: string
}>

Then one could use the fun Member helper…

// tsx
declare type Member<A> = A extends readonly (infer T)[] ? T : never

const Link = ({ link }: { link: Member<Sanity.SomeDocQueryResultMemberLinks> }) => …

Even cooler would be the ability to add an alias to the array member…

// query:
*[_type == 'linkList' && slug.current == $slug][0]{
  title,
  links[] -> "link": { // < member alias
    _id,
    link
  },
}

// generated code:
export type SomeDocQueryResult = {
  title: string
  links: Array<SomeDocQueryResultMemberLink>
}
export type SomeDocQueryResultMemberLink = {
  _id: string
  link: string
}

// tsx
const Link = ({ link }: { link: Sanity.SomeDocQueryResultMemberLink }) => …

… but this solution would require extending Groq itself, which I imagine you wouldn't take lightly.

Anyway, if there's an obvious solution to this that I'm missing, I'd love to hear about it. Otherwise, that's my suggestion.

Thank you for making this tool! It's been a rocky ride, but I would have had to move on from Sanity without it.

@EricWVGG
Copy link
Author

EricWVGG commented Dec 1, 2024

Incidentally, yes, I do see some difficulty in the possibility of redundant aliases within individual queries and across multiple queries (hence naming like SomeDocQueryResultMemberLink in the examples)… IMO that can just be resolved with a huge type error in tyepgen, "all aliases must be unique", no need to overthink that.

@EricWVGG
Copy link
Author

EricWVGG commented Dec 1, 2024

… I did figure out one solution… if I were new to Typescript this would make me give up and join a road crew. Would join the garbage crew if it were deeply nested.

declare type Member<A> = A extends readonly (infer T)[] ? T : never

export const Link = ({ link }: {
  link: Member<NonNullable<Sanity.PageQueryResult>['projects']>
}

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

1 participant