groqd
groqd copied to clipboard
Potential gaps in 0.x to 1.x migration guide
Is there an existing issue for this?
- [x] I have searched the existing issues
Code of Conduct
- [x] I agree to follow this project's Code of Conduct
Question
Hi there,
Thanks for the work on this.
I'm inheriting a project from another agency and they're using groqd 0.x.
I'm looking to migrate to 1.x but struggling on a couple areas, and I can't see guidance on migrating them in the current 0.x to 1.x guide.
I wanted to note a few things that seem to be missing, and ask for advice on migrating them:
- How to migrate from the previously exported
sanityImage. Is the expectation to write our own fragment for this now? All good if so, I just can't find an answer in the docs. I looked here but seems there's currently still an issue so wasn't sure what I should be doing - I've seen uses of
q("").grab$()in the codebase. Does this now become justq.project()? - Similarly I've seen
.filter(), howeverfilter()requires now 1 parameter—how is this migrated? E.G. from the old docsq("types").filter().deref().grab({ name: q.string() }), - This code also imports some types:
Selection,InferTypeandTypeFromSelection, which are no longer exported. What's the migration path for these?
I apologise if any of these should be obvious. I'm new to Groqd.
I will try to answer some of these because I am also in the middle of migrating.
How to migrate from the previously exported sanityImage
I created a fragment with the fields I need (I mainly need the sanity cdn URL and some metadata dimensions with deref), but you could add any field you need and copy the structure from the sanity type gen.
export const sanityImageFragment = q
.fragment<{
asset?: {
_ref: string
_type: 'reference'
_weak?: boolean
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
}
_type: 'image'
}>()
.project((sub) => ({
url: sub.field('asset').deref().field('url'),
metadata: sub
.field('asset')
.deref()
.project({
width: ['metadata.dimensions.width', q.number()],
height: ['metadata.dimensions.height', q.number()],
})
.notNull(),
}))
// and then in project
.project({
...
image: sub.field('image').project(sanityImageFragment),
...
})
q("").grab$()
Will be
q.star.filterByType('something') // or something similar like sub.field() ...
.project({
name: q.default(q.string(), "DEFAULT")
}),
q("types").filter().deref().grab({ name: q.string() }),
q("...") should become .field("...") so the above will be
.project(sub => ({
types: sub.field("types[]").deref().project({
name: q.string(),
}),
}))
You don't need to .filter() after .field because field will automatically do the groq string [fieldname]. You only need to filter if you want to additionally filter the subquery like
sub
.field('content[]')
.filterByType('content-block-name-1', 'content-block-name-2')
.project({...})
Selection, InferType and TypeFromSelection
A selection is now a fragment and to generate a type from a fragment, you can do
export const pokemonFragment = q
.fragmentForType<'pokemon'>()
.project({...})
export type PokemonType = InferFragmentType<typeof pokemonFragment>
If you want to create a type directly from the GroqQueryBuilder you can do something like that, I suppose
const pokemonQuery = q.star
.filterByType("pokemon")
.slice(0, 8)
.project(sub => ({
name: q.string(),
attack: sub.field("base.Attack", q.number()),
types: sub.field("types[]").deref().project({
name: q.string(),
}),
}))
type Pokemon = InferResultItem<typeof pokemonQuery> // will be a single Item from the Query
type Pokemons = InferResultType<typeof pokemonQuery> // will be Array<Pokemon> (which is the result of the query)
Thank you so much for pointing out these areas. I will definitely consider how to add these to the migration guide! Thank you @matzexcom for your answers, you're doing such a great job of migrating and finding all the new patterns!
One thing I'd like to clarify, if you're doing a "root" level projection via q("").grab({...}) then yes, you can now just do:
q.project({
products: q.star.filterByType("product"),
categories: q.star.filterByType("category"),
})
Regarding sanityImage, I'm eager to hear how helpful it would be to recreate this helper?
I was hoping that it would be easy enough (and/or obvious enough) that you could create your own fragment, like:
q.fragment<SanitySchema.Image>().project({
asset: true,
...etc
})
but I'm eager to see how you were using the sanityImage helper, and I'd be happy to port it over!
Thanks for all the help here.
As for the sanityImage helper, I think it was being used fairly standard. I just wasn't clear on the migration path for it as it disappeared from the exports but without a mention in the migration guide. Hope that helps clear that up.
I would also like to add the nullToUndefined helper is not obvious how to upgrade.
I think we have found the solution using q.string().nullable().transform(val => val ?? undefined)
Perhaps it could also be added to the docs?
@scottquested Can you share some use-cases for nullToUndefined?
The most common use-case I know of is to provide defaults, so we did add a helper for this, like q.default(q.string(), ""). It's basically the same as q.string().optional().default("") but it works for nulls.
We do have documentation for migrating from grab$ to project by using q.default(), which is similar to this use-case, but perhaps we do need to better document how to migrate away from nullToUndefined too.
@scottrippey So we use it mainly for this type of use case
export const someButtonId = {
id: nullToUndefined(q.string().optional()),
} satisfies Selection;
Which with the new syntax we think should look like this
export const someButtonId = {
id: q.string().nullable().transform(val => val ?? undefined)
} satisfies Selection;