Utility Methods
makeSafeQueryRunner
A wrapper around q
so you can easily use groqd
with an actual fetch implementation, with a sprinkle of sugar to improve error message legibility.
Pass makeSafeQueryRunner
a "query executor" of the shape type QueryExecutor = (query: string, ...rest: any[]) => Promise<any>
, and it will return a "query runner" function. This is best illustrated with an example:
import sanityClient from "@sanity/client";
import { q } from "groqd";
// Wrap sanityClient.fetch
const client = sanityClient({
/* ... */
});
export const runQuery = makeSafeQueryRunner((query) => client.fetch(query));
// 👇 Now you can run queries and `data` is strongly-typed, and runtime-validated.
const data = await runQuery(
q("*").filter("_type == 'pokemon'").grab({ name: q.string() }).slice(0, 150)
);
In Sanity workflows, you might also want to pass e.g. params to your client.fetch
call. To support this add additional arguments to your makeSafeQueryRunner
query executor's arguments as below.
// ...
export const runQuery = makeSafeQueryRunner(
// 👇 add a second arg
(query, params: Record<string, unknown> = {}) => client.fetch(query, params)
);
const data = await runQuery(
q("*").filter("_type == 'pokemon' && _id == $id").grab({ name: q.string() }),
{ id: "pokemon.1" } // 👈 and optionally pass them here.
);
The makeSafeQueryRunner
utility adds a bit of error handling around parsing, and if parsing fails (due to Zod validation), makeSafeQueryRunner
will throw a GroqdParseError
error that contains an error message such as:
Error parsing `result[0].things[0].name`: Expected string, received number.
You can import the GroqdParseError
class directly from groqd
if you need it for instanceof
checks.
The GroqdParseError
class contains a zodError
field to view the full Zod error, and a .rawResponse
field so you can view the raw response that failed validation (helpful for debugging).
nullToUndefined
GROQ will return null
if you query for a value that does not exist. This can lead to confusion when writing queries, because Zod's .optional().default("default value")
doesn't work with null values. groqd
ships with a nullToUndefined
method that will preprocess null
values into undefined
to smooth over this rough edge.
q("*")
.filter("_type == 'pokemon'")
.grab({
name: q.string(),
// 👇 Missing field, allow us to set a default value when it doesn't exist
foo: nullToUndefined(q.string().optional().default("bar")),
})
The nullToUndefined
helper can also accept a Selection
object to apply to an entire selection.
q("*")
.filter("_type == 'pokemon'")
.grab(nullToUndefined({
name: q.string(),
foo: q.string().optional().default("bar"),
}))
sanityImage
A convenience method to make it easier to generate image queries for Sanity's image type. Supports fetching various info from both image
documents and asset
documents.
In its simplest form, it looks something like this:
import { q, sanityImage } from "groqd";
q("*")
.filter("_type == 'pokemon'")
.grab({
cover: sanityImage("cover"), // 👈 just pass the field name
});
// -> { cover: { _key: string; _type: string; asset: { _type: "reference"; _ref: string; } } }[]
which will allow you to fetch the minimal/basic image document information.
sanityImage
used to be attached to q
as q.sanityImage
. Due to its complexity, it has since been moved out into its own standalone utility method.
sanityImage
's isList
option
If you have an array of image documents, you can pass isList: true
to an options object as the second argument to sanityImage
method.
q("*")
.filter("_type == 'pokemon'")
.grab({
images: sanityImage("images", { isList: true }), // 👈 fetch as a list
});
// -> { images: { ... }[] }[]
sanityImage
's withCrop
option
Sanity's image document has fields for crop information, which you can query for with the withCrop
option.
q("*")
.filter("_type == 'pokemon'")
.grab({
cover: sanityImage("cover", { withCrop: true }), // 👈 fetch crop info
});
// -> { cover: { ..., crop: { top: number; bottom: number; left: number; right: number; } | null } }[]
sanityImage
's withHotspot
option
Sanity's image document has fields for hotspot information, which you can query for with the withHotspot
option.
q("*")
.filter("_type == 'pokemon'")
.grab({
cover: sanityImage("cover", { withHotspot: true }), // 👈 fetch hotspot info
});
// -> { cover: { ..., hotspot: { x: number; y: number; height: number; width: number; } | null } }[]
sanityImage
's additionalFields
option
Sanity allows you to add additional fields to their image documents, such as alt text or descriptions. The additionalFields
option allows you to specify such fields to query with your image query.
q("*")
.filter("_type == 'pokemon'")
.grab({
cover: sanityImage("cover", {
// 👇 fetch additional fields
additionalFields: {
alt: q.string(),
description: q.string(),
},
}),
});
// -> { cover: { ..., alt: string, description: string } }[]
sanityImage
's withAsset
option
Sanity's image documents have a reference to an asset document that contains a whole host of information relative to the uploaded image asset itself. The sanityImage
will allow you to dereference this asset document and query various fields from it.
You can pass an array to the withAsset
option to specify which fields you want to query from the asset document:
- pass
"base"
to query the base asset document fields, includingextension
,mimeType
,originalFilename
,size
,url
, andpath
. - pass
"dimensions"
to query the asset document'smetadata.dimensions
field, useful if you need the image's original dimensions or aspect ratio. - pass
"location"
to query the asset document'smetadata.location
field. - pass
"lqip"
to query the asset'smetadata.lqip
(Low Quality Image Placeholder) field, useful if you need to display LQIPs. - pass
"hasAlpha"
to query the asset'smetadata.hasAlpha
field, useful if you need to know if the image has an alpha channel. - pass
"isOpaque"
to query the asset'smetadata.isOpaque
field, useful if you need to know if the image is opaque. - pass
"blurHash"
to query the asset'smetadata.blurHash
field, useful if you need to display blurhashes. - pass
"palette"
to query the asset document'smetadata.palette
field, useful if you want to use the image's color palette in your UI.
An example:
q("*")
.filter("_type == 'pokemon'")
.grab({
cover: sanityImage("cover", {
withAsset: ["base", "dimensions"],
}),
});
// -> { cover: { ..., asset: { extension: string; mimeType: string; ...; metadata: { dimensions: { aspectRatio: number; height: number; width: number; }; }; }; } }[]