Skip to content

Latest commit

 

History

History

groq-js

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

@sanity-typed/groq-js

NPM Downloads GitHub commit activity (branch) GitHub Repo stars GitHub contributors GitHub issues by-label Minified Size License

GitHub Sponsors

groq-js with typed GROQ Results

Page Contents

Install

npm install groq-js @sanity-typed/groq-js

Usage

Use parse and evaluate exactly as you would from groq-js. Then, use the results with the typescript types!

Typically, this isn't used directly, but via @sanity-typed/client-mock's methods that produce groq results. But it can be done directly:

your-typed-groq-js.ts:

// import { evaluate, parse } from "groq-js";
import { evaluate, parse } from "@sanity-typed/groq-js";

const input = '*[_type == "product"]{productName}';

const tree = parse(input);
/**
 *  typeof tree === {
 *    type: "Projection";
 *    base: {
 *      type: "Filter";
 *      base: {
 *        type: "Everything";
 *      };
 *      expr: {
 *        type: "OpCall";
 *        op: "==";
 *        left: {
 *          name: "_type";
 *          type: "AccessAttribute";
 *        };
 *        right: {
 *          type: "Value";
 *          value: "product";
 *        };
 *      };
 *    };
 *    expr: {
 *      type: "Object";
 *      attributes: [{
 *        type: "ObjectAttributeValue";
 *        name: "productName";
 *        value: {
 *          type: "AccessAttribute";
 *          name: "productName";
 *        };
 *      }];
 *    };
 *  }
 */

const value = await evaluate(tree, {
  dataset: [
    {
      _type: "product",
      productName: "Some Cool Product",
      // ...
    },
    {
      _type: "someOtherType",
      otherField: "foo",
      // ...
    },
  ],
});

const result = await value.get();
/**
 *  typeof result === [{
 *    productName: "Some Cool Product";
 *  }]
 */

Breaking Changes

1 to 2

Typescript version from 5.4.2 <= x <= 5.6.3

The supported Typescript version is now 5.4.2 <= x <= 5.6.3. Older versions are no longer supported and newer versions will be added as we validate it.

Considerations

Using your derived types

You can also use your typed schema to keep parity with the types your typed client would receive.

npm install sanity groq-js @sanity-typed/types @sanity-typed/groq-js

product.ts:

// import { defineArrayMember, defineField, defineType } from "sanity";
import {
  defineArrayMember,
  defineField,
  defineType,
} from "@sanity-typed/types";

/** No changes using defineType, defineField, and defineArrayMember */
export const product = defineType({
  name: "product",
  type: "document",
  title: "Product",
  fields: [
    defineField({
      name: "productName",
      type: "string",
      title: "Product name",
      validation: (Rule) => Rule.required(),
    }),
    defineField({
      name: "tags",
      type: "array",
      title: "Tags for item",
      of: [
        defineArrayMember({
          type: "object",
          name: "tag",
          fields: [
            defineField({ type: "string", name: "label" }),
            defineField({ type: "string", name: "value" }),
          ],
        }),
      ],
    }),
  ],
});

sanity.config.ts:

import { structureTool } from "sanity/structure";

// import { defineConfig } from "sanity";
import { defineConfig } from "@sanity-typed/types";
import type { InferSchemaValues } from "@sanity-typed/types";

import { post } from "./schemas/post";
import { product } from "./schemas/product";

/** No changes using defineConfig */
const config = defineConfig({
  projectId: "59t1ed5o",
  dataset: "production",
  plugins: [structureTool()],
  schema: {
    types: [
      product,
      // ...
      post,
    ],
  },
});

export default config;

/** Typescript type of all types! */
export type SanityValues = InferSchemaValues<typeof config>;
/**
 *  SanityValues === {
 *    product: {
 *      _createdAt: string;
 *      _id: string;
 *      _rev: string;
 *      _type: "product";
 *      _updatedAt: string;
 *      productName: string;
 *      tags?: {
 *        _key: string;
 *        _type: "tag";
 *        label?: string;
 *        value?: string;
 *      }[];
 *    };
 *    // ... all your types!
 *  }
 */

your-typed-groq-js-with-sanity-types.ts:

// import { evaluate, parse } from "groq-js";
import { evaluate, parse } from "@sanity-typed/groq-js";
import type { DocumentValues } from "@sanity-typed/types";

import type { SanityValues } from "./sanity.config";

const input = '*[_type == "product"]{productName}';

const tree = parse(input);

const value = await evaluate(tree, {
  dataset: [
    {
      _type: "product",
      productName: "Some Cool Product",
      // ...
    },
    {
      _type: "someOtherType",
      otherField: "foo",
      // ...
    },
  ] satisfies DocumentValues<SanityValues>[],
});

const result = await value.get();
/**
 *  typeof result === {
 *    productName: string;
 *  }[]
 */

// Notice how `productName` is inferred as a `string`, not as `"Some Cool Product"`.
// Also, it's in an array as opposed to a tuple.
// This resembles the types you'd receive from @sanity-typed/client,
// which wouldn't be statically aware of `"Some Cool Product"` either.

The parsed tree changes in seemingly breaking ways

@sanity-typed/groq attempts to type its parsed types as close as possible to groq-js's parse function output. Any fixes to match it more correctly won't be considered a major change and, if groq-js changes it's output in a version update, we're likely to match it. If you're using the parsed tree's types directly, this might cause your code to break. We don't consider this a breaking change because the intent of these groq libraries is to match the types of a groq query as closely as possible.

GROQ Query results changes in seemingly breaking ways

Similar to parsing, evaluating groq queries will attempt to match how sanity actually evaluates queries. Again, any fixes to match that or changes to groq evaluation will likely not be considered a major change but, rather, a bug fix.

Typescript Errors in IDEs

Often you'll run into an issue where you get typescript errors in your IDE but, when building workspace (either you studio or app using types), there are no errors. This only occurs because your IDE is using a different version of typescript than the one in your workspace. A few debugging steps:

VSCode

Type instantiation is excessively deep and possibly infinite

🚨 CHECK Typescript Errors in IDEs FIRST!!! ISSUES WILL GET CLOSED IMMEDIATELY!!! 🚨

You might run into the dreaded Type instantiation is excessively deep and possibly infinite error when writing GROQ queries. This isn't too uncommon with more complex GROQ queries. Unfortunately, this isn't a completely avoidable problem, as typescript has limits on complexity and parsing types from a string is an inherently complex problem. A set of steps for a workaround:

  1. While not ideal, use @ts-expect-error to disable the error. You could use @ts-ignore instead, but ideally you'd like to remove the comment if a fix is released.
  2. You still likely want manual types. Intersect the returned type with whatever is missing as a patch.
  3. Create a PR in groq/src/specific-issues.test.ts with your issue. #642 is a great example for this. Try to reduce your query and config as much as possible. The goal is a minimal reproduction.
  4. If a PR isn't possible, make an issue with the same content. ie, the query and config you're using. Again, reduce them as much as possible. And then, now that you've done all the work, move it into a PR instead!
  5. I'm one person and some of these issues are quite complex. Take a stab at fixing the bug! There's a ridiculous amount of tests so it's relatively safe to try things out.

People will sometimes create a repo with their issue. Please open a PR with a minimal test instead. Without a PR there will be no tests reflecting your issue and it may appear again in a regression. Forking a github repo to make a PR is a more welcome way to contribute to an open source library.