Socket
Socket
Sign inDemoInstall

groq-builder

Package Overview
Dependencies
2
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    groq-builder

A **schema-aware**, strongly-typed GROQ query builder. It enables you to use **auto-completion** and **type-checking** for your GROQ queries.


Version published
Weekly downloads
648
decreased by-27.19%
Maintainers
1
Created
Weekly downloads
 

Readme

Source

groq-builder

A schema-aware, strongly-typed GROQ query builder.
It enables you to use auto-completion and type-checking for your GROQ queries.

In case you're wondering "What is GROQ?"

From https://www.sanity.io/docs/groq:

"GROQ is Sanity's open-source query language. It's a powerful and intuitive language that's easy to learn. With GROQ you can describe exactly what information your application needs, join information from several sets of documents, and stitch together a very specific response with only the exact fields you need."

Features

  • Schema-aware - Use your sanity.config.ts for auto-completion and type-checking
  • Strongly-typed - Query results are strongly typed, based on the schema
  • Optional runtime validation - Validate or transform query results at run-time, with broad or granular levels

Example

import { createGroqBuilder } from 'groq-builder';
import type { SchemaConfig } from './schema-config';
//            ☝️ Note:
// Please see the "Schema Configuration" docs 
// for an overview of this SchemaConfig type 

const q = createGroqBuilder<SchemaConfig>()

const productsQuery = (
  q.star
   .filterByType('products')
   .order('price desc')
   .slice(0, 10)
   .project(q => ({
     name: true,
     price: true,
     slug: q.field("slug.current"),
     imageUrls: q.field("images[]").deref().field("url")
   }))
);

In the above query, ALL fields are strongly-typed, according to the Sanity schema defined in sanity.config.ts!

  • All strings like 'products', 'price desc', and 'images[]' are strongly-typed, based on the matching field definitions.
  • In the projection, name and price are strongly-typed based on the fields of product.
  • In the projection, sub-queries are strongly typed too.

This example generates the following GROQ query:

*[_type == "products"] | order(price desc)[0...10] {
  name,
  price,
  "slug": slug.current,
  "imageUrls": images[]->url
}

Query Result Types

The above productsQuery example generates the following results type:

import type { InferResultType } from 'groq-builder';

type ProductsQueryResult = InferResultType<typeof productsQuery>;
//   👆 Evaluates to the following:
type ProductsQueryResult = Array<{
  name: string,
  price: number,
  slug: string,
  imageUrls: Array<string>,
}>;

Optional Runtime Validation and Custom Parsing

You can add custom runtime validation and/or parsing logic into your queries, using the validate method.

The validate function accepts a simple function:

const products = q.star.filterByType('products').project(q => ({
  name: true,
  price: true,
  priceFormatted: q.field("price").validate(price => formatCurrency(price)),
}));

It is also compatible with Zod, and can take any Zod parser or validation logic:

const products = q.star.filterByType('products').project(q => ({
  name: z.string(),
  slug: ["slug.current", z.string().optional()],
  price: q.field("price").validate(z.number().nonnegative()),
}));

Schema Configuration

The entry-point to this library is the createGroqBuilder<SchemaConfig>() function, which returns a strongly-typed q object. You must supply the SchemaConfig type parameter, which lists all document types from your Sanity Schema.

There are 2 approaches for creating this Schema. You can specify the Schema manually, or you can auto-generate the types based on your sanity.config.ts.

Manually typing your Sanity Schema

The simplest way to create a Sanity Schema is to manually specify the document types. Here's a working example:

import { createGroqBuilder } from './index';

declare const references: unique symbol;
type Product = {
  _type: "product";
  _id: string;
  name: string;
  price: number;
  images: Array<{ width: number; height: number; url: string; }>;
  category: { _type: "reference"; _ref: string; [references]: "category"; };
}
type Category = {
  _type: "category";
  _id: string;
  name: string;
  products: Array<{ _type: "reference"; _ref: string; [references]: "product"; }>;
}

export type SchemaConfig = {
  documentTypes: Product | Category;
  referenceSymbol: typeof references;
}

export const q = createGroqBuilder<SchemaConfig>();

The only complexity is how references are handled. In the Sanity data, the reference object doesn't say what kind of document it's referencing. We have to add this type information, using a unique symbol. So above, we added [references]: "category" to capture the reference type. This information is used by the .deref() method to ensure we follow references correctly.

Automatically generating your Sanity Schema

Fortunately, there is a way to automatically generate the Sanity Schema, using the Sanity configuration itself (sanity.config.ts). This workflow has 2 steps: inferring types from the config, then copying the compiled types to your application.

Augment your sanity.config.ts to infer the types

In the repo with your Sanity configuration (sanity.config.ts), use the @sanity-typed/types library to augment your configuration code.

This is pretty easy, and involves:

  • Changing your imports from 'sanity'; to from '@sanity-typed/types'
  • Adding as const in a few places (according to the docs)

Then, in your schema.config.ts, you infer all document types by adding:

import { InferSchemaValues } from '@sanity-typed/types';
export type SanityValues = InferSchemaValues<typeof config>;
Compile the types and copy to your application

Now that you've got the SanityValues type, you'll need to compile the types, and copy them to your application (where you're using groq-builder).

Normally you could use tsc to compile the types, and copy them over. However, there is a far better approach: use the ts-simplify CLI tool to compile and simplify the types.

From your Sanity repo, run:

npx ts-simplify ./sanity.config.ts ./sanity-schema.ts

This generates a ./sanity-schema.ts file that has no dependencies, just the Sanity types!

Move this file to your application (where you're using groq-builder), and finally, glue it all together like so:

./q.ts

import { createGroqBuilder, ExtractDocumentTypes } from 'groq-builder';
import { referenced, SanityValues } from './sanity-schema'; // This is the generated file

export const q = createGroqBuilder<{
  documentTypes: ExtractDocumentTypes<SanityValues>;
  referenceSymbol: typeof referenced;
}>();

Keywords

FAQs

Last updated on 19 Apr 2024

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc