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

Automatically infer types of queries using groq and @sanity/codegen #6934

Closed
romeovs opened this issue Jun 14, 2024 · 1 comment
Closed

Automatically infer types of queries using groq and @sanity/codegen #6934

romeovs opened this issue Jun 14, 2024 · 1 comment
Assignees
Labels
typegen Issues related to TypeScript types generation

Comments

@romeovs
Copy link
Contributor

romeovs commented Jun 14, 2024

Is your feature request related to a problem? Please describe.
Currently, I'm using groq tagged templates in my codebase in tandem with sanity typegen generate to generate types. This works well.

const example = groq`
  *[_type == 'post'] {
    title
    author-> {
      name
    }
  }
`

However, to make use of the generated type, I need to manually add the generated type annotations to client.fetch etc. to make use of them:

It would be nice if we could automatically type groq queries without having to manually add the generated type annotations:

import { ExampleQueryResult } from "./generated"
const res = await client.fetch<ExampleQueryResult>(example)

It would be nice if we could automatically type the query variable with the expected result and variable types using a phantom type like so:

type TypedQuery<Res> = string & {
  _result: ExampleQueryResult
}

const example: TypedQuery<ExampleQueryResult>

that way we can automatically infer the type of the query and we don't need the manual type annotation:

function inferredQuery<T>(query: TypedQuery<T>): Promise<T> {
  return client.fetch(query)
}

and we can just use it like so:

// res is typed as ExampleQueryResult here
const res = await inferredQuery(example)

Note: I've left out query parameters here for simplicity, but it would be easy to add them to TypedQuery

To facilitate this, we would need a couple of things.

1. Introduce a TypedQuery type

Introduce a new type TypedQuery that describes a query and with metadata like the return type and variables:

type TypedQuery<R, P> = string & {
  _result?: R
  _params?: P
}

This is a phantom type so does not impact runtime performance.

2. @sanity/codegen should record a map of all query strings

@sanity/codegen should generated a map that uses query strings as keys and has TypedQuery as values. Eg. in the example given above the map would look like:

// the generated type for the query result, this functionality already exists:
export type ExampleQueryResult = {
  title: string | null
  author: {
    name: string | null
  } | null
} | null


export type QueryTypes = {
  "\\n  *[_type == 'post'] {\\n    title\\n    author-> {\\n      name\\n    }\\n  }\\n":
    TypedQuery<ExampleQueryResult>
}

3. Return a TypedQuery from the groq wrapper

Using the type map, is now possible to return a TypedQuery from groq.

Unfortunately [TypeScript currently does not support using template strings as constant type]( parameters to template tags](microsoft/TypeScript#33304), so for this step to work groq should be used as a function:

function groq<T extends keyof QueryTypes>(query: T): QueryTypes[T] {
  return query
}

We would then have to define the query as:

const example = groq(`
  *[_type == 'post'] {
    title
    author-> {
      name
    }
  }
`)

4. Support TypedQuery in client.fetch and similar functions

Add an overload to client.fetch and similar functions that automatically infers the result typed when a TypeQuery is passed, instead of just a query string:

function inferredQuery<T>(query: TypedQuery<T>): Promise<T> {
  return client.fetch(query)
}

Describe the solution you'd like
All of the above steps can be done as separate utility functions and wrappers, except for 3. because @sanity/codegen does not detect a query if it is defined using groq(`...`) as opposed to groq`...` .

What would be need is for findQueriesInSource to parse groq queries when they are defined in the functional style (groq(`...`)).

Additional context

This functionality matches what is in graphql-codegen when using it with the client-preset and this would mimic it.

I would be happy to provide a PR or a POC repo where all of this is working together, but I wanted to check if there is interest in taking @sanity/codegen in this direction first.

@rexxars rexxars added typegen Issues related to TypeScript types generation feature-request labels Jun 14, 2024
@linear linear bot removed typegen Issues related to TypeScript types generation feature-request labels Jun 17, 2024
@rexxars rexxars added the typegen Issues related to TypeScript types generation label Jun 18, 2024
@sgulseth
Copy link
Member

sgulseth commented Jun 18, 2024

Hi! Thanks for the request!

We've been discussing a similar approach ourselves internally, however with a bit different API. We would like the groq package to be independent in itself to the typegen module, so what we've been thinking about is adding a defineQuery(query: string): string-export to groq(For the same reasons with tagged template literals as you mention). Something similar to have I've outlined here, though it requires some changes to the sanity client as well before the playground is happy. We don't have any timeline on it atm, but any contribution would be appreciated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
typegen Issues related to TypeScript types generation
Projects
None yet
Development

No branches or pull requests

3 participants